diff --git a/examples/3d-island-terrain/3d-island-terrain.json b/examples/3d-island-terrain/3d-island-terrain.json index 9e8961495..705fee01a 100644 --- a/examples/3d-island-terrain/3d-island-terrain.json +++ b/examples/3d-island-terrain/3d-island-terrain.json @@ -5094,23 +5094,24 @@ { "author": "", "category": "Camera", - "dimension": "", + "dimension": "3D", "extensionNamespace": "", "fullName": "Third person camera", - "gdevelopVersion": "", + "gdevelopVersion": ">=5.5.269", "helpPath": "", "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSI3LDEwIDEsMTMgNywxNiAxMywxMyAiLz4NCjxwb2x5bGluZSBjbGFzcz0ic3QwIiBwb2ludHM9IjEsMTMgMSwyMCA3LDIzIDEzLDIwIDEzLDEzICIvPg0KPGxpbmUgY2xhc3M9InN0MCIgeDE9IjciIHkxPSIxNiIgeDI9IjciIHkyPSIyMyIvPg0KPGxpbmUgY2xhc3M9InN0MCIgeDE9IjMxIiB5MT0iMTYiIHgyPSIyMSIgeTI9IjgiLz4NCjxsaW5lIGNsYXNzPSJzdDAiIHgxPSIyMSIgeTE9IjI0IiB4Mj0iMzEiIHkyPSIxNiIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTIyLjcsMjIuNkMyMCwyMS43LDE4LDE5LjEsMTgsMTZjMC0zLjEsMi01LjcsNC43LTYuNiIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTE5LjgsMTEuM2MxLjQsMS4xLDIuMiwyLjgsMi4yLDQuN2MwLDEuOS0wLjksMy42LTIuMiw0LjciLz4NCjwvc3ZnPg0K", "name": "ThirdPersonCamera", "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/Line Hero Pack/Master/SVG/Virtual Reality/94e95d2c318e1f3dc7151a351024e13c574e1e44669c6696aa107d60230073f6_Virtual Reality_3d_vision_eye_vr.svg", - "shortDescription": "Move the camera to look at an object from a given distance.", - "version": "1.5.0", + "shortDescription": "Third-person camera orbiting an object at configurable distance, elevation, and rotation.", + "version": "1.9.0", "description": [ "Move the camera to look at an object from a given distance with a rotation and an elevation angles.", "", "It can be useful for:", - "- Third person camera", - "- Isometric-like point of view", - "" + "- 3D platformer games ([open the project online](https://editor.gdevelop.io/?project=example://3d-platformer))", + "- 3D racing games ([open the project online](https://editor.gdevelop.io/?project=example://3d-car-coin-hunt))", + "- Orbit camera controls ([open the project online](https://editor.gdevelop.io/?project=example://3d-tile-based-city-builder))", + "- Isometric-like point of view ([open the project online](https://editor.gdevelop.io/?project=example://3d-road-crosser))" ], "origin": { "identifier": "ThirdPersonCamera", @@ -5127,6 +5128,76 @@ "globalVariables": [], "sceneVariables": [], "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": [ + "//@ts-ignore", + "if (gdjs.__thirdPersonCameraExtension) {", + " //@ts-ignore", + " return;", + "}", + "", + "/**", + " * It can be built the follow:", + " * ", + " * `objectDefaultRotation.setFromAxisAngle(new THREE.Vector3(0, 0, 1), -Math.PI/2);`", + " * ", + " * `objectDefaultRotation.multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), Math.PI/2));`", + " * ", + " */", + "const objectDefaultRotation = new THREE.Quaternion(0.5, -0.5, -0.5, 0.5);", + "const targetQuaternion = new THREE.Quaternion();", + "const yAxis = new THREE.Vector3(0, 1, 0);", + "const zAxis = new THREE.Vector3(0, 0, 1);", + "const quaternion = new THREE.Quaternion();", + "const objectRotation = new THREE.Euler();", + "", + "/**", + " * @param object {gdjs.RuntimeObject}", + " * @param ratio {number}", + " * @param elevationAngleOffset {number}", + " */", + "function slerpCamera(object, ratio, rotationAngleOffset, elevationAngleOffset) {", + " const layer = object.getInstanceContainer().getLayer(object.getLayer());", + " const threeCamera = layer.getRenderer().getThreeCamera();", + " const threeObject = object.get3DRendererObject();", + "", + " objectRotation.copy(threeObject.rotation);", + " objectRotation.z = -objectRotation.z;", + " objectRotation.x = -objectRotation.x;", + " targetQuaternion.setFromEuler(objectRotation);", + " targetQuaternion.multiply(quaternion.setFromAxisAngle(zAxis, -gdjs.toRad(rotationAngleOffset)));", + " targetQuaternion.multiply(quaternion.setFromAxisAngle(yAxis, gdjs.toRad(elevationAngleOffset)));", + " targetQuaternion.multiply(objectDefaultRotation);", + "", + " threeCamera.quaternion.slerp(targetQuaternion, ratio);", + "", + " // The layer angle takes over the 3D camera Z rotation.", + " layer.setCameraRotation(gdjs.toDegrees(-threeCamera.rotation.z));", + "}", + "", + "//@ts-ignore", + "gdjs.__thirdPersonCameraExtension = {", + " slerpCamera,", + "};", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, { "description": "Move the camera to look at a position from a distance.", "fullName": "Look at a position from a distance (deprecated)", @@ -5638,6 +5709,9 @@ "eventsFunctionsFolderStructure": { "folderName": "__ROOT", "children": [ + { + "functionName": "DefineHelperClasses" + }, { "functionName": "LookFromDistanceAtPosition" }, @@ -5672,6 +5746,7 @@ "name": "ThirdPersonCamera", "objectType": "", "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", "eventsFunctions": [ { "fullName": "", @@ -5679,6 +5754,21 @@ "name": "onCreated", "sentence": "", "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, { "type": "BuiltinCommonInstructions::Comment", "color": { @@ -5707,6 +5797,18 @@ "" ] }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetElevationHalfwayDuration" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "ElevationHalfwayDuration", + "" + ] + }, { "type": { "value": "ThirdPersonCamera::ThirdPersonCamera::SetTranslationZHalfwayDuration" @@ -5748,46 +5850,44 @@ "conditions": [ { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::PropertyHasJustBeenCreated" + "value": "BooleanVariable" }, "parameters": [ - "Object", - "Behavior" + "HasJustBeenCreated", + "True", + "" ] } ], "actions": [ { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyHasJustBeenCreated" + "value": "SetBooleanVariable" }, "parameters": [ - "Object", - "Behavior", + "HasJustBeenCreated", + "False", "" ] }, { "type": { - "value": "SetCameraAngle" + "value": "SetNumberVariable" }, "parameters": [ - "", + "CameraZ", "=", - "Object.Angle() + 90", - "Object.Layer()", - "0" + "Object.Object3D::CenterZ()" ] }, { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyCameraZ" + "value": "ThirdPersonCamera::ThirdPersonCamera::JumpToTargetedRotation" }, "parameters": [ "Object", "Behavior", - "=", - "Object.Object3D::CenterZ()" + "" ] } ] @@ -5798,11 +5898,12 @@ { "type": { "inverted": true, - "value": "ThirdPersonCamera::ThirdPersonCamera::PropertyIsCalledManually" + "value": "BooleanVariable" }, "parameters": [ - "Object", - "Behavior" + "IsCalledManually", + "True", + "" ] } ], @@ -5870,11 +5971,11 @@ "actions": [ { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyIsCalledManually" + "value": "SetBooleanVariable" }, "parameters": [ - "Object", - "Behavior", + "IsCalledManually", + "False", "" ] }, @@ -5927,29 +6028,146 @@ "comment": "At each frame, the camera must catchup the target by a given ratio (speed)\ncameraX(t) - targetX = (cameraX(t - 1) - targetX) * (1 - speed)\n\nThe frame rate must not impact on the catch-up speed, we don't want a speed in ratio per frame but a speed ratio per second, like this:\ncameraX(t) - targetX = (cameraX(t - 1s) - targetX) * (1 - speed)\n\nOk, but we still need to process each frame, we can use a exponent for this:\ncameraX(t) - targetX = (cameraX(t - timeDelta) - targetX) * (1 - speed)^timeDelta\ncameraX(t) = targetX + (cameraX(t - timeDelta) - targetX) * exp(timeDelta * ln((1 - speed)))\n\npow is probably more efficient than precalculated log if the speed is changed continuously but this might be rare enough." }, { - "colorB": 228, - "colorG": 176, - "colorR": 74, - "creationTime": 0, - "name": "Z translation", - "source": "", - "type": "BuiltinCommonInstructions::Group", + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "RotationMode", + "=", + "\"ZYX\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::Slerp" + }, + "parameters": [ + "Object", + "Behavior", + "1 - exp(TimeDelta() * RotationLogSpeed)", + "RotationAngleOffset", + "ElevationAngleOffset", + "" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::MoveToObject" + }, + "parameters": [ + "Object", + "Behavior", + "OffsetY", + "OffsetX", + "OffsetZ", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "-", + "Distance * Scene3D::CameraForwardX(Object.Layer())", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "-", + "Distance * Scene3D::CameraForwardY(Object.Layer())", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "-", + "Distance * Scene3D::CameraForwardZ(Object.Layer())", + "Object.Layer()", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "RotationMode", + "!=", + "\"ZYX\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "False", + "" + ] + } + ] + } + ], + "actions": [], "events": [ { - "type": "BuiltinCommonInstructions::Standard", - "conditions": [], - "actions": [], + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Z translation", + "source": "", + "type": "BuiltinCommonInstructions::Group", "events": [ { "type": "BuiltinCommonInstructions::Standard", "conditions": [ { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::PropertyCameraZ" + "value": "NumberVariable" }, "parameters": [ - "Object", - "Behavior", + "CameraZ", "<", "Object.Behavior::FreeAreaZMin()" ] @@ -5958,11 +6176,10 @@ "actions": [ { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyCameraZ" + "value": "SetNumberVariable" }, "parameters": [ - "Object", - "Behavior", + "CameraZ", "=", "Object.Behavior::FreeAreaZMin() + (CameraZ - Object.Behavior::FreeAreaZMin()) * exp(TimeDelta() * TranslationZLogSpeed)" ] @@ -5974,11 +6191,10 @@ "conditions": [ { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::PropertyCameraZ" + "value": "NumberVariable" }, "parameters": [ - "Object", - "Behavior", + "CameraZ", ">", "Object.Behavior::FreeAreaZMax()" ] @@ -5987,100 +6203,278 @@ "actions": [ { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyCameraZ" + "value": "SetNumberVariable" }, "parameters": [ - "Object", - "Behavior", + "CameraZ", "=", "Object.Behavior::FreeAreaZMax() + (CameraZ - Object.Behavior::FreeAreaZMax()) * exp(TimeDelta() * TranslationZLogSpeed)" ] } ] } - ] - } - ], - "parameters": [] - }, - { - "colorB": 228, - "colorG": 176, - "colorR": 74, - "creationTime": 0, - "name": "Z rotation", - "source": "", - "type": "BuiltinCommonInstructions::Group", - "events": [ - { - "type": "BuiltinCommonInstructions::Standard", - "conditions": [ - { - "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::PropertyIsRotatingWithObject" - }, - "parameters": [ - "Object", - "Behavior" - ] - } ], - "actions": [ - { - "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyTargetedRotationAngle" - }, - "parameters": [ - "Object", - "Behavior", - "=", - "Object.Angle()" - ] - } - ] + "parameters": [] }, { - "type": "BuiltinCommonInstructions::Standard", - "conditions": [], - "actions": [ + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Z rotation", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ { - "type": { - "value": "SetNumberVariable" - }, - "parameters": [ - "CameraAngle", - "=", - "TargetedRotationAngle + 90 + RotationAngleOffset + AngleDifference(CameraAngle(Object.Layer()), TargetedRotationAngle + 90 + RotationAngleOffset) * exp(TimeDelta() * RotationLogSpeed)" + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedRotationAngle", + "=", + "Object.Angle()" + ] + } ] }, { - "type": { - "value": "ThirdPersonCamera::LookFromDistanceAtPosition3D" - }, - "parameters": [ - "", - "Object.CenterX() + ThirdPersonCamera::RotatedX(CameraAngle, OffsetX, -OffsetY)", - "Object.CenterY() + ThirdPersonCamera::RotatedY(CameraAngle, OffsetX, -OffsetY)", - "CameraZ + OffsetZ", - "Distance", - "CameraAngle", - "Object.Object3D::RotationY() + ElevationAngleOffset", - "", - "" + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "RotationMode", + "!=", + "\"Z\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsElevatingWithObject", + "True", + "" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedElevationAngle", + "=", + "Object.Object3D::RotationY()" + ] + } ] - } - ], - "variables": [ + }, { - "folded": true, - "name": "CameraAngle", - "type": "number", - "value": 0 + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraRotationAngle", + "=", + "TargetedRotationAngle + 90 + RotationAngleOffset + AngleDifference(CameraAngle(Object.Layer()), TargetedRotationAngle + 90 + RotationAngleOffset) * exp(TimeDelta() * RotationLogSpeed)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraElevationAngle", + "=", + "TargetedElevationAngle + ElevationAngleOffset + AngleDifference(90 - Scene3D::CameraRotationX(Object.Layer()), TargetedElevationAngle + ElevationAngleOffset) * exp(TimeDelta() * ElevationLogSpeed)" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition3D" + }, + "parameters": [ + "", + "Object.CenterX() + ThirdPersonCamera::RotatedX(CameraRotationAngle, OffsetX, -OffsetY)", + "Object.CenterY() + ThirdPersonCamera::RotatedY(CameraRotationAngle, OffsetX, -OffsetY)", + "CameraZ + OffsetZ", + "Distance", + "CameraRotationAngle", + "CameraElevationAngle", + "Object.Layer()", + "" + ] + } + ], + "variables": [ + { + "folded": true, + "name": "CameraRotationAngle", + "type": "number", + "value": 0 + }, + { + "name": "CameraElevationAngle", + "type": "number", + "value": 0 + } + ] } - ] + ], + "parameters": [] } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Lerp camera", + "functionType": "Action", + "name": "Slerp", + "private": true, + "sentence": "Lerp the camera rotation to _PARAM0_ with a ratio of _PARAM2_ with rotation offset _PARAM3_° and elevation offset _PARAM4_°", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.RuntimeObject3D} */", + "const object = objects[0];", + "const ratio = eventsFunctionContext.getArgument(\"Ratio\");", + "const rotationAngleOffset = eventsFunctionContext.getArgument(\"NewRotationAngleOffset\");", + "const elevationAngleOffset = eventsFunctionContext.getArgument(\"NewElevationAngleOffset\");", + "", + "gdjs.__thirdPersonCameraExtension.slerpCamera(object, ratio, rotationAngleOffset, elevationAngleOffset);", + "" ], - "parameters": [] + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + }, + { + "description": "Ratio", + "name": "Ratio", + "type": "expression" + }, + { + "description": "Rotation angle offset", + "name": "NewRotationAngleOffset", + "type": "expression" + }, + { + "description": "Elevation angle offset", + "name": "NewElevationAngleOffset", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "fullName": "Move to object", + "functionType": "Action", + "name": "MoveToObject", + "private": true, + "sentence": "Change the camera position to _PARAM0_ with offset _PARAM2_, _PARAM3_, _PARAM4_ ", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "=", + "Object.CenterX() + LocalOffsetX * Object.Object3D::ForwardX() + LocalOffsetY * Object.Object3D::RightX() + LocalOffsetZ * Object.Object3D::UpX()", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "=", + "Object.CenterY() + LocalOffsetX * Object.Object3D::ForwardY() + LocalOffsetY * Object.Object3D::RightY() + LocalOffsetZ * Object.Object3D::UpY()", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "Object.Object3D::CenterZ() + LocalOffsetX * Object.Object3D::ForwardZ() + LocalOffsetY * Object.Object3D::RightZ() + LocalOffsetZ * Object.Object3D::UpZ()", + "Object.Layer()", + "" + ] + } + ] } ], "parameters": [ @@ -6094,6 +6488,21 @@ "name": "Behavior", "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", "type": "behavior" + }, + { + "description": "X offset", + "name": "LocalOffsetX", + "type": "expression" + }, + { + "description": "Y offset", + "name": "LocalOffsetY", + "type": "expression" + }, + { + "description": "Z offset", + "name": "LocalOffsetZ", + "type": "expression" } ], "objectGroups": [] @@ -6103,7 +6512,6 @@ "fullName": "Rotate the camera all the way", "functionType": "Action", "name": "JumpToTargetedRotation", - "private": true, "sentence": "Rotate the camera all the way to the targeted angle of _PARAM0_", "events": [ { @@ -6111,28 +6519,55 @@ "conditions": [ { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::PropertyIsRotatingWithObject" + "value": "BooleanVariable" }, "parameters": [ - "Object", - "Behavior" + "IsRotatingWithObject", + "True", + "" ] } ], "actions": [ { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyTargetedRotationAngle" + "value": "SetNumberVariable" }, "parameters": [ - "Object", - "Behavior", + "TargetedRotationAngle", "=", "Object.Angle()" ] } ] }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsElevatingWithObject", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedElevationAngle", + "=", + "Object.Object3D::RotationY()" + ] + } + ] + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [], @@ -6343,22 +6778,123 @@ "actions": [ { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyRotationHalfwayDuration" + "value": "SetNumberVariable" }, "parameters": [ - "Object", - "Behavior", + "RotationHalfwayDuration", "=", "Value" ] }, { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyRotationLogSpeed" + "value": "SetNumberVariable" }, "parameters": [ - "Object", - "Behavior", + "RotationLogSpeed", + "=", + "log(0.5) / Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the halfway time for elevation rotation of the object.", + "fullName": "Halfway time for elevation rotation", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "ElevationHalfwayDuration", + "sentence": "the halfway time for elevation rotation", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ElevationHalfwayDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ElevationHalfwayDuration", + "group": "Third person camera configuration", + "name": "SetElevationHalfwayDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "ln((1 - speed)) = ln(1 / 2) / halfwatTime" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ElevationHalfwayDuration", + "=", + "Value" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ElevationLogSpeed", "=", "log(0.5) / Value" ] @@ -6448,22 +6984,20 @@ "actions": [ { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyTranslationZHalfwayDuration" + "value": "SetNumberVariable" }, "parameters": [ - "Object", - "Behavior", + "TranslationZHalfwayDuration", "=", "Value" ] }, { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyTranslationZLogSpeed" + "value": "SetNumberVariable" }, "parameters": [ - "Object", - "Behavior", + "TranslationZLogSpeed", "=", "log(0.5) / Value" ] @@ -6625,11 +7159,10 @@ "actions": [ { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyFollowFreeAreaZMax" + "value": "SetNumberVariable" }, "parameters": [ - "Object", - "Behavior", + "FollowFreeAreaZMax", "=", "Value" ] @@ -6707,11 +7240,10 @@ "actions": [ { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyFollowFreeAreaZMin" + "value": "SetNumberVariable" }, "parameters": [ - "Object", - "Behavior", + "FollowFreeAreaZMin", "=", "Value" ] @@ -6789,11 +7321,10 @@ "actions": [ { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyDistance" + "value": "SetNumberVariable" }, "parameters": [ - "Object", - "Behavior", + "Distance", "=", "Value" ] @@ -6871,11 +7402,10 @@ "actions": [ { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyOffsetX" + "value": "SetNumberVariable" }, "parameters": [ - "Object", - "Behavior", + "OffsetX", "=", "Value" ] @@ -6953,11 +7483,10 @@ "actions": [ { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyOffsetY" + "value": "SetNumberVariable" }, "parameters": [ - "Object", - "Behavior", + "OffsetY", "=", "Value" ] @@ -7035,11 +7564,10 @@ "actions": [ { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyOffsetZ" + "value": "SetNumberVariable" }, "parameters": [ - "Object", - "Behavior", + "OffsetZ", "=", "Value" ] @@ -7117,11 +7645,10 @@ "actions": [ { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyRotationAngleOffset" + "value": "SetNumberVariable" }, "parameters": [ - "Object", - "Behavior", + "RotationAngleOffset", "=", "Value" ] @@ -7199,11 +7726,10 @@ "actions": [ { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyElevationAngleOffset" + "value": "SetNumberVariable" }, "parameters": [ - "Object", - "Behavior", + "ElevationAngleOffset", "=", "Value" ] @@ -7281,22 +7807,21 @@ "actions": [ { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyTargetedRotationAngle" + "value": "SetNumberVariable" }, "parameters": [ - "Object", - "Behavior", + "TargetedRotationAngle", "=", "Value" ] }, { "type": { - "value": "ThirdPersonCamera::ThirdPersonCamera::SetPropertyIsRotatingWithObject" + "value": "SetBooleanVariable" }, "parameters": [ - "Object", - "Behavior", + "IsRotatingWithObject", + "False", "" ] } @@ -7334,6 +7859,12 @@ { "functionName": "DoMoveCameraCloser" }, + { + "functionName": "Slerp" + }, + { + "functionName": "MoveToObject" + }, { "functionName": "JumpToTargetedRotation" }, @@ -7352,6 +7883,12 @@ { "functionName": "SetRotationHalfwayDuration" }, + { + "functionName": "ElevationHalfwayDuration" + }, + { + "functionName": "SetElevationHalfwayDuration" + }, { "functionName": "TranslationZHalfwayDuration" }, @@ -7442,16 +7979,29 @@ "value": "0.125", "type": "Number", "unit": "Second", - "label": "Halfway time for rotation", + "label": "Halfway time for rotation", "group": "Catch-up speed", + "quickCustomizationVisibility": "hidden", "name": "RotationHalfwayDuration" }, + { + "value": "0", + "type": "Number", + "unit": "Second", + "label": "Halfway time for elevation rotation", + "description": "Only used by Z and ZY rotation modes", + "group": "Catch-up speed", + "quickCustomizationVisibility": "hidden", + "name": "ElevationHalfwayDuration" + }, { "value": "0.125", "type": "Number", "unit": "Second", "label": "Halfway time on Z axis", + "description": "Only used by Z and ZY rotation modes", "group": "Catch-up speed", + "quickCustomizationVisibility": "hidden", "name": "TranslationZHalfwayDuration" }, { @@ -7492,6 +8042,7 @@ "unit": "DegreeAngle", "label": "Rotation angle offset", "group": "Position", + "advanced": true, "name": "RotationAngleOffset" }, { @@ -7509,6 +8060,7 @@ "label": "Follow free area top border on Z axis", "group": "Position", "advanced": true, + "quickCustomizationVisibility": "hidden", "name": "FollowFreeAreaZMax" }, { @@ -7518,6 +8070,7 @@ "label": "Follow free area bottom border on Z axis", "group": "Position", "advanced": true, + "quickCustomizationVisibility": "hidden", "name": "FollowFreeAreaZMin" }, { @@ -7527,6 +8080,13 @@ "hidden": true, "name": "RotationLogSpeed" }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "ElevationLogSpeed" + }, { "value": "", "type": "Number", @@ -7560,16 +8120,77 @@ "value": "true", "type": "Boolean", "label": "Automatically rotate the camera with the object", + "quickCustomizationVisibility": "hidden", "name": "IsRotatingWithObject" }, + { + "value": "false", + "type": "Boolean", + "label": "Automatically rotate the camera with the object (elevation)", + "description": "Best left unchecked, use the rotation mode instead", + "deprecated": true, + "quickCustomizationVisibility": "hidden", + "name": "IsElevatingWithObject" + }, + { + "value": "Z", + "type": "Choice", + "label": "Rotation mode", + "choices": [ + { + "label": "Horizontal rotation (good for characters)", + "value": "Z" + }, + { + "label": "Vertical and horizontal rotations (good for cars)", + "value": "ZY" + }, + { + "label": "All rotations (good for spaceships)", + "value": "ZYX" + } + ], + "name": "RotationMode" + }, { "value": "0", "type": "Number", "unit": "DegreeAngle", "label": "Targeted camera rotation angle", - "description": "When this angle is set, the camera follow this value instead of the object angle.", "hidden": true, "name": "TargetedRotationAngle" + }, + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "Targeted camera rotation angle", + "hidden": true, + "name": "TargetedElevationAngle" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Forward X", + "hidden": true, + "name": "ForwardX" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Forward Y", + "hidden": true, + "name": "ForwardY" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Forward Z", + "hidden": true, + "name": "ForwardZ" } ], "propertiesFolderStructure": { @@ -7584,6 +8205,9 @@ { "propertyName": "RotationHalfwayDuration" }, + { + "propertyName": "ElevationHalfwayDuration" + }, { "propertyName": "TranslationZHalfwayDuration" } @@ -7621,6 +8245,9 @@ { "propertyName": "RotationLogSpeed" }, + { + "propertyName": "ElevationLogSpeed" + }, { "propertyName": "TranslationZLogSpeed" }, @@ -7636,8 +8263,26 @@ { "propertyName": "IsRotatingWithObject" }, + { + "propertyName": "IsElevatingWithObject" + }, + { + "propertyName": "RotationMode" + }, { "propertyName": "TargetedRotationAngle" + }, + { + "propertyName": "TargetedElevationAngle" + }, + { + "propertyName": "ForwardX" + }, + { + "propertyName": "ForwardY" + }, + { + "propertyName": "ForwardZ" } ] } @@ -7648,16 +8293,15 @@ { "author": "", "category": "General", - "dimension": "", "extensionNamespace": "", "fullName": "3D collision", - "gdevelopVersion": "", + "gdevelopVersion": ">=5.5.222", "helpPath": "", "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXZlY3Rvci1pbnRlcnNlY3Rpb24iIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMy4xNCwxQTIuMTQsMi4xNCAwIDAsMCAxLDMuMTRWNUgzVjNINVYxSDMuMTRNNywxVjNIMTBWMUg3TTEyLDFWM0gxNFY1SDE2VjMuMTRDMTYsMS45NiAxNS4wNCwxIDEzLjg2LDFIMTJNMSw3VjEwSDNWN0gxTTksN0M3Ljg5LDcgNyw3Ljg5IDcsOUM3LDExLjMzIDcsMTYgNywxNkM3LDE2IDExLjU3LDE2IDEzLjg2LDE2QTIuMTQsMi4xNCAwIDAsMCAxNiwxMy44NkMxNiwxMS41NyAxNiw3IDE2LDdDMTYsNyAxMS4zMyw3IDksN00xOCw3VjlIMjBWMTFIMjJWOUMyMiw3Ljg5IDIxLjExLDcgMjAsN0gxOE05LDlIMTRWMTRIOVY5TTEsMTJWMTMuODZDMSwxNS4wNCAxLjk2LDE2IDMuMTQsMTZINVYxNEgzVjEySDFNMjAsMTNWMTZIMjJWMTNIMjBNNywxOFYyMEM3LDIxLjExIDcuODksMjIgOSwyMkgxMVYyMEg5VjE4SDdNMjAsMThWMjBIMThWMjJIMjBDMjEuMTEsMjIgMjIsMjEuMTEgMjIsMjBWMThIMjBNMTMsMjBWMjJIMTZWMjBIMTNaIiAvPjwvc3ZnPg==", "name": "Collision3D", "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/7a5696a515bf40813692e118147568392a854f65f5b50750c9b9aaa967aba7df_vector-intersection.svg", "shortDescription": "Check collision and distance between 3D objects.", - "version": "0.2.0", + "version": "0.2.1", "description": "Check collision and distance between 3D objects and separate them from each other.", "origin": { "identifier": "Collision3D", @@ -8212,7 +8856,7 @@ "type": "BuiltinCommonInstructions::JsCode", "inlineCode": [ "\r", - "const otherObjects = eventsFunctionContext.getObjectsLists(\"OtherObject\");\r", + "const otherObjects = eventsFunctionContext.getObjects(\"OtherObject\");\r", "\r", "if (objects.length > 0 && otherObjects.length > 0) {\r", " eventsFunctionContext.returnValue = gdjs._collision3DExtension.getDistanceToObject(\r", @@ -8256,7 +8900,7 @@ "type": "BuiltinCommonInstructions::JsCode", "inlineCode": [ "\r", - "const otherObjects = eventsFunctionContext.getObjectsLists(\"OtherObject\");\r", + "const otherObjects = eventsFunctionContext.getObjects(\"OtherObject\");\r", "\r", "if (objects.length > 0 && otherObjects.length > 0) {\r", " eventsFunctionContext.returnValue = gdjs._collision3DExtension.getSqDistanceToObject(\r", @@ -8557,56 +9201,6 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "DefineHelperClasses" - }, - { - "functionName": "onFirstSceneLoaded" - }, - { - "folderName": "Position", - "children": [ - { - "functionName": "AreWithinDistance" - }, - { - "functionName": "Distance" - }, - { - "functionName": "SqDistance" - }, - { - "functionName": "DistanceToPosition" - }, - { - "functionName": "SqDistanceToPosition" - }, - { - "functionName": "SeparateFromObjects" - } - ] - }, - { - "folderName": "Objects", - "children": [ - { - "functionName": "PickNearest" - } - ] - }, - { - "folderName": "Collision", - "children": [ - { - "functionName": "AreInCollision" - } - ] - } - ] - }, "eventsBasedBehaviors": [], "eventsBasedObjects": [] }, @@ -8621,8 +9215,8 @@ "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPGNpcmNsZSBjbGFzcz0ic3QwIiBjeD0iMTYiIGN5PSIxNiIgcj0iMTMiLz4NCjxwb2x5bGluZSBjbGFzcz0ic3QwIiBwb2ludHM9IjI4LjQsMTIgMjAsMTIgMjAsMy42ICIvPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMjAsMjguNCAyMCwyMCAyOC40LDIwICIvPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMy42LDIwIDEyLDIwIDEyLDI4LjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSIxMiwzLjYgMTIsMTIgMy42LDEyICIvPg0KPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSIxNiw2IDE2LjcsNyAxNS4zLDcgIi8+DQo8cG9seWdvbiBjbGFzcz0ic3QwIiBwb2ludHM9IjE2LDI2IDE1LjMsMjUgMTYuNywyNSAiLz4NCjxwb2x5Z29uIGNsYXNzPSJzdDAiIHBvaW50cz0iNiwxNiA3LDE1LjMgNywxNi43ICIvPg0KPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSIyNiwxNiAyNSwxNi43IDI1LDE1LjMgIi8+DQo8L3N2Zz4NCg==", "name": "SpriteMultitouchJoystick", "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Videogames/Videogames_controller_joystick_arrows_direction.svg", - "shortDescription": "Joysticks or buttons for touchscreens.", - "version": "1.9.0", + "shortDescription": "On-screen multitouch joystick and buttons.", + "version": "1.9.2", "description": [ "Multitouch joysticks are objects showing a joystick on the screen, useful for mobile. They work like a physical gamepad:", "- 4 or 8 directions", @@ -14415,7 +15009,7 @@ "parameters": [ "Object", "PhysicsCar3D", - "SpriteMultitouchJoystick::StickForceX(ControllerIdentifier, \"Primary\")" + "SpriteMultitouchJoystick::StickForceX(ControllerIdentifier, SteerJoystickIdentifier)" ] } ] @@ -14445,7 +15039,7 @@ "parameters": [ "Object", "PhysicsCar3D", - "-SpriteMultitouchJoystick::StickForceY(ControllerIdentifier, \"Secondary\")" + "-SpriteMultitouchJoystick::StickForceY(ControllerIdentifier, SpeedJoystickIdentifier)" ] } ] @@ -15196,6 +15790,7 @@ "areaMinX": 0, "areaMinY": 0, "areaMinZ": 0, + "assetStoreTag": "multitouch joysticks", "defaultName": "Joystick", "description": "Joystick for touchscreens.", "fullName": "Multitouch Joystick", @@ -16629,15 +17224,13 @@ "propertyName": "ShouldBeHiddenWhenReleased" } ] - }, - "variants": [] + } } ] }, { "author": "PANDAKO-GitHub", "category": "Input", - "dimension": "", "extensionNamespace": "", "fullName": "Mouse Pointer Lock", "gdevelopVersion": ">=5.5.222", @@ -16645,8 +17238,8 @@ "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMTksMTkgMjQsMTkgMjQsMjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSI2LDIzIDExLDIzIDExLDE4ICIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTI0LDE5LjRjLTAuNywwLjgtMS40LDEuNi0yLjIsMi40Yy03LDctMTUuMywxMC4yLTE4LjUsN3MtMC4xLTExLjUsNy0xOC41czE1LjMtMTAuMiwxOC41LTcNCgljMS40LDEuNCwxLjYsMy42LDAuOCw2LjMiLz4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xMSwyMi41Yy0wLjMtMC4yLTAuNS0wLjUtMC44LTAuOGMtNy03LTEwLjItMTUuMy03LTE4LjVzMTEuNS0wLjEsMTguNSw3czEwLjIsMTUuMyw3LDE4LjUNCgljLTEuNywxLjctNC44LDEuNi04LjQsMC4xIi8+DQo8L3N2Zz4NCg==", "name": "MousePointerLock", "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Virtual Reality/Virtual Reality_360_rotate_vr_movement.svg", - "shortDescription": "This behavior removes the limit on the distance the mouse can move and hides the cursor.", - "version": "0.3.0", + "shortDescription": "Lock and hide mouse pointer for unlimited movement (e.g., FPS mouse look).", + "version": "0.3.1", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -17098,7 +17691,7 @@ "functionType": "Condition", "name": "isEmulatingPointerLock", "private": true, - "sentence": "The mouse pointer is lock is emulated", + "sentence": "The mouse pointer lock is emulated", "events": [ { "type": "BuiltinCommonInstructions::Standard", @@ -17415,68 +18008,12 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "onFirstSceneLoaded" - }, - { - "functionName": "onScenePostEvents" - }, - { - "functionName": "RequestPointerLock" - }, - { - "functionName": "ExitPointerLock" - }, - { - "functionName": "isPointerLocked" - }, - { - "functionName": "isPointerActuallyLocked" - }, - { - "functionName": "isEmulatingPointerLock" - }, - { - "functionName": "IsMoving" - }, - { - "functionName": "MovementX" - }, - { - "functionName": "SetMovementX" - }, - { - "functionName": "MovementY" - }, - { - "functionName": "SetMovementY" - }, - { - "functionName": "TouchX" - }, - { - "functionName": "TouchY" - }, - { - "functionName": "TouchSpeedFactor" - }, - { - "functionName": "SetTouchSpeedFactor" - } - ] - }, "eventsBasedBehaviors": [ { "description": "Control camera rotations with a mouse.", "fullName": "First person camera mouse mapper", - "helpPath": "", - "iconUrl": "", "name": "FirstPersonPointerMapper", "objectType": "", - "previewIconUrl": "", "eventsFunctions": [ { "fullName": "", @@ -17751,7 +18288,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "HorizontalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetHorizontalSpeed", "sentence": "", "events": [ @@ -17832,7 +18368,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalSpeed", "sentence": "", "events": [ @@ -17913,7 +18448,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMin", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMin", "sentence": "", "events": [ @@ -17994,7 +18528,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMax", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMax", "sentence": "", "events": [ @@ -18075,7 +18608,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "OffsetZ", - "group": "First person camera mouse mapper configuration", "name": "SetOffsetZ", "sentence": "", "events": [ @@ -18112,57 +18644,6 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "doStepPreEvents" - }, - { - "folderName": "Layers and cameras", - "children": [ - { - "functionName": "LookFromObjectEyes" - } - ] - }, - { - "folderName": "First person camera mouse mapper configuration", - "children": [ - { - "functionName": "HorizontalSpeed" - }, - { - "functionName": "SetHorizontalSpeed" - }, - { - "functionName": "VerticalSpeed" - }, - { - "functionName": "SetVerticalSpeed" - }, - { - "functionName": "VerticalAngleMin" - }, - { - "functionName": "SetVerticalAngleMin" - }, - { - "functionName": "VerticalAngleMax" - }, - { - "functionName": "SetVerticalAngleMax" - }, - { - "functionName": "OffsetZ" - }, - { - "functionName": "SetOffsetZ" - } - ] - } - ] - }, "propertyDescriptors": [ { "value": "", @@ -18216,32 +18697,7 @@ "name": "ShouldLock" } ], - "propertiesFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "propertyName": "Object3D" - }, - { - "propertyName": "HorizontalSpeed" - }, - { - "propertyName": "VerticalSpeed" - }, - { - "propertyName": "VerticalAngleMin" - }, - { - "propertyName": "VerticalAngleMax" - }, - { - "propertyName": "OffsetZ" - }, - { - "propertyName": "ShouldLock" - } - ] - } + "sharedPropertyDescriptors": [] } ], "eventsBasedObjects": [] @@ -33554,6 +34010,7 @@ "areaMinY": 0, "areaMinZ": 0, "defaultName": "Terrain", + "assetStoreTag": "3d terrains", "description": "A ground with hills, mountains or valleys that can be sculpted and painted in the scene editor, or changed with events. Add the 3D physics behavior to it to make objects walk or roll on it.", "fullName": "3D terrain", "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXRlcnJhaW4iIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTQsNkwxMC4yNSwxMUwxMy4xLDE0LjhMMTEuNSwxNkM5LjgxLDEzLjc1IDcsMTAgNywxMEwxLDE4SDIzTDE0LDZaIiAvPjwvc3ZnPg==", diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/README.md b/examples/starting-3d-grand-theft-auto-with-a-map/README.md deleted file mode 100644 index d193c4f61..000000000 --- a/examples/starting-3d-grand-theft-auto-with-a-map/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# 3D GTA with a map - -A 3D open world where you can walk, jump, get into a car and drive through a town, farms, forests and a tunnel through a hill, using keyboard or mobile controls. The map is a 3D terrain that can be sculpted and painted in the editor. - -The assets are CC0: the City Kits by Kenney, the "Ultimate Nature" pack by Quaternius and ground textures from ambientCG. diff --git a/examples/starting-expanded-3D-platformer/README.md b/examples/starting-expanded-3D-platformer/README.md new file mode 100644 index 000000000..0f62e21b8 --- /dev/null +++ b/examples/starting-expanded-3D-platformer/README.md @@ -0,0 +1,3 @@ +# 3D Platformer Expanded + +A 3D physics-based platformer in a valley made with a 3D terrain: dirt paths winding up hills, platforms on their tops, coins along the way, a pond and woods. The player controls a character that can move, jump and fall, push a 3D physics box, jump on static boxes and collect coins. A third-person camera follows behind the player, but can also be manually controlled. The game supports both keyboard and mouse controls as well as mobile touch controls. diff --git a/examples/starting-expanded-3D-platformer/assets/CoinPickUp.wav b/examples/starting-expanded-3D-platformer/assets/CoinPickUp.wav new file mode 100644 index 000000000..0509182e6 Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/CoinPickUp.wav differ diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Flat dark joystick border.png b/examples/starting-expanded-3D-platformer/assets/Flat dark joystick border.png similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Flat dark joystick border.png rename to examples/starting-expanded-3D-platformer/assets/Flat dark joystick border.png diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Flat dark joystick thumb.png b/examples/starting-expanded-3D-platformer/assets/Flat dark joystick thumb.png similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Flat dark joystick thumb.png rename to examples/starting-expanded-3D-platformer/assets/Flat dark joystick thumb.png diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Skybox_Bottom.png b/examples/starting-expanded-3D-platformer/assets/Skybox_Back.png similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Skybox_Bottom.png rename to examples/starting-expanded-3D-platformer/assets/Skybox_Back.png diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Skybox_Back.png b/examples/starting-expanded-3D-platformer/assets/Skybox_Bottom.png similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Skybox_Back.png rename to examples/starting-expanded-3D-platformer/assets/Skybox_Bottom.png diff --git a/examples/starting-expanded-3D-platformer/assets/Skybox_Front.png b/examples/starting-expanded-3D-platformer/assets/Skybox_Front.png new file mode 100644 index 000000000..018d5faac Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/Skybox_Front.png differ diff --git a/examples/starting-expanded-3D-platformer/assets/Skybox_Left.png b/examples/starting-expanded-3D-platformer/assets/Skybox_Left.png new file mode 100644 index 000000000..e4053f388 Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/Skybox_Left.png differ diff --git a/examples/starting-expanded-3D-platformer/assets/Skybox_Right.png b/examples/starting-expanded-3D-platformer/assets/Skybox_Right.png new file mode 100644 index 000000000..d14c192b1 Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/Skybox_Right.png differ diff --git a/examples/starting-expanded-3D-platformer/assets/Skybox_Top.png b/examples/starting-expanded-3D-platformer/assets/Skybox_Top.png new file mode 100644 index 000000000..e83ef6a1b Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/Skybox_Top.png differ diff --git a/examples/starting-expanded-3D-platformer/assets/StarterPushable.png b/examples/starting-expanded-3D-platformer/assets/StarterPushable.png new file mode 100644 index 000000000..f5edfc71e Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/StarterPushable.png differ diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/StarterToggle_Keyboard.png b/examples/starting-expanded-3D-platformer/assets/StarterToggle_Keyboard.png similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/StarterToggle_Keyboard.png rename to examples/starting-expanded-3D-platformer/assets/StarterToggle_Keyboard.png diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/StarterToggle_Touch.png b/examples/starting-expanded-3D-platformer/assets/StarterToggle_Touch.png similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/StarterToggle_Touch.png rename to examples/starting-expanded-3D-platformer/assets/StarterToggle_Touch.png diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/StartingCapsule.glb b/examples/starting-expanded-3D-platformer/assets/StartingCapsule.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/StartingCapsule.glb rename to examples/starting-expanded-3D-platformer/assets/StartingCapsule.glb diff --git a/examples/starting-expanded-3D-platformer/assets/StartingCoin.glb b/examples/starting-expanded-3D-platformer/assets/StartingCoin.glb new file mode 100644 index 000000000..7ed57e63a Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/StartingCoin.glb differ diff --git a/examples/starting-expanded-3D-platformer/assets/StartingWall.png b/examples/starting-expanded-3D-platformer/assets/StartingWall.png new file mode 100644 index 000000000..b3a097887 Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/StartingWall.png differ diff --git a/examples/starting-expanded-3D-platformer/assets/TerrainDirt.jpg b/examples/starting-expanded-3D-platformer/assets/TerrainDirt.jpg new file mode 100644 index 000000000..71221d59d Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/TerrainDirt.jpg differ diff --git a/examples/starting-expanded-3D-platformer/assets/TerrainDirtNormal.jpg b/examples/starting-expanded-3D-platformer/assets/TerrainDirtNormal.jpg new file mode 100644 index 000000000..f701da364 Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/TerrainDirtNormal.jpg differ diff --git a/examples/starting-expanded-3D-platformer/assets/TerrainGrass.jpg b/examples/starting-expanded-3D-platformer/assets/TerrainGrass.jpg new file mode 100644 index 000000000..81e81c1c2 Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/TerrainGrass.jpg differ diff --git a/examples/starting-expanded-3D-platformer/assets/TerrainGrassNormal.jpg b/examples/starting-expanded-3D-platformer/assets/TerrainGrassNormal.jpg new file mode 100644 index 000000000..c768d8df2 Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/TerrainGrassNormal.jpg differ diff --git a/examples/starting-expanded-3D-platformer/assets/TerrainRock.jpg b/examples/starting-expanded-3D-platformer/assets/TerrainRock.jpg new file mode 100644 index 000000000..5de6dfb13 Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/TerrainRock.jpg differ diff --git a/examples/starting-expanded-3D-platformer/assets/TerrainRockNormal.jpg b/examples/starting-expanded-3D-platformer/assets/TerrainRockNormal.jpg new file mode 100644 index 000000000..8d17bc073 Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/TerrainRockNormal.jpg differ diff --git a/examples/starting-expanded-3D-platformer/assets/TerrainSand.jpg b/examples/starting-expanded-3D-platformer/assets/TerrainSand.jpg new file mode 100644 index 000000000..c6f848c09 Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/TerrainSand.jpg differ diff --git a/examples/starting-expanded-3D-platformer/assets/TerrainSandNormal.jpg b/examples/starting-expanded-3D-platformer/assets/TerrainSandNormal.jpg new file mode 100644 index 000000000..ef35cddc8 Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/TerrainSandNormal.jpg differ diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Top arrow button.png b/examples/starting-expanded-3D-platformer/assets/Top arrow button.png similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Top arrow button.png rename to examples/starting-expanded-3D-platformer/assets/Top arrow button.png diff --git a/examples/starting-expanded-3D-platformer/assets/WaterNormalMap.jpg b/examples/starting-expanded-3D-platformer/assets/WaterNormalMap.jpg new file mode 100644 index 000000000..558426e0e Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/WaterNormalMap.jpg differ diff --git a/examples/starting-expanded-3D-platformer/assets/flag.glb b/examples/starting-expanded-3D-platformer/assets/flag.glb new file mode 100644 index 000000000..a22b2cebb Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/flag.glb differ diff --git a/examples/starting-expanded-3D-platformer/assets/flowers.glb b/examples/starting-expanded-3D-platformer/assets/flowers.glb new file mode 100644 index 000000000..dde578bb1 Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/flowers.glb differ diff --git a/examples/starting-expanded-3D-platformer/assets/mushrooms.glb b/examples/starting-expanded-3D-platformer/assets/mushrooms.glb new file mode 100644 index 000000000..71d012f9d Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/mushrooms.glb differ diff --git a/examples/starting-expanded-3D-platformer/assets/rocks.glb b/examples/starting-expanded-3D-platformer/assets/rocks.glb new file mode 100644 index 000000000..04e26ea65 Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/rocks.glb differ diff --git a/examples/starting-expanded-3D-platformer/assets/sign.glb b/examples/starting-expanded-3D-platformer/assets/sign.glb new file mode 100644 index 000000000..da5a3d1f5 Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/sign.glb differ diff --git a/examples/starting-expanded-3D-platformer/assets/stones.glb b/examples/starting-expanded-3D-platformer/assets/stones.glb new file mode 100644 index 000000000..42a6fa196 Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/stones.glb differ diff --git a/examples/starting-expanded-3D-platformer/assets/tree.glb b/examples/starting-expanded-3D-platformer/assets/tree.glb new file mode 100644 index 000000000..cfc7cfa1b Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/tree.glb differ diff --git a/examples/starting-expanded-3D-platformer/assets/treePine.glb b/examples/starting-expanded-3D-platformer/assets/treePine.glb new file mode 100644 index 000000000..8798c6bb2 Binary files /dev/null and b/examples/starting-expanded-3D-platformer/assets/treePine.glb differ diff --git a/examples/starting-expanded-3D-platformer/preview.png b/examples/starting-expanded-3D-platformer/preview.png new file mode 100644 index 000000000..5f1bc81d3 Binary files /dev/null and b/examples/starting-expanded-3D-platformer/preview.png differ diff --git a/examples/starting-expanded-3D-platformer/starting-expanded-3D-platformer.json b/examples/starting-expanded-3D-platformer/starting-expanded-3D-platformer.json new file mode 100644 index 000000000..9a72a2bb8 --- /dev/null +++ b/examples/starting-expanded-3D-platformer/starting-expanded-3D-platformer.json @@ -0,0 +1,37876 @@ +{ + "firstLayout": "", + "gdVersion": { + "build": 283, + "major": 5, + "minor": 6, + "revision": 0 + }, + "properties": { + "adaptGameResolutionAtRuntime": true, + "antialiasingMode": "MSAA", + "antialisingEnabledOnMobile": false, + "folderProject": false, + "orientation": "landscape", + "packageName": "com.example.gamename", + "pixelsRounding": false, + "projectUuid": "8bf81679-3c71-5e64-be81-93f101df3053", + "scaleMode": "linear", + "sizeOnStartupMode": "", + "templateSlug": "", + "version": "1.0.0", + "name": "3D Platformer Expanded", + "description": "A 3D platformer in a valley made with a 3D terrain: paths around hills, platforms on their tops, coins to collect and a pond.", + "author": "", + "windowWidth": 1280, + "windowHeight": 720, + "latestCompilationDirectory": "", + "maxFPS": 60, + "minFPS": 20, + "verticalSync": false, + "platformSpecificAssets": { + "android-icon-144": "", + "android-icon-192": "", + "android-icon-36": "", + "android-icon-48": "", + "android-icon-72": "", + "android-icon-96": "", + "android-windowSplashScreenAnimatedIcon": "", + "desktop-icon-512": "", + "ios-icon-100": "", + "ios-icon-1024": "", + "ios-icon-114": "", + "ios-icon-120": "", + "ios-icon-144": "", + "ios-icon-152": "", + "ios-icon-167": "", + "ios-icon-180": "", + "ios-icon-20": "", + "ios-icon-29": "", + "ios-icon-40": "", + "ios-icon-50": "", + "ios-icon-57": "", + "ios-icon-58": "", + "ios-icon-60": "", + "ios-icon-72": "", + "ios-icon-76": "", + "ios-icon-80": "", + "ios-icon-87": "" + }, + "loadingScreen": { + "backgroundColor": 0, + "backgroundFadeInDuration": 0.2, + "backgroundImageResourceName": "", + "gdevelopLogoStyle": "light", + "logoAndProgressFadeInDuration": 0.2, + "logoAndProgressLogoFadeInDelay": 0, + "minDuration": 1.5, + "progressBarColor": 16777215, + "progressBarHeight": 20, + "progressBarMaxWidth": 200, + "progressBarMinWidth": 40, + "progressBarWidthPercent": 30, + "showGDevelopSplash": true, + "showProgressBar": true + }, + "watermark": { + "placement": "bottom-left", + "showWatermark": true + }, + "authorIds": [], + "authorUsernames": [], + "categories": [], + "playableDevices": [ + "mobile" + ], + "extensionProperties": [], + "platforms": [ + { + "name": "GDevelop JS platform" + } + ], + "currentPlatform": "GDevelop JS platform" + }, + "resources": { + "resources": [ + { + "file": "assets/StartingCoin.glb", + "kind": "model3D", + "metadata": "", + "name": "assets\\StartingCoin.glb", + "userAdded": false + }, + { + "file": "assets/StartingCapsule.glb", + "kind": "model3D", + "metadata": "", + "name": "assets\\StartingCapsule.glb", + "userAdded": false + }, + { + "file": "assets/StartingWall.png", + "kind": "image", + "metadata": "", + "name": "assets\\StartingWall.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/StarterPushable.png", + "kind": "image", + "metadata": "", + "name": "assets\\StarterPushable.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Flat dark joystick border.png", + "kind": "image", + "metadata": "", + "name": "Flat dark joystick border.png", + "smoothed": true, + "userAdded": false, + "origin": { + "identifier": "https://asset-resources.gdevelop.io/public-resources/Multitouch joysticks/1db606cabd7372d1494ba5934bc25bcdd72f5a213c4a27509be57c3f4d5aecca_Flat dark joystick border.png", + "name": "Flat dark joystick border.png" + } + }, + { + "file": "assets/Flat dark joystick thumb.png", + "kind": "image", + "metadata": "", + "name": "Flat dark joystick thumb.png", + "smoothed": true, + "userAdded": false, + "origin": { + "identifier": "https://asset-resources.gdevelop.io/public-resources/Multitouch joysticks/10167ade22c4a6b48324e6c1d1bd6dc74179d7bed0775890903f418b4a05c8a1_Flat dark joystick thumb.png", + "name": "Flat dark joystick thumb.png" + } + }, + { + "file": "assets/Top arrow button.png", + "kind": "image", + "metadata": "", + "name": "Top arrow button.png", + "smoothed": true, + "userAdded": false, + "origin": { + "identifier": "https://asset-resources.gdevelop.io/public-resources/On-Screen Controls/Sprites/Flat Dark/e3943e1b23ceb90f00fc5e7c0481c5147b983fdc5397fb3690e102e53ce72d4f_Top arrow button.png", + "name": "Top arrow button.png" + } + }, + { + "file": "assets/StarterToggle_Keyboard.png", + "kind": "image", + "metadata": "", + "name": "assets\\StarterToggle_Keyboard.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/StarterToggle_Touch.png", + "kind": "image", + "metadata": "", + "name": "assets\\StarterToggle_Touch.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Front.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Front.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Back.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Back.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Top.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Top.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Bottom.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Bottom.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Left.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Left.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Right.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Right.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/CoinPickUp.wav", + "kind": "audio", + "metadata": "", + "name": "assets/CoinPickUp.wav", + "preloadAsMusic": false, + "preloadAsSound": true, + "preloadInCache": false, + "userAdded": false + }, + { + "file": "assets/TerrainGrass.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainGrass.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainGrassNormal.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainGrassNormal.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainDirt.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainDirt.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainDirtNormal.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainDirtNormal.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainRock.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainRock.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainRockNormal.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainRockNormal.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainSand.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainSand.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainSandNormal.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainSandNormal.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/WaterNormalMap.jpg", + "kind": "image", + "metadata": "", + "name": "assets/WaterNormalMap.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/flag.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/flag.glb", + "userAdded": true + }, + { + "file": "assets/sign.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/sign.glb", + "userAdded": true + }, + { + "file": "assets/tree.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/tree.glb", + "userAdded": true + }, + { + "file": "assets/treePine.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/treePine.glb", + "userAdded": true + }, + { + "file": "assets/rocks.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/rocks.glb", + "userAdded": true + }, + { + "file": "assets/stones.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/stones.glb", + "userAdded": true + }, + { + "file": "assets/flowers.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/flowers.glb", + "userAdded": true + }, + { + "file": "assets/mushrooms.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/mushrooms.glb", + "userAdded": true + } + ] + }, + "objects": [], + "objectsFolderStructure": { + "folderName": "__ROOT" + }, + "objectsGroups": [], + "variables": [], + "layouts": [ + { + "b": 255, + "disableInputWhenNotFocused": true, + "mangledName": "Game_32Scene", + "name": "Game Scene", + "r": 255, + "standardSortMethod": true, + "stopSoundsOnStartup": true, + "title": "", + "v": 255, + "uiSettings": { + "grid": false, + "gridType": "rectangular", + "gridWidth": 64, + "gridHeight": 64, + "gridDepth": 32, + "gridOffsetX": 0, + "gridOffsetY": 0, + "gridOffsetZ": 0, + "gridColor": 10401023, + "gridAlpha": 0.8, + "snap": false, + "zoomFactor": 0.5117784035210373, + "windowMask": false, + "selectedLayer": "", + "gameEditorMode": "embedded-game" + }, + "objectsGroups": [], + "variables": [ + { + "name": "ControlKind", + "persistentUuid": "ddbd5abc-fb86-427c-b240-e8b653b93b3c", + "type": "string", + "value": "Keyboard" + }, + { + "name": "IsCameraLocked", + "persistentUuid": "690e89c7-0fce-446f-9764-7d695c2d8f98", + "type": "boolean", + "value": false + }, + { + "name": "CoinPitch", + "persistentUuid": "61f41e22-a769-42d4-8287-dda386904970", + "type": "number", + "value": 1 + } + ], + "instances": [ + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "MobileControls", + "locked": true, + "name": "Joystick", + "persistentUuid": "757f9f40-246a-49c8-a4d3-fe0022b826a7", + "width": 0, + "x": 170, + "y": 576, + "zOrder": 7, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 0, + "height": 104, + "keepRatio": true, + "layer": "MobileControls", + "locked": true, + "name": "JumpButton", + "persistentUuid": "c641f19e-29c5-4424-be1c-656b5505f1ac", + "width": 104, + "x": 1104, + "y": 576, + "zOrder": 8, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "MobileControls", + "locked": true, + "name": "ControlsToggle", + "persistentUuid": "fadfe7f0-b617-48f2-9751-87f17208ed7c", + "width": 0, + "x": 32, + "y": 32, + "zOrder": 12, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 6144, + "layer": "", + "locked": false, + "name": "Terrain", + "persistentUuid": "5fb6e6f1-9993-5b27-9711-2b25452c1c80", + "x": -3072, + "y": -3072, + "z": -120, + "zOrder": 0, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 1200 + }, + { + "angle": 0, + "customSize": true, + "height": 200, + "width": 6544, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "5e612ad2-cbba-5640-9e81-46c6c623db55", + "x": -3272, + "y": -3272, + "z": -500, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 4000 + }, + { + "angle": 0, + "customSize": true, + "height": 200, + "width": 6544, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "e74c7dd4-3226-5d07-a09b-5db6ab092c7a", + "x": -3272, + "y": 3072, + "z": -500, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 4000 + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 200, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "2fd35141-700b-53e3-94a6-654f04206223", + "x": -3272, + "y": -3072, + "z": -500, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 4000 + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 200, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "399b3d76-cbbf-50dc-b5dc-720eb8ac3018", + "x": 3072, + "y": -3072, + "z": -500, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 4000 + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 6144, + "layer": "", + "locked": false, + "name": "Grass", + "persistentUuid": "596224c7-5ec5-55e5-9794-38a6afe64163", + "x": -3072, + "y": -3072, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 40 + }, + { + "angle": 0, + "customSize": true, + "height": 1600, + "width": 1900, + "layer": "", + "locked": false, + "name": "Water", + "persistentUuid": "c93970b1-6f3d-550d-bad3-d5f544973fe6", + "x": -900, + "y": -800, + "z": -42, + "zOrder": 2, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 1 + }, + { + "angle": -90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Player", + "persistentUuid": "ea6e8202-9106-5fa0-9e26-73b84e3bd0d1", + "x": 0, + "y": 1850, + "z": 0, + "zOrder": 8, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "31732b09-3b1b-5bb0-aa2f-59907c91044b", + "x": 0, + "y": 1600, + "z": 30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "f396edf1-1473-58e8-b408-16b0baaf91b6", + "x": 0, + "y": 1480, + "z": 30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "6c894235-c1bf-5e3a-b250-bd8307e32d03", + "x": 0, + "y": 1360, + "z": 30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 128, + "width": 128, + "layer": "", + "locked": false, + "name": "PushableBox", + "persistentUuid": "04afe583-ff8e-54cb-9b77-56828c1975fc", + "x": 240, + "y": 1500, + "z": 1, + "zOrder": 3, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 64 + }, + { + "angle": -90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Sign", + "persistentUuid": "ed294c29-3172-5bce-8f30-374174b41002", + "x": -140, + "y": 1500, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 192, + "width": 192, + "layer": "", + "locked": false, + "name": "Obstacle", + "persistentUuid": "075aa4f7-58f4-5758-8350-59a62f4d6d07", + "x": -1761.1, + "y": -1608.9, + "z": 295.4, + "zOrder": 3, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 84 + }, + { + "angle": 0, + "customSize": true, + "height": 192, + "width": 192, + "layer": "", + "locked": false, + "name": "Obstacle", + "persistentUuid": "a926ef6d-1766-5b36-b29d-419b24adf49f", + "x": -1596, + "y": -1496, + "z": 295.4, + "zOrder": 3, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 164 + }, + { + "angle": 0, + "customSize": true, + "height": 256, + "width": 256, + "layer": "", + "locked": false, + "name": "Obstacle", + "persistentUuid": "5ffd31cb-3b45-5906-ba40-8b340d0c0b34", + "x": -1462.9, + "y": -1415.1, + "z": 295.4, + "zOrder": 3, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 244 + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "741651f8-6078-588e-b098-11a6962d6823", + "x": -1394.9, + "y": -1287.1, + "z": 569.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "b7a9461e-ad0a-547f-a0d7-c87644d471c1", + "x": -1334.9, + "y": -1287.1, + "z": 569.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "50a1b557-4db6-597d-b48f-4d0aae0bf0a3", + "x": -1274.9, + "y": -1287.1, + "z": 569.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Flag", + "persistentUuid": "ab60cfc5-f2a9-5df7-aba7-09448d8b9848", + "x": -1244.9, + "y": -1197.1, + "z": 539.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 192, + "width": 192, + "layer": "", + "locked": false, + "name": "Obstacle", + "persistentUuid": "57f0451e-21ef-5231-af5f-247b30d59b8e", + "x": 1416.9, + "y": -1561.1, + "z": 235.3, + "zOrder": 3, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 84 + }, + { + "angle": 0, + "customSize": true, + "height": 192, + "width": 192, + "layer": "", + "locked": false, + "name": "Obstacle", + "persistentUuid": "cd347915-301e-5b97-8146-85457993c895", + "x": 1304, + "y": -1396, + "z": 235.3, + "zOrder": 3, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 164 + }, + { + "angle": 0, + "customSize": true, + "height": 256, + "width": 256, + "layer": "", + "locked": false, + "name": "Obstacle", + "persistentUuid": "dd8271f6-b72d-52ed-ac58-97be696fea0f", + "x": 1159.1, + "y": -1262.9, + "z": 235.3, + "zOrder": 3, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 244 + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "10ff2d7d-9d3e-5c10-aa37-fb73a7bc5040", + "x": 1227.1, + "y": -1134.9, + "z": 509.3, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "66187681-646e-5935-a31c-7cd03521e827", + "x": 1287.1, + "y": -1134.9, + "z": 509.3, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "a9be8c60-94e1-5aac-84b3-e01c1acebb15", + "x": 1347.1, + "y": -1134.9, + "z": 509.3, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Flag", + "persistentUuid": "c8ca6cbc-1cf4-5c04-8534-5c55e86afcd5", + "x": 1377.1, + "y": -1044.9, + "z": 479.3, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 192, + "width": 192, + "layer": "", + "locked": false, + "name": "Obstacle", + "persistentUuid": "d1903cb3-7c52-5ccb-8e25-cac79ebbf775", + "x": 1769.1, + "y": 916.9, + "z": 195.4, + "zOrder": 3, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 84 + }, + { + "angle": 0, + "customSize": true, + "height": 192, + "width": 192, + "layer": "", + "locked": false, + "name": "Obstacle", + "persistentUuid": "62804ceb-9bdd-5962-bcc5-864488d6307c", + "x": 1604, + "y": 804, + "z": 195.4, + "zOrder": 3, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 164 + }, + { + "angle": 0, + "customSize": true, + "height": 256, + "width": 256, + "layer": "", + "locked": false, + "name": "Obstacle", + "persistentUuid": "35ba73b0-591a-5bed-bf97-99dbc9c07d17", + "x": 1406.9, + "y": 659.1, + "z": 195.4, + "zOrder": 3, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 244 + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "dbaafb90-2e30-5aad-b418-b9efc4ac07b1", + "x": 1474.9, + "y": 787.1, + "z": 469.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "de1470fd-49e6-5360-99d6-d7b47f81aa6b", + "x": 1534.9, + "y": 787.1, + "z": 469.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "a0e57fa5-41ec-5931-a4c4-2487d381ca85", + "x": 1594.9, + "y": 787.1, + "z": 469.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Flag", + "persistentUuid": "ffe3d0c2-191d-5985-9473-8f1f4b99969a", + "x": 1624.9, + "y": 877.1, + "z": 439.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 192, + "width": 192, + "layer": "", + "locked": false, + "name": "Obstacle", + "persistentUuid": "1cebac5e-ee33-5639-9e35-a54163b5d1ac", + "x": -1908.9, + "y": 969.1, + "z": 145.4, + "zOrder": 3, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 84 + }, + { + "angle": 0, + "customSize": true, + "height": 192, + "width": 192, + "layer": "", + "locked": false, + "name": "Obstacle", + "persistentUuid": "6ffad940-0e84-59ef-9eea-0b1fad3c986e", + "x": -1796, + "y": 804, + "z": 145.4, + "zOrder": 3, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 164 + }, + { + "angle": 0, + "customSize": true, + "height": 256, + "width": 256, + "layer": "", + "locked": false, + "name": "Obstacle", + "persistentUuid": "164bd9fc-a9d7-543e-999f-26b7bb19360a", + "x": -1715.1, + "y": 606.9, + "z": 145.4, + "zOrder": 3, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 244 + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "e90a30d2-48d8-501d-9c44-627d67ff6aec", + "x": -1647.1, + "y": 734.9, + "z": 419.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "12ce68ac-a792-53c7-a8ae-cc7702cda3b5", + "x": -1587.1, + "y": 734.9, + "z": 419.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "cfbe7dc8-8cf3-5a5a-8e49-abbcf2b507f1", + "x": -1527.1, + "y": 734.9, + "z": 419.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Flag", + "persistentUuid": "1485c000-2094-5dc7-8a1f-e892b2ac7a76", + "x": -1497.1, + "y": 824.9, + "z": 389.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "00db794e-9c27-5833-8a05-8d4600a296d1", + "x": -311, + "y": 1334, + "z": 30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "3c803255-317e-5b13-81c7-5b359b803e1b", + "x": -611, + "y": 960, + "z": 33.9, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "b69ca27d-69c6-5eef-ae17-1c08301e9436", + "x": -901, + "y": 577, + "z": 30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "5444e909-8e7e-5691-b1c0-d2e65aedd22a", + "x": -1105, + "y": 144, + "z": 30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "3110a37f-31b2-5966-9cb5-4e2f98d16ca6", + "x": -1202, + "y": -324, + "z": 30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "f57e0d66-55be-54cd-92b8-a68f53fe251d", + "x": -1043, + "y": -768, + "z": 30.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "7647a187-bbf0-5534-a52d-e26bf1a0585e", + "x": -899, + "y": -1215, + "z": 65.8, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "425c7f92-c1a2-58ef-b68e-e3741b179f13", + "x": -1018, + "y": -1676, + "z": 99.5, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "5874c973-b63f-5f13-aa3f-5801422086e8", + "x": -1379, + "y": -1972, + "z": 86, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "c92009ff-36ec-55a5-8342-f2294fc410bd", + "x": -1830, + "y": -1920, + "z": 70.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "f173768c-71c3-59bf-aa17-72873aa8adc9", + "x": -2093, + "y": -1525, + "z": 75.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "4b300cd3-996b-58e6-914b-73e35fc435e0", + "x": -1922, + "y": -1107, + "z": 121.3, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "19dd4090-c8db-50ff-8821-eeb988720285", + "x": 401, + "y": 1437, + "z": 30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "7047a5c9-180c-5a34-9ef8-c45e793740b8", + "x": 766, + "y": 1127, + "z": 30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "264574be-0408-5673-936a-ed5e60e853bf", + "x": 1031, + "y": 727, + "z": 30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "e887589e-9a90-5d80-9875-31a1657e9e64", + "x": 1385, + "y": 425, + "z": 36.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "a632c940-e898-5799-ad6d-793ba22c7c48", + "x": 1856, + "y": 483, + "z": 77.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "611346f4-668b-5e57-ba0a-c0705146fb2e", + "x": 2206, + "y": 775, + "z": 49.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "837a31e5-465a-53e1-9e61-5b4188a6d9a6", + "x": 2113, + "y": 1222, + "z": 47.5, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "46dc4dd7-6e97-511d-a1b7-0c66623de5cc", + "x": 1114, + "y": 7, + "z": 30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "028e3ee6-0b4c-5f3a-8b56-86a04f8a9e1e", + "x": 951, + "y": -444, + "z": 30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "6c49c727-475a-5796-b8df-a952b404f25a", + "x": 813, + "y": -903, + "z": 30.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "14ee8d94-f504-5c5f-9625-8d6b7fa00c74", + "x": 854, + "y": -1376, + "z": 57.8, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "3fd2ac6a-aadc-5edc-aefd-3e125cd0173b", + "x": 1086, + "y": -1790, + "z": 45.9, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "db532bc2-0c45-5957-83de-fdcb19287021", + "x": 1547, + "y": -1785, + "z": 75.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "da4c4dec-2b31-55e8-8c7e-19fc6650dac2", + "x": 1815, + "y": -1423, + "z": 111.5, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "b2b7e331-b418-5cff-a74b-121449f27eaa", + "x": -883, + "y": 1390, + "z": 30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "f0b2f58c-8208-5bdb-b56b-91a87c10a172", + "x": -1330, + "y": 1547, + "z": 30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "b4de77bc-e7e4-5771-8baa-32a5a7a9b59d", + "x": -1802, + "y": 1499, + "z": 30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Coin", + "persistentUuid": "ea3662d7-075c-5422-80de-6428b1ae9a51", + "x": -2080, + "y": 1119, + "z": 55.8, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 6144, + "layer": "", + "locked": true, + "name": "Trees", + "persistentUuid": "8172cfc1-3a5b-5b83-adde-9ad733eae4e8", + "x": -3072, + "y": -3072, + "z": -120, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 6144, + "layer": "", + "locked": true, + "name": "PineTrees", + "persistentUuid": "35f368c7-07b8-55bd-99c8-35848ac71ad9", + "x": -3072, + "y": -3072, + "z": -120, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 6144, + "layer": "", + "locked": true, + "name": "Rocks", + "persistentUuid": "8a8605ea-8574-5d41-b159-604101e52cb9", + "x": -3072, + "y": -3072, + "z": -120, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 6144, + "layer": "", + "locked": true, + "name": "Stones", + "persistentUuid": "28c88e13-4af7-5c1f-ba7b-29c7001c9af3", + "x": -3072, + "y": -3072, + "z": -120, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 6144, + "layer": "", + "locked": true, + "name": "Flowers", + "persistentUuid": "e488b16b-9280-5dce-8f79-d2362b63c0e0", + "x": -3072, + "y": -3072, + "z": -120, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 6144, + "layer": "", + "locked": true, + "name": "Mushrooms", + "persistentUuid": "d6c1e0bc-9f04-53c7-8691-a5ff284926fb", + "x": -3072, + "y": -3072, + "z": -120, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + } + ], + "objects": [ + { + "assetStoreId": "bb9aa52170c0941b6a504a192b7d9e495e174642b99a19bd548939c37c27209e", + "name": "Player", + "persistentUuid": "cb2504cd-b5ed-4425-8c48-7ee4dfee1f06", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "isFolded": true, + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Dynamic", + "bullet": false, + "fixedRotation": false, + "shape": "Capsule", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0 + }, + { + "isFolded": true, + "name": "PhysicsCharacter3D", + "type": "Physics3D::PhysicsCharacter3D", + "physics3D": "Physics3D", + "jumpHeight": 100, + "jumpSustainTime": 0.2, + "gravity": 1000, + "fallingSpeedMax": 500, + "forwardAcceleration": 650, + "forwardDeceleration": 1600, + "forwardSpeedMax": 300, + "sidewaysAcceleration": 800, + "sidewaysDeceleration": 800, + "sidewaysSpeedMax": 400, + "slopeMaxAngle": 50, + "stairHeightMax": 20, + "shouldBindObjectAndForwardAngle": true, + "canBePushed": true + }, + { + "isFolded": true, + "name": "Platformer3DKeyboardMapper", + "type": "PhysicsCharacter3DKeyMapper::Platformer3DKeyboardMapper", + "PhysicsCharacter3D": "PhysicsCharacter3D", + "Jump": "Space", + "Up": "w", + "Left": "a", + "Down": "s", + "Right": "d", + "CameraAngle": 0, + "LockedCameraAngle": false + }, + { + "isFolded": true, + "name": "Platformer3DMultitouchMapper", + "type": "SpriteMultitouchJoystick::Platformer3DMultitouchMapper", + "PhysicsCharacter3D": "PhysicsCharacter3D", + "ControllerIdentifier": 1, + "JoystickIdentifier": "Primary", + "JumpButton": "A" + }, + { + "isFolded": true, + "name": "ThirdPersonCamera", + "type": "ThirdPersonCamera::ThirdPersonCamera", + "Object3D": "Object3D", + "RotationHalfwayDuration": 1.5, + "TranslationZHalfwayDuration": 0.2, + "Distance": 600, + "OffsetX": 0, + "OffsetY": 0, + "OffsetZ": 20, + "RotationAngleOffset": 0, + "ElevationAngleOffset": 30, + "FollowFreeAreaZMax": 0, + "FollowFreeAreaZMin": 0, + "RotationLogSpeed": 2.0247e-320, + "TranslationZLogSpeed": 2.0247e-320, + "IsCalledManually": false, + "CameraZ": 0, + "HasJustBeenCreated": true, + "IsRotatingWithObject": true, + "TargetedRotationAngle": 0 + } + ], + "content": { + "centerLocation": "ModelOrigin", + "crossfadeDuration": 0, + "depth": 60, + "height": 30, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets\\StartingCapsule.glb", + "originLocation": "ModelOrigin", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 30, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "Obstacle", + "persistentUuid": "60d2470c-9efe-410e-8148-9c5cc5cdf8bd", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0 + } + ], + "content": { + "width": 128, + "height": 128, + "depth": 128, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "frontFaceResourceName": "assets\\StartingWall.png", + "backFaceResourceName": "", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "assets\\StartingWall.png", + "rightFaceResourceName": "assets\\StartingWall.png", + "topFaceResourceName": "assets\\StartingWall.png", + "bottomFaceResourceName": "assets\\StartingWall.png", + "frontFaceVisible": true, + "backFaceVisible": false, + "leftFaceVisible": true, + "rightFaceVisible": true, + "topFaceVisible": true, + "bottomFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceRepeat": false, + "leftFaceResourceRepeat": true, + "rightFaceResourceRepeat": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceRepeat": true, + "materialType": "StandardWithoutMetalness", + "isCastingShadow": true, + "isReceivingShadow": true, + "tileScale": 0.2 + } + }, + { + "assetStoreId": "0d1fe5c3dd1defdf38ab89966ea00eeeb4a731f11694c2b9faa524674d094dc2", + "name": "Coin", + "persistentUuid": "35047ac7-6ec9-4971-bc6a-5580d7fbf8ed", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "centerLocation": "ObjectCenter", + "crossfadeDuration": 0, + "depth": 31, + "height": 31, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets\\StartingCoin.glb", + "originLocation": "ObjectCenter", + "rotationX": 0, + "rotationY": 90, + "rotationZ": 0, + "width": 31, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "PushableBox", + "persistentUuid": "bc743b3f-baea-4503-8815-27163108a35c", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Dynamic", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "density": 10, + "friction": 2.75, + "restitution": 0.1, + "linearDamping": 4, + "angularDamping": 5, + "gravityScale": 1, + "layers": 17, + "masks": 17, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0 + } + ], + "content": { + "width": 128, + "height": 128, + "depth": 128, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "frontFaceResourceName": "assets\\StarterPushable.png", + "backFaceResourceName": "", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "assets\\StarterPushable.png", + "rightFaceResourceName": "assets\\StarterPushable.png", + "topFaceResourceName": "assets\\StarterPushable.png", + "bottomFaceResourceName": "assets\\StarterPushable.png", + "frontFaceVisible": true, + "backFaceVisible": false, + "leftFaceVisible": true, + "rightFaceVisible": true, + "topFaceVisible": true, + "bottomFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceRepeat": false, + "leftFaceResourceRepeat": true, + "rightFaceResourceRepeat": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceRepeat": true, + "materialType": "StandardWithoutMetalness", + "isCastingShadow": true, + "isReceivingShadow": true, + "tileScale": 0.2 + } + }, + { + "assetStoreId": "e71bd69f896d6c7531b48c65ceb5da25071d4fbdeb518aeceecba8d21f34ed8d", + "name": "Joystick", + "persistentUuid": "7996d398-2382-4b97-a108-cec3e6152739", + "type": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "variant": "", + "variables": [], + "effects": [], + "behaviors": [], + "content": {}, + "childrenContent": { + "Border": { + "adaptCollisionMaskAutomatically": false, + "updateIfNotVisible": false, + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": false, + "image": "Flat dark joystick border.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 0, + "y": 0 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [] + } + ] + } + ] + } + ] + }, + "Thumb": { + "adaptCollisionMaskAutomatically": false, + "updateIfNotVisible": false, + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": false, + "image": "Flat dark joystick thumb.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 0, + "y": 0 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [] + } + ] + } + ] + } + ] + } + } + }, + { + "adaptCollisionMaskAutomatically": true, + "assetStoreId": "9c727020616afdd6ba786b8af206a90481f07db0ca175ed6a4cc5b7e01c66d06", + "name": "JumpButton", + "persistentUuid": "e29ad2f3-ceef-4d9c-8de7-903abac2b4b0", + "type": "Sprite", + "updateIfNotVisible": false, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "ButtonFSM", + "type": "ButtonStates::ButtonFSM", + "ShouldCheckHovering": true, + "State": "Idle", + "TouchId": 0, + "TouchIsInside": false, + "MouseIsInside": false, + "Index": 0 + }, + { + "name": "ButtonScaleTween", + "type": "ButtonStates::ButtonScaleTween", + "Scale": "Scale", + "ButtonFSM": "ButtonFSM", + "Tween": "Tween", + "IdleScale": 1.3, + "FocusedScale": 1.25, + "FadeInDuration": 0.02, + "FadeOutDuration": 0.05, + "PressedScale": 1.2, + "FadeInEasing": "easeOutQuad", + "FadeOutEasing": "easeInQuad", + "PreviousState": "Idle" + }, + { + "name": "MultitouchButton", + "type": "SpriteMultitouchJoystick::MultitouchButton", + "ControllerIdentifier": 1, + "ButtonIdentifier": "A", + "TouchId": 0, + "TouchIndex": 2.0247e-320, + "IsReleased": false + }, + { + "name": "Tween", + "type": "Tween::TweenBehavior" + } + ], + "animations": [ + { + "name": "", + "useMultipleDirections": false, + "directions": [ + { + "looping": true, + "timeBetweenFrames": 0.025, + "sprites": [ + { + "hasCustomCollisionMask": true, + "image": "Top arrow button.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 40, + "y": 40 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [ + [ + { + "x": 0, + "y": 0 + }, + { + "x": 80, + "y": 0 + }, + { + "x": 80, + "y": 80 + }, + { + "x": 0, + "y": 80 + } + ] + ] + } + ] + } + ] + } + ] + }, + { + "assetStoreId": "", + "name": "ControlsToggle", + "persistentUuid": "92ea5932-2d5d-4f6a-a72f-4b399578714d", + "type": "SpriteToggleSwitch::SpriteToggleSwitch", + "variant": "Controls toggle", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "IsChecked": false + } + }, + { + "assetStoreId": "", + "name": "Terrain", + "type": "Terrain3D::Terrain3D", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "Relief": "Flat", + "Seed": 1, + "Resolution": "256", + "TextureSize": 300, + "CliffLayer": "3", + "CliffAngle": 40, + "NormalMapStrength": 1, + "HeightBlending": 0.8, + "TextureVariation": 0.5, + "Edits": "[{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.1},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.1},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.1},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.1},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.1},{\"tool\":\"raise\",\"x\":0,\"y\":0,\"toX\":1,\"toY\":0,\"radius\":0.1139,\"height\":0.4333},{\"tool\":\"raise\",\"x\":0,\"y\":1,\"toX\":1,\"toY\":1,\"radius\":0.1139,\"height\":0.4333},{\"tool\":\"raise\",\"x\":0,\"y\":0,\"toX\":0,\"toY\":1,\"radius\":0.1139,\"height\":0.4333},{\"tool\":\"raise\",\"x\":1,\"y\":0,\"toX\":1,\"toY\":1,\"radius\":0.1139,\"height\":0.4333},{\"tool\":\"raise\",\"x\":0.3047,\"y\":0,\"radius\":0.0977,\"height\":0.3167},{\"tool\":\"raise\",\"x\":0.7116,\"y\":0,\"radius\":0.1139,\"height\":0.35},{\"tool\":\"raise\",\"x\":1,\"y\":0.5488,\"radius\":0.0977,\"height\":0.3167},{\"tool\":\"raise\",\"x\":1,\"y\":0.207,\"radius\":0.0814,\"height\":0.25},{\"tool\":\"raise\",\"x\":0.5651,\"y\":1,\"radius\":0.1058,\"height\":0.3},{\"tool\":\"raise\",\"x\":0,\"y\":0.4023,\"radius\":0.0977,\"height\":0.3333},{\"tool\":\"raise\",\"x\":0,\"y\":0.7767,\"radius\":0.0814,\"height\":0.25},{\"tool\":\"raise\",\"x\":0.2559,\"y\":0.2721,\"radius\":0.1302,\"height\":0.25},{\"tool\":\"raise\",\"x\":0.7279,\"y\":0.2884,\"radius\":0.1139,\"height\":0.2},{\"tool\":\"raise\",\"x\":0.7767,\"y\":0.6465,\"radius\":0.1058,\"height\":0.1667},{\"tool\":\"raise\",\"x\":0.2233,\"y\":0.6465,\"radius\":0.0977,\"height\":0.125},{\"tool\":\"raise\",\"x\":0.5488,\"y\":0.1745,\"radius\":0.0814,\"height\":0.075},{\"tool\":\"raise\",\"x\":0.4512,\"y\":0.6465,\"radius\":0.0651,\"height\":0.0333},{\"tool\":\"raise\",\"x\":0.8743,\"y\":0.4349,\"radius\":0.0732,\"height\":0.0917},{\"tool\":\"raise\",\"x\":0.1257,\"y\":0.5326,\"radius\":0.0651,\"height\":0.0667},{\"tool\":\"raise\",\"x\":0.6465,\"y\":0.8581,\"radius\":0.0732,\"height\":0.05},{\"tool\":\"smooth\",\"x\":0.5,\"y\":0.5,\"radius\":0.4232,\"strength\":0.25},{\"tool\":\"flatten\",\"x\":0.2559,\"y\":0.2721,\"radius\":0.0488,\"z\":0.3495},{\"tool\":\"flatten\",\"x\":0.2559,\"y\":0.2721,\"radius\":0.0488,\"z\":0.3495},{\"tool\":\"flatten\",\"x\":0.2559,\"y\":0.2721,\"radius\":0.0488,\"z\":0.3495},{\"tool\":\"flatten\",\"x\":0.7279,\"y\":0.2884,\"radius\":0.0488,\"z\":0.2994},{\"tool\":\"flatten\",\"x\":0.7279,\"y\":0.2884,\"radius\":0.0488,\"z\":0.2994},{\"tool\":\"flatten\",\"x\":0.7279,\"y\":0.2884,\"radius\":0.0488,\"z\":0.2994},{\"tool\":\"flatten\",\"x\":0.7767,\"y\":0.6465,\"radius\":0.0488,\"z\":0.2662},{\"tool\":\"flatten\",\"x\":0.7767,\"y\":0.6465,\"radius\":0.0488,\"z\":0.2662},{\"tool\":\"flatten\",\"x\":0.7767,\"y\":0.6465,\"radius\":0.0488,\"z\":0.2662},{\"tool\":\"flatten\",\"x\":0.2233,\"y\":0.6465,\"radius\":0.0488,\"z\":0.2245},{\"tool\":\"flatten\",\"x\":0.2233,\"y\":0.6465,\"radius\":0.0488,\"z\":0.2245},{\"tool\":\"flatten\",\"x\":0.2233,\"y\":0.6465,\"radius\":0.0488,\"z\":0.2245},{\"tool\":\"flatten\",\"x\":0.5488,\"y\":0.4674,\"toX\":0.4674,\"toY\":0.5326,\"radius\":0.0846,\"z\":0.0417},{\"tool\":\"flatten\",\"x\":0.5488,\"y\":0.4674,\"toX\":0.4674,\"toY\":0.5326,\"radius\":0.0846,\"z\":0.0417},{\"tool\":\"flatten\",\"x\":0.5488,\"y\":0.4674,\"toX\":0.4674,\"toY\":0.5326,\"radius\":0.0846,\"z\":0.0417},{\"tool\":\"flatten\",\"x\":0.5488,\"y\":0.4674,\"toX\":0.4674,\"toY\":0.5326,\"radius\":0.0846,\"z\":0.0417},{\"tool\":\"smooth\",\"x\":0.5081,\"y\":0.5,\"radius\":0.1465,\"strength\":0.3},{\"tool\":\"paint\",\"x\":0.5488,\"y\":0.4674,\"toX\":0.4674,\"toY\":0.5326,\"radius\":0.1042,\"layer\":4,\"strength\":1},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.7767,\"radius\":0.0732,\"z\":0.1},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.7767,\"radius\":0.0732,\"z\":0.1},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.7767,\"radius\":0.0732,\"z\":0.1},{\"tool\":\"paint\",\"x\":0.5,\"y\":0.7767,\"toX\":0.4661,\"toY\":0.7371,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.4661,\"y\":0.7371,\"toX\":0.4328,\"toY\":0.6971,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.4328,\"y\":0.6971,\"toX\":0.4006,\"toY\":0.6563,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.4006,\"y\":0.6563,\"toX\":0.3683,\"toY\":0.6154,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3683,\"y\":0.6154,\"toX\":0.3403,\"toY\":0.5715,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3403,\"y\":0.5715,\"toX\":0.3201,\"toY\":0.5234,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3201,\"y\":0.5234,\"toX\":0.307,\"toY\":0.4731,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.307,\"y\":0.4731,\"toX\":0.307,\"toY\":0.4214,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.307,\"y\":0.4214,\"toX\":0.3302,\"toY\":0.375,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3302,\"y\":0.375,\"toX\":0.3525,\"toY\":0.3283,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3525,\"y\":0.3283,\"toX\":0.3504,\"toY\":0.2765,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3504,\"y\":0.2765,\"toX\":0.3343,\"toY\":0.2272,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3343,\"y\":0.2272,\"toX\":0.2992,\"toY\":0.1898,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.2992,\"y\":0.1898,\"toX\":0.2502,\"toY\":0.173,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.2502,\"y\":0.173,\"toX\":0.2021,\"toY\":0.1875,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.2021,\"y\":0.1875,\"toX\":0.1701,\"toY\":0.2282,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1701,\"y\":0.2282,\"toX\":0.159,\"toY\":0.2775,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.159,\"y\":0.2775,\"toX\":0.1872,\"toY\":0.3198,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1872,\"y\":0.3198,\"toX\":0.2306,\"toY\":0.2957,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.2306,\"y\":0.2957,\"toX\":0.2559,\"toY\":0.2721,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5,\"y\":0.7767,\"toX\":0.5436,\"toY\":0.7484,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5436,\"y\":0.7484,\"toX\":0.5864,\"toY\":0.7186,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5864,\"y\":0.7186,\"toX\":0.6247,\"toY\":0.6834,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.6247,\"y\":0.6834,\"toX\":0.6538,\"toY\":0.6403,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.6538,\"y\":0.6403,\"toX\":0.6829,\"toY\":0.5972,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.6829,\"y\":0.5972,\"toX\":0.7254,\"toY\":0.5692,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.7254,\"y\":0.5692,\"toX\":0.777,\"toY\":0.5716,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.777,\"y\":0.5716,\"toX\":0.8262,\"toY\":0.5885,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8262,\"y\":0.5885,\"toX\":0.859,\"toY\":0.6261,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.859,\"y\":0.6261,\"toX\":0.8584,\"toY\":0.6777,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8584,\"y\":0.6777,\"toX\":0.8215,\"toY\":0.7116,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8215,\"y\":0.7116,\"toX\":0.7889,\"toY\":0.6784,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.7889,\"y\":0.6784,\"toX\":0.7767,\"toY\":0.6465,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.7116,\"y\":0.5732,\"toX\":0.6912,\"toY\":0.5252,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.6912,\"y\":0.5252,\"toX\":0.6719,\"toY\":0.4769,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.6719,\"y\":0.4769,\"toX\":0.6548,\"toY\":0.4277,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.6548,\"y\":0.4277,\"toX\":0.6382,\"toY\":0.3784,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.6382,\"y\":0.3784,\"toX\":0.63,\"toY\":0.3271,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.63,\"y\":0.3271,\"toX\":0.639,\"toY\":0.276,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.639,\"y\":0.276,\"toX\":0.6598,\"toY\":0.2285,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.6598,\"y\":0.2285,\"toX\":0.701,\"toY\":0.2005,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.701,\"y\":0.2005,\"toX\":0.7518,\"toY\":0.2095,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.7518,\"y\":0.2095,\"toX\":0.7891,\"toY\":0.2432,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.7891,\"y\":0.2432,\"toX\":0.7961,\"toY\":0.2943,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.7961,\"y\":0.2943,\"toX\":0.7539,\"toY\":0.2988,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.7539,\"y\":0.2988,\"toX\":0.7279,\"toY\":0.2884,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.4186,\"y\":0.679,\"toX\":0.3776,\"toY\":0.7111,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3776,\"y\":0.7111,\"toX\":0.3338,\"toY\":0.7391,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3338,\"y\":0.7391,\"toX\":0.2835,\"toY\":0.7518,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.2835,\"y\":0.7518,\"toX\":0.2316,\"toY\":0.7515,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.2316,\"y\":0.7515,\"toX\":0.187,\"toY\":0.7272,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.187,\"y\":0.7272,\"toX\":0.1615,\"toY\":0.6821,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1615,\"y\":0.6821,\"toX\":0.1859,\"toY\":0.6497,\"radius\":0.0146,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1859,\"y\":0.6497,\"toX\":0.2233,\"toY\":0.6465,\"radius\":0.0146,\"layer\":2,\"strength\":1}]", + "Layer1Color": "255;255;255", + "Layer1Texture": "assets/TerrainGrass.jpg", + "Layer1NormalMap": "assets/TerrainGrassNormal.jpg", + "Layer2Color": "255;255;255", + "Layer2Texture": "assets/TerrainDirt.jpg", + "Layer2NormalMap": "assets/TerrainDirtNormal.jpg", + "Layer3Color": "255;255;255", + "Layer3Texture": "assets/TerrainRock.jpg", + "Layer3NormalMap": "assets/TerrainRockNormal.jpg", + "Layer4Color": "255;255;255", + "Layer4Texture": "assets/TerrainSand.jpg", + "Layer4NormalMap": "assets/TerrainSandNormal.jpg" + } + }, + { + "assetStoreId": "", + "name": "MapLimit", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "width": 100, + "height": 100, + "depth": 100, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "frontFaceResourceName": "", + "backFaceResourceName": "", + "leftFaceResourceName": "", + "rightFaceResourceName": "", + "topFaceResourceName": "", + "bottomFaceResourceName": "", + "backFaceUpThroughWhichAxisRotation": "X", + "frontFaceVisible": false, + "backFaceVisible": false, + "leftFaceVisible": false, + "rightFaceVisible": false, + "topFaceVisible": false, + "bottomFaceVisible": false, + "frontFaceResourceRepeat": true, + "backFaceResourceRepeat": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceRepeat": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceRepeat": false, + "materialType": "StandardWithoutMetalness", + "isCastingShadow": false, + "isReceivingShadow": true + } + }, + { + "assetStoreId": "", + "name": "Grass", + "type": "Terrain3D::TerrainGrass", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Density": 20, + "BendingObject": "Player", + "BladeHeight": 16, + "BladeWidth": 2, + "BaseColor": "52;104;36", + "TipColor": "150;196;82", + "FadeDistance": 2500 + } + }, + { + "assetStoreId": "", + "name": "Water", + "type": "Water3D::Water3D", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Opacity": 120, + "CrestColor": "231;248;255", + "Foam": 0.05, + "NormalMapSize": 600, + "WaveHeight": 2, + "WaveLength": 150, + "WaveSpeed": 1.8, + "NormalMap": "assets/WaterNormalMap.jpg", + "Color": "30;100;120" + } + }, + { + "assetStoreId": "eb078672e0d5c2525a29658d3a9a39207ce5e4987b794b66b7cc372937072703", + "name": "Flag", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 110, + "height": 45.5, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/flag.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": -90, + "width": 10.4, + "animations": [] + } + }, + { + "assetStoreId": "d5b7abf87dea05c9d77abc76972f12a5c879dde198fef3ea95fafc132d4f9bbb", + "name": "Sign", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 70, + "height": 56.4, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/sign.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": -90, + "width": 12.3, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "Trees", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "Model": "assets/tree.glb", + "Size": 220, + "Density": 0.03, + "Seed": 1, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.5,\"y\":0.7767,\"radius\":0.0895},{\"tool\":\"erase\",\"x\":0.5081,\"y\":0.5,\"radius\":0.1383},{\"tool\":\"erase\",\"x\":0.229,\"y\":0.2538,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2559,\"y\":0.2721,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2827,\"y\":0.2905,\"radius\":0.0273},{\"tool\":\"erase\",\"x\":0.7462,\"y\":0.2615,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7279,\"y\":0.2884,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7095,\"y\":0.3153,\"radius\":0.0273},{\"tool\":\"erase\",\"x\":0.8036,\"y\":0.6649,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7767,\"y\":0.6465,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7498,\"y\":0.6281,\"radius\":0.0273},{\"tool\":\"erase\",\"x\":0.2049,\"y\":0.6734,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2233,\"y\":0.6465,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2417,\"y\":0.6196,\"radius\":0.0273}]", + "MaxSlope": 32, + "TrunkCollisions": true, + "TrunkDiameter": 24 + } + }, + { + "assetStoreId": "", + "name": "PineTrees", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "Model": "assets/treePine.glb", + "Size": 240, + "Density": 0.03, + "Seed": 2, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.5,\"y\":0.7767,\"radius\":0.0895},{\"tool\":\"erase\",\"x\":0.5081,\"y\":0.5,\"radius\":0.1383},{\"tool\":\"erase\",\"x\":0.229,\"y\":0.2538,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2559,\"y\":0.2721,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2827,\"y\":0.2905,\"radius\":0.0273},{\"tool\":\"erase\",\"x\":0.7462,\"y\":0.2615,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7279,\"y\":0.2884,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7095,\"y\":0.3153,\"radius\":0.0273},{\"tool\":\"erase\",\"x\":0.8036,\"y\":0.6649,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7767,\"y\":0.6465,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7498,\"y\":0.6281,\"radius\":0.0273},{\"tool\":\"erase\",\"x\":0.2049,\"y\":0.6734,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2233,\"y\":0.6465,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2417,\"y\":0.6196,\"radius\":0.0273}]", + "MaxSlope": 32, + "TrunkCollisions": true, + "TrunkDiameter": 20 + } + }, + { + "assetStoreId": "", + "name": "Rocks", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "Model": "assets/rocks.glb", + "Size": 72, + "Density": 0.08, + "Seed": 3, + "Coverage": "Painted", + "Edits": "[{\"tool\":\"add\",\"x\":0.6514,\"y\":0.7952,\"radius\":0.0426},{\"tool\":\"add\",\"x\":0.1035,\"y\":0.4268,\"radius\":0.0329},{\"tool\":\"add\",\"x\":0.1266,\"y\":0.8792,\"radius\":0.0443},{\"tool\":\"add\",\"x\":0.6437,\"y\":0.3421,\"radius\":0.0423},{\"tool\":\"add\",\"x\":0.564,\"y\":0.4313,\"radius\":0.042},{\"tool\":\"add\",\"x\":0.1024,\"y\":0.688,\"radius\":0.043},{\"tool\":\"add\",\"x\":0.8029,\"y\":0.3097,\"radius\":0.0459},{\"tool\":\"add\",\"x\":0.7928,\"y\":0.7394,\"radius\":0.0441},{\"tool\":\"add\",\"x\":0.9017,\"y\":0.6362,\"radius\":0.0399},{\"tool\":\"add\",\"x\":0.0913,\"y\":0.2126,\"radius\":0.0288},{\"tool\":\"erase\",\"x\":0.5,\"y\":0.7767,\"radius\":0.0895},{\"tool\":\"erase\",\"x\":0.229,\"y\":0.2538,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2559,\"y\":0.2721,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2827,\"y\":0.2905,\"radius\":0.0273},{\"tool\":\"erase\",\"x\":0.7462,\"y\":0.2615,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7279,\"y\":0.2884,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7095,\"y\":0.3153,\"radius\":0.0273},{\"tool\":\"erase\",\"x\":0.8036,\"y\":0.6649,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7767,\"y\":0.6465,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7498,\"y\":0.6281,\"radius\":0.0273},{\"tool\":\"erase\",\"x\":0.2049,\"y\":0.6734,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2233,\"y\":0.6465,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2417,\"y\":0.6196,\"radius\":0.0273}]", + "MaxSlope": 36, + "TrunkCollisions": true, + "TrunkDiameter": 80 + } + }, + { + "assetStoreId": "", + "name": "Stones", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Model": "assets/stones.glb", + "Size": 4, + "Density": 0.02, + "Seed": 4, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.5,\"y\":0.7767,\"radius\":0.0895},{\"tool\":\"erase\",\"x\":0.229,\"y\":0.2538,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2559,\"y\":0.2721,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2827,\"y\":0.2905,\"radius\":0.0273},{\"tool\":\"erase\",\"x\":0.7462,\"y\":0.2615,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7279,\"y\":0.2884,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7095,\"y\":0.3153,\"radius\":0.0273},{\"tool\":\"erase\",\"x\":0.8036,\"y\":0.6649,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7767,\"y\":0.6465,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7498,\"y\":0.6281,\"radius\":0.0273},{\"tool\":\"erase\",\"x\":0.2049,\"y\":0.6734,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2233,\"y\":0.6465,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2417,\"y\":0.6196,\"radius\":0.0273}]", + "CastShadow": false, + "DrawDistance": 3000 + } + }, + { + "assetStoreId": "", + "name": "Flowers", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Model": "assets/flowers.glb", + "Size": 37, + "Density": 0.08, + "Seed": 5, + "Coverage": "Painted", + "Edits": "[{\"tool\":\"add\",\"x\":0.5,\"y\":0.5,\"radius\":0.3743},{\"tool\":\"erase\",\"x\":0.5081,\"y\":0.5,\"radius\":0.1383},{\"tool\":\"erase\",\"x\":0.229,\"y\":0.2538,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2559,\"y\":0.2721,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2827,\"y\":0.2905,\"radius\":0.0273},{\"tool\":\"erase\",\"x\":0.7462,\"y\":0.2615,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7279,\"y\":0.2884,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7095,\"y\":0.3153,\"radius\":0.0273},{\"tool\":\"erase\",\"x\":0.8036,\"y\":0.6649,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7767,\"y\":0.6465,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7498,\"y\":0.6281,\"radius\":0.0273},{\"tool\":\"erase\",\"x\":0.2049,\"y\":0.6734,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2233,\"y\":0.6465,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2417,\"y\":0.6196,\"radius\":0.0273}]", + "CastShadow": false, + "DrawDistance": 2000, + "MinScale": 0.7, + "MaxScale": 1.3 + } + }, + { + "assetStoreId": "", + "name": "Mushrooms", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Model": "assets/mushrooms.glb", + "Size": 23, + "Density": 0.05, + "Seed": 6, + "Coverage": "Painted", + "Edits": "[{\"tool\":\"add\",\"x\":0.5,\"y\":0.5,\"radius\":0.3743},{\"tool\":\"erase\",\"x\":0.5081,\"y\":0.5,\"radius\":0.1383},{\"tool\":\"erase\",\"x\":0.229,\"y\":0.2538,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2559,\"y\":0.2721,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2827,\"y\":0.2905,\"radius\":0.0273},{\"tool\":\"erase\",\"x\":0.7462,\"y\":0.2615,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7279,\"y\":0.2884,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7095,\"y\":0.3153,\"radius\":0.0273},{\"tool\":\"erase\",\"x\":0.8036,\"y\":0.6649,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7767,\"y\":0.6465,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.7498,\"y\":0.6281,\"radius\":0.0273},{\"tool\":\"erase\",\"x\":0.2049,\"y\":0.6734,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2233,\"y\":0.6465,\"radius\":0.0221},{\"tool\":\"erase\",\"x\":0.2417,\"y\":0.6196,\"radius\":0.0273}]", + "CastShadow": false, + "DrawDistance": 2000, + "MinScale": 0.7, + "MaxScale": 1.3 + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Player" + }, + { + "objectName": "Coin" + }, + { + "objectName": "ControlsToggle" + }, + { + "folderName": "Mobile Controls", + "children": [ + { + "objectName": "Joystick" + }, + { + "objectName": "JumpButton" + } + ] + }, + { + "folderName": "World", + "children": [ + { + "objectName": "Terrain" + }, + { + "objectName": "MapLimit" + }, + { + "objectName": "Grass" + }, + { + "objectName": "Water" + } + ] + }, + { + "folderName": "Decoration", + "children": [ + { + "objectName": "Flag" + }, + { + "objectName": "Sign" + }, + { + "objectName": "PushableBox" + }, + { + "objectName": "Obstacle" + } + ] + }, + { + "folderName": "Nature", + "children": [ + { + "objectName": "Trees" + }, + { + "objectName": "PineTrees" + }, + { + "objectName": "Rocks" + }, + { + "objectName": "Stones" + }, + { + "objectName": "Flowers" + }, + { + "objectName": "Mushrooms" + } + ] + } + ] + }, + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Coins", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CompareTimer" + }, + "parameters": [ + "", + "\"Coin_PitchTimer\"", + ">", + "1" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CoinPitch", + "=", + "RandomFloatInRange(0.9,1.1)" + ] + }, + { + "type": { + "value": "RemoveTimer" + }, + "parameters": [ + "", + "\"Coin_PitchTimer\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Collision3D::AreWithinDistance" + }, + "parameters": [ + "", + "Coin", + "Player", + "50", + "", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Delete" + }, + "parameters": [ + "Coin", + "" + ] + }, + { + "type": { + "value": "PlaySound" + }, + "parameters": [ + "", + "assets/CoinPickUp.wav", + "", + "100", + "CoinPitch" + ] + }, + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"Coin_PitchTimer\"" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CoinPitch", + "+", + "RandomWithStep(0.050,0.1,0.025)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Rotate" + }, + "parameters": [ + "Coin", + "35", + "" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Controls toggle", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::HasJustBeenToggled" + }, + "parameters": [ + "ControlsToggle", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Set to Touch" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::IsChecked" + }, + "parameters": [ + "ControlsToggle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "ControlKind", + "=", + "\"Touch\"" + ] + }, + { + "type": { + "value": "Show" + }, + "parameters": [ + "JumpButton", + "" + ] + }, + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "JumpButton", + "MultitouchButton", + "yes" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Set to Keyboard" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteToggleSwitch::SpriteToggleSwitch::IsChecked" + }, + "parameters": [ + "ControlsToggle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "ControlKind", + "=", + "\"Keyboard\"" + ] + }, + { + "type": { + "value": "Hide" + }, + "parameters": [ + "JumpButton" + ] + }, + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "JumpButton", + "MultitouchButton", + "" + ] + } + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Common 3rd person camera controls", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Clamp camera angle" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationY" + }, + "parameters": [ + "Player", + "Object3D", + "=", + "clamp(Player.RotationY(), -90, 90)" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetElevationAngleOffset" + }, + "parameters": [ + "Player", + "ThirdPersonCamera", + "=", + "clamp(Player.ThirdPersonCamera::ElevationAngleOffset(), -90, 90)", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Move camera when pointer locks" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "MousePointerLock::isPointerLocked" + }, + "parameters": [ + "", + "" + ] + }, + { + "type": { + "value": "MousePointerLock::IsMoving" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetCameraRotation" + }, + "parameters": [ + "Player", + "ThirdPersonCamera", + "+", + "180 * MousePointerLock::MovementX() / SceneWindowWidth()", + "" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetElevationAngleOffset" + }, + "parameters": [ + "Player", + "ThirdPersonCamera", + "+", + "90 * MousePointerLock::MovementY() / SceneWindowHeight()", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsCameraLocked", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Prevent 3rd person auto-rotation while camera is being controlled" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsCameraLocked", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetRotationHalfwayDuration" + }, + "parameters": [ + "Player", + "ThirdPersonCamera", + "=", + "1000000", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Prevent camera rotation when Player moves backwards" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetTargetedRotationAngle" + }, + "parameters": [ + "Player", + "ThirdPersonCamera", + "=", + "Player.PhysicsCharacter3D::ForwardAngle()", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"s\"" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "abs(AngleDifference(90, SpriteMultitouchJoystick::StickAngle(1, \"Primary\")))", + "<", + "90" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetTargetedRotationAngle" + }, + "parameters": [ + "Player", + "ThirdPersonCamera", + "=", + "180 - Player.PhysicsCharacter3D::ForwardAngle()", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "abs(AngleDifference(Player.ThirdPersonCamera::TargetedRotationAngle() + 90, CameraAngle()))", + ">", + "90" + ] + } + ], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetRotationHalfwayDuration" + }, + "parameters": [ + "Player", + "ThirdPersonCamera", + "=", + "1000000", + "" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Mobile controls", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Hide" + }, + "parameters": [ + "Joystick" + ] + }, + { + "type": { + "value": "Hide" + }, + "parameters": [ + "JumpButton" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::ActivateControl" + }, + "parameters": [ + "Joystick", + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "ControlKind", + "=", + "\"Touch\"" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "IsCursorOnObject" + }, + "parameters": [ + "ControlsToggle", + "", + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Show hidden movement joystick" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::HasTouchStartedOnScreenSide" + }, + "parameters": [ + "", + "Joystick", + "\"Left\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::TeleportAndPress" + }, + "parameters": [ + "Joystick", + "CursorX(\"UI\")" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Request pointer lock when touching the screen" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "MouseButtonFromTextPressed" + }, + "parameters": [ + "", + "\"Left\"" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::HasTouchStartedOnScreenSide" + }, + "parameters": [ + "", + "Joystick", + "\"Left\"", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" + }, + "parameters": [ + "JumpButton", + "MultitouchButton", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "MousePointerLock::RequestPointerLock" + }, + "parameters": [ + "", + "" + ] + } + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Unlock the camera when the Player object is being moved" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::IsPressed" + }, + "parameters": [ + "Joystick", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsCameraLocked", + "False", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetRotationHalfwayDuration" + }, + "parameters": [ + "Player", + "ThirdPersonCamera", + "=", + "2", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetRotationHalfwayDuration" + }, + "parameters": [ + "Player", + "ThirdPersonCamera", + "=", + "0.5", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsCameraLocked", + "False", + "" + ] + } + ] + } + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Keyboard controls", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "ControlKind", + "=", + "\"Keyboard\"" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Request pointer lock when touching the screen" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "MouseButtonFromTextPressed" + }, + "parameters": [ + "", + "\"Left\"" + ] + }, + { + "type": { + "inverted": true, + "value": "MousePointerLock::isPointerLocked" + }, + "parameters": [ + "", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "IsCursorOnObject" + }, + "parameters": [ + "ControlsToggle", + "", + "", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "MousePointerLock::RequestPointerLock" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Unlock the camera when the Player object is being moved" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "AnyKeyPressed" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetRotationHalfwayDuration" + }, + "parameters": [ + "Player", + "ThirdPersonCamera", + "=", + "2", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsCameraLocked", + "False", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetRotationHalfwayDuration" + }, + "parameters": [ + "Player", + "ThirdPersonCamera", + "=", + "2", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetRotationHalfwayDuration" + }, + "parameters": [ + "Player", + "ThirdPersonCamera", + "=", + "1000000", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsCameraLocked", + "False", + "" + ] + } + ] + } + ] + } + ] + } + ], + "parameters": [] + } + ], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [ + { + "effectType": "Scene3D::AmbientLight", + "name": "3D Light", + "doubleParameters": { + "intensity": 0.25 + }, + "stringParameters": { + "color": "255;255;255" + }, + "booleanParameters": {} + }, + { + "effectType": "Scene3D::DirectionalLight", + "name": "Effect", + "doubleParameters": { + "distanceFromCamera": 1500, + "elevation": 45, + "frustumSize": 4000, + "intensity": 0.75, + "minimumShadowBias": 0, + "rotation": 30, + "shadowDistance": 2000 + }, + "stringParameters": { + "color": "255;255;255", + "shadowQuality": "medium", + "shadowRenderingMode": "fittedToCameraView", + "top": "Z+" + }, + "booleanParameters": { + "isCastingShadow": true + } + }, + { + "effectType": "Scene3D::Skybox", + "name": "SkyBox", + "doubleParameters": {}, + "stringParameters": { + "backFaceResourceName": "assets/Skybox_Back.png", + "bottomFaceResourceName": "assets/Skybox_Bottom.png", + "frontFaceResourceName": "assets/Skybox_Front.png", + "leftFaceResourceName": "assets/Skybox_Left.png", + "rightFaceResourceName": "assets/Skybox_Right.png", + "top": "Z+", + "topFaceResourceName": "assets/Skybox_Top.png" + }, + "booleanParameters": {} + } + ] + }, + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "MobileControls", + "renderingType": "", + "visibility": true, + "cameras": [], + "effects": [] + } + ], + "behaviorsSharedData": [ + { + "name": "Animation", + "type": "AnimatableCapability::AnimatableBehavior" + }, + { + "name": "ButtonFSM", + "type": "ButtonStates::ButtonFSM" + }, + { + "name": "ButtonScaleTween", + "type": "ButtonStates::ButtonScaleTween" + }, + { + "name": "Effect", + "type": "EffectCapability::EffectBehavior" + }, + { + "name": "Flippable", + "type": "FlippableCapability::FlippableBehavior" + }, + { + "name": "MultitouchButton", + "type": "SpriteMultitouchJoystick::MultitouchButton" + }, + { + "name": "Object3D", + "type": "Scene3D::Base3DBehavior" + }, + { + "name": "Opacity", + "type": "OpacityCapability::OpacityBehavior" + }, + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "gravityX": 0, + "gravityY": 0, + "gravityZ": -9.8, + "worldScale": 100 + }, + { + "name": "PhysicsCharacter3D", + "type": "Physics3D::PhysicsCharacter3D" + }, + { + "name": "Platformer3DKeyboardMapper", + "type": "PhysicsCharacter3DKeyMapper::Platformer3DKeyboardMapper" + }, + { + "name": "Platformer3DMultitouchMapper", + "type": "SpriteMultitouchJoystick::Platformer3DMultitouchMapper" + }, + { + "name": "Resizable", + "type": "ResizableCapability::ResizableBehavior" + }, + { + "name": "Scale", + "type": "ScalableCapability::ScalableBehavior" + }, + { + "name": "ThirdPersonCamera", + "type": "ThirdPersonCamera::ThirdPersonCamera" + }, + { + "name": "Tween", + "type": "Tween::TweenBehavior" + } + ] + } + ], + "externalEvents": [], + "tests": [ + { + "description": "Pressing Space makes the 3D character jump off the ground, and gravity brings it back down.", + "name": "Jumping with Space", + "type": "gameplay", + "source": [ + "// Space must make the physics character jump off the ground", + "// (PhysicsCharacter3D, mapped to Space by Platformer3DKeyboardMapper).", + "await harness.goToScene('Game Scene');", + "// Faster in the software renderer used to run tests (grass is thousands of blades, trees are drawn in batches it is slow with).", + "harness.setGameResolutionSize(160, 90);", + "harness.getCurrentRuntimeScene().getLayer('').setEffectBooleanParameter('Effect', 'isCastingShadow', false);", + "for (const grass of harness.getCurrentRuntimeScene().getObjects('Grass')) grass.hide(true);", + "for (const object of harness.getCurrentRuntimeScene().getAdhocListOfAllInstances()) if (object.type === 'Terrain3D::ScatteredModels') object.hide(true);", + "harness.setTimeoutMs(60000);", + "harness.watch('Player');", + "", + "const getPlayer = () => harness.getObjects('Player')[0];", + "const characterState = () => getPlayer().behaviors.PhysicsCharacter3D.state;", + "", + "await harness.stepFrames(5);", + "harness.assert(characterState().IsOnFloor === true, 'The player stands on the ground.');", + "const groundZ = getPlayer().z;", + "", + "// Doing nothing must leave the player on the ground.", + "await harness.stepFrames(10);", + "harness.assert(", + " Math.abs(getPlayer().z - groundZ) < 1 && characterState().IsOnFloor === true,", + " 'The player stays on the ground while no key is pressed.'", + ");", + "", + "harness.setKeyPressed('Space', true);", + "await harness.stepFrames(15);", + "harness.setKeyPressed('Space', false);", + "", + "let highestZ = groundZ;", + "let sawJumping = false;", + "let leftTheFloor = false;", + "await harness.stepFrames(30, {", + " onFrame: () => {", + " const state = characterState();", + " highestZ = Math.max(highestZ, getPlayer().z);", + " if (state.IsJumping === true) sawJumping = true;", + " if (state.IsOnFloor === false) leftTheFloor = true;", + " },", + "});", + "const jumpHeight = highestZ - groundZ;", + "console.log('jumpHeight=' + Math.round(jumpHeight));", + "", + "harness.assert(sawJumping, 'The character reports it is jumping after pressing Space.');", + "harness.assert(leftTheFloor, 'The player leaves the floor when jumping.');", + "harness.assert(", + " jumpHeight > 60,", + " 'The player rises well above the ground when jumping (rose by ' +", + " Math.round(jumpHeight) +", + " ' units).'", + ");", + "", + "// ...and gravity brings it back down.", + "const landedBack = await harness.stepUntil(", + " () => characterState().IsOnFloor === true,", + " { maxFrames: 40 }", + ");", + "harness.assert(landedBack, 'The player falls back onto the ground.');", + "harness.assert(", + " Math.abs(getPlayer().z - groundZ) < 2,", + " 'The player lands back at the height it jumped from.'", + ");" + ] + }, + { + "description": "The player walks forward into a coin lying on the ground: the coin is picked up.", + "name": "Collecting a coin by walking into it", + "type": "gameplay", + "source": [ + "// Walking into a coin must collect it: the events delete a Coin as soon as", + "// it is within 50 units of the Player.", + "await harness.goToScene('Game Scene');", + "// Faster in the software renderer used to run tests (grass is thousands of blades, trees are drawn in batches it is slow with).", + "harness.setGameResolutionSize(160, 90);", + "harness.getCurrentRuntimeScene().getLayer('').setEffectBooleanParameter('Effect', 'isCastingShadow', false);", + "for (const grass of harness.getCurrentRuntimeScene().getObjects('Grass')) grass.hide(true);", + "for (const object of harness.getCurrentRuntimeScene().getAdhocListOfAllInstances()) if (object.type === 'Terrain3D::ScatteredModels') object.hide(true);", + "harness.setTimeoutMs(60000);", + "harness.watch('Coin');", + "", + "const getPlayer = () => harness.getObjects('Player')[0];", + "const coinIds = () => harness.getObjects('Coin').map(coin => coin.id);", + "", + "await harness.stepFrames(5);", + "const player = getPlayer();", + "harness.assert(", + " player.behaviors.PhysicsCharacter3D.state.IsOnFloor === true,", + " 'The player stands on the ground.'", + ");", + "", + "// \"w\" walks the character toward the direction it faces.", + "const forwardAngle = player.behaviors.PhysicsCharacter3D.state.ForwardAngle;", + "const forwardRadians = (forwardAngle * Math.PI) / 180;", + "const forward = { x: Math.cos(forwardRadians), y: Math.sin(forwardRadians) };", + "", + "// Arrange: stand the player back from a coin lying on the ground, so that", + "// the coin is straight ahead. Reaching it is still up to the game.", + "const groundCoins = harness", + " .getNearby('Coin', 'Player', 5000)", + " .filter(coin => Math.abs(coin.centerZ - player.centerZ) < 60);", + "harness.assert(groundCoins.length > 0, 'There are coins lying on the ground.');", + "const targetCoin = groundCoins[0];", + "const approachDistance = 140;", + "harness.setObjectPosition(", + " player.id,", + " targetCoin.centerX - forward.x * approachDistance,", + " targetCoin.centerY - forward.y * approachDistance,", + " player.z", + ");", + "", + "// Standing away from the coin must not collect anything: this is what makes", + "// the check below a test of the player walking into it.", + "await harness.stepFrames(12);", + "const coinsBefore = coinIds().length;", + "harness.assert(", + " coinIds().includes(targetCoin.id),", + " 'The coin to walk into is still there once the player is in place.'", + ");", + "", + "// Walk forward into the coin.", + "harness.setKeyPressed('w', true);", + "const collected = await harness.stepUntil(", + " () => !coinIds().includes(targetCoin.id),", + " { maxFrames: 70 }", + ");", + "harness.releaseAllInputs();", + "", + "const rel = harness.getRelativePosition('Player', {", + " x: targetCoin.centerX,", + " y: targetCoin.centerY,", + " z: targetCoin.centerZ,", + "});", + "console.log(", + " 'coinsBefore=' +", + " coinsBefore +", + " ' coinsLeft=' +", + " coinIds().length +", + " ' distanceToCoin=' +", + " (rel ? Math.round(rel.distance) : 'n/a')", + ");", + "harness.assert(", + " collected,", + " 'Walking forward into the coin collects it (the player ended up ' +", + " (rel ? Math.round(rel.distance) : '?') +", + " ' units from it).'", + ");", + "harness.assert(", + " coinIds().length < coinsBefore,", + " 'The number of coins left in the level went down.'", + ");", + "", + "// A picture of what the game looks like once this has happened, kept with", + "// the test results.", + "await harness.takeScreenshot('the player standing where the coin was');" + ] + } + ], + "eventsFunctionsExtensions": [ + { + "author": "", + "category": "Input", + "dimension": "", + "extensionNamespace": "", + "fullName": "3D character keyboard mapper", + "gdevelopVersion": ">=5.5.222", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBhdGggZD0iTTI5LDE0aC00LjJjMC4xLTAuMywwLjItMC42LDAuMi0xYzAtMS43LTEuMy0zLTMtM0g4Yy0wLjYsMC0xLTAuNC0xLTFzMC40LTEsMS0xaDZjMS43LDAsMy0xLjMsMy0zVjJjMC0wLjYtMC40LTEtMS0xDQoJcy0xLDAuNC0xLDF2M2MwLDAuNi0wLjQsMS0xLDFIOEM2LjMsNiw1LDcuMyw1LDlzMS4zLDMsMywzaDE0YzAuNiwwLDEsMC40LDEsMXMtMC40LDEtMSwxSDNjLTAuNiwwLTEsMC40LTEsMXYxNGMwLDAuNiwwLjQsMSwxLDENCgloMjZjMC42LDAsMS0wLjQsMS0xVjE1QzMwLDE0LjQsMjkuNiwxNCwyOSwxNHogTTcsMThoMmMwLjYsMCwxLDAuNCwxLDFjMC0wLjYsMC40LTEsMS0xaDJjMC42LDAsMSwwLjQsMSwxYzAtMC42LDAuNC0xLDEtMWgyDQoJYzAuNiwwLDEsMC40LDEsMWMwLTAuNiwwLjQtMSwxLTFoMmMwLjYsMCwxLDAuNCwxLDFjMC0wLjYsMC40LTEsMS0xaDJjMC42LDAsMSwwLjQsMSwxcy0wLjQsMS0xLDFoLTJjLTAuNiwwLTEtMC40LTEtMQ0KCWMwLDAuNi0wLjQsMS0xLDFoLTJjLTAuNiwwLTEtMC40LTEtMWMwLDAuNi0wLjQsMS0xLDFoLTJjLTAuNiwwLTEtMC40LTEtMWMwLDAuNi0wLjQsMS0xLDFoLTJjLTAuNiwwLTEtMC40LTEtMWMwLDAuNi0wLjQsMS0xLDFINw0KCWMtMC42LDAtMS0wLjQtMS0xUzYuNCwxOCw3LDE4eiBNMTksMjJjMCwwLjEsMCwwLjMtMC4xLDAuNHMtMC4xLDAuMi0wLjIsMC4zYzAsMC0wLjEsMC4xLTAuMiwwLjFjMCwwLTAuMSwwLjEtMC4yLDAuMQ0KCWMtMC4xLDAtMC4xLDAtMC4yLDAuMWMtMC4xLDAtMC4xLDAtMC4yLDBjLTAuMSwwLTAuMSwwLTAuMiwwYy0wLjEsMC0wLjEsMC0wLjItMC4xYy0wLjEsMC0wLjEtMC4xLTAuMi0wLjFjMCwwLTAuMS0wLjEtMC4xLTAuMQ0KCWMtMC4xLTAuMS0wLjItMC4yLTAuMi0wLjNDMTcsMjIuMywxNywyMi4xLDE3LDIyYzAtMC4xLDAtMC4zLDAuMS0wLjRjMC0wLjEsMC4xLTAuMiwwLjItMC4zYzAsMCwwLjEtMC4xLDAuMS0wLjENCgljMC4xLDAsMC4xLTAuMSwwLjItMC4xYzAuMSwwLDAuMSwwLDAuMi0wLjFjMC4xLDAsMC4zLDAsMC40LDBjMC4xLDAsMC4xLDAsMC4yLDAuMWMwLjEsMCwwLjEsMC4xLDAuMiwwLjFjMC4xLDAsMC4xLDAuMSwwLjIsMC4xDQoJYzAuMSwwLjEsMC4yLDAuMiwwLjIsMC4zQzE5LDIxLjcsMTksMjEuOSwxOSwyMnogTTE1LDIyYzAsMC4xLDAsMC4zLTAuMSwwLjRzLTAuMSwwLjItMC4yLDAuM2MwLDAtMC4xLDAuMS0wLjIsMC4xDQoJYzAsMC0wLjEsMC4xLTAuMiwwLjFjLTAuMSwwLTAuMSwwLTAuMiwwLjFjLTAuMSwwLTAuMSwwLTAuMiwwYy0wLjMsMC0wLjUtMC4xLTAuNy0wLjNjLTAuMS0wLjEtMC4yLTAuMi0wLjItMC4zDQoJQzEzLDIyLjMsMTMsMjIuMSwxMywyMmMwLTAuMSwwLTAuMSwwLTAuMmMwLTAuMSwwLTAuMSwwLjEtMC4yYzAtMC4xLDAtMC4xLDAuMS0wLjJjMC0wLjEsMC4xLTAuMSwwLjEtMC4xYzAuMi0wLjIsMC42LTAuMywwLjktMC4zDQoJYzAuMSwwLDAuMSwwLDAuMiwwLjFjMC4xLDAsMC4xLDAuMSwwLjIsMC4xYzAuMSwwLDAuMSwwLjEsMC4yLDAuMWMwLDAsMC4xLDAuMSwwLjEsMC4xYzAsMC4xLDAuMSwwLjEsMC4xLDAuMmMwLDAuMSwwLDAuMSwwLjEsMC4yDQoJQzE1LDIxLjksMTUsMjEuOSwxNSwyMnogTTUsMjJjMC0wLjEsMC0wLjMsMC4xLTAuNGMwLTAuMSwwLjEtMC4yLDAuMi0wLjNjMC4yLTAuMiwwLjYtMC4zLDAuOS0wLjNjMC4xLDAsMC4xLDAsMC4yLDAuMQ0KCWMwLjEsMCwwLjEsMC4xLDAuMiwwLjFjMCwwLDAuMSwwLjEsMC4yLDAuMWMwLjEsMC4xLDAuMiwwLjIsMC4yLDAuM0M3LDIxLjcsNywyMS45LDcsMjJjMCwwLjMtMC4xLDAuNS0wLjMsMC43YzAsMC0wLjEsMC4xLTAuMiwwLjENCgljLTAuMSwwLTAuMSwwLjEtMC4yLDAuMWMtMC4xLDAtMC4xLDAtMC4yLDAuMWMtMC4xLDAtMC4xLDAtMC4yLDBjLTAuMywwLTAuNS0wLjEtMC43LTAuM0M1LjEsMjIuNSw1LDIyLjMsNSwyMnogTTgsMjZINw0KCWMtMC42LDAtMS0wLjQtMS0xczAuNC0xLDEtMWgxYzAuNiwwLDEsMC40LDEsMVM4LjYsMjYsOCwyNnogTTEwLjksMjIuNGMwLDAuMSwwLDAuMS0wLjEsMC4yYzAsMC0wLjEsMC4xLTAuMSwwLjENCglDMTAuNSwyMi45LDEwLjMsMjMsMTAsMjNjLTAuMSwwLTAuMywwLTAuNC0wLjFjLTAuMS0wLjEtMC4yLTAuMS0wLjMtMC4yYzAtMC4xLTAuMS0wLjEtMC4xLTAuMWMwLTAuMS0wLjEtMC4xLTAuMS0wLjINCgljMC0wLjEsMC0wLjEtMC4xLTAuMmMwLTAuMSwwLTAuMSwwLTAuMmMwLTAuMywwLjEtMC41LDAuMy0wLjdjMC4xLTAuMSwwLjItMC4yLDAuMy0wLjJjMC40LTAuMiwwLjgtMC4xLDEuMSwwLjINCgljMC4yLDAuMiwwLjMsMC40LDAuMywwLjdjMCwwLjEsMCwwLjEsMCwwLjJDMTEsMjIuMywxMSwyMi4zLDEwLjksMjIuNHogTTIwLDI2aC04Yy0wLjYsMC0xLTAuNC0xLTFzMC40LTEsMS0xaDhjMC42LDAsMSwwLjQsMSwxDQoJUzIwLjYsMjYsMjAsMjZ6IE0yMi45LDIyLjRjMCwwLjEsMCwwLjEtMC4xLDAuMmMwLDAtMC4xLDAuMS0wLjEsMC4xYzAsMC0wLjEsMC4xLTAuMiwwLjFjMCwwLTAuMSwwLjEtMC4yLDAuMWMtMC4xLDAtMC4xLDAtMC4yLDAuMQ0KCWMtMC4xLDAtMC4xLDAtMC4yLDBjLTAuMSwwLTAuMSwwLTAuMiwwYy0wLjEsMC0wLjEsMC0wLjItMC4xYy0wLjEsMC0wLjEtMC4xLTAuMi0wLjFjMCwwLTAuMS0wLjEtMC4xLTAuMWMwLTAuMS0wLjEtMC4xLTAuMS0wLjENCgljMC0wLjEtMC4xLTAuMS0wLjEtMC4yYzAtMC4xLDAtMC4xLTAuMS0wLjJjMC0wLjEsMC0wLjEsMC0wLjJjMC0wLjMsMC4xLTAuNSwwLjMtMC43YzAsMCwwLjEtMC4xLDAuMS0wLjFjMC4xLDAsMC4xLTAuMSwwLjItMC4xDQoJYzAuMSwwLDAuMSwwLDAuMi0wLjFjMC4xLDAsMC4zLDAsMC40LDBjMC4xLDAsMC4xLDAsMC4yLDAuMWMwLjEsMCwwLjEsMC4xLDAuMiwwLjFjMC4xLDAsMC4xLDAuMSwwLjIsMC4xYzAuMiwwLjIsMC4zLDAuNCwwLjMsMC43DQoJYzAsMC4xLDAsMC4xLDAsMC4yQzIzLDIyLjMsMjMsMjIuMywyMi45LDIyLjR6IE0yNSwyNmgtMWMtMC42LDAtMS0wLjQtMS0xczAuNC0xLDEtMWgxYzAuNiwwLDEsMC40LDEsMVMyNS42LDI2LDI1LDI2eiBNMjYuNywyMi43DQoJQzI2LjUsMjIuOSwyNi4zLDIzLDI2LDIzYy0wLjEsMC0wLjEsMC0wLjIsMGMtMC4xLDAtMC4xLDAtMC4yLTAuMWMtMC4xLDAtMC4xLTAuMS0wLjItMC4xYy0wLjEsMC0wLjEtMC4xLTAuMi0wLjENCglDMjUuMSwyMi41LDI1LDIyLjMsMjUsMjJjMC0wLjEsMC0wLjMsMC4xLTAuNGMwLTAuMSwwLjEtMC4yLDAuMi0wLjNjMCwwLDAuMS0wLjEsMC4yLTAuMWMwLDAsMC4xLTAuMSwwLjItMC4xYzAuMSwwLDAuMSwwLDAuMi0wLjENCgljMC4zLTAuMSwwLjcsMCwwLjksMC4zYzAuMiwwLjIsMC4zLDAuNCwwLjMsMC43QzI3LDIyLjMsMjYuOSwyMi41LDI2LjcsMjIuN3oiLz4NCjwvc3ZnPg0K", + "name": "PhysicsCharacter3DKeyMapper", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/Glyphster Pack/Master/SVG/Computers and Hardware/3e5871434a72821bf3ecb44a6857e62a438cf23dc8f95966f56ae97e95315468_Computers and Hardware_keyboard.svg", + "shortDescription": "3D platformer and 3D shooter keyboard controls.", + "version": "1.1.1", + "description": "3D platformer and 3D shooter keyboard controls.", + "origin": { + "identifier": "PhysicsCharacter3DKeyMapper", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT" + }, + "eventsBasedBehaviors": [ + { + "description": "Control a 3D physics character with a keyboard for a platformer or a top-down game.", + "fullName": "3D platformer keyboard mapper", + "helpPath": "", + "iconUrl": "", + "name": "Platformer3DKeyboardMapper", + "objectType": "", + "previewIconUrl": "", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "LockedCameraAngle", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraAngle", + "=", + "CameraAngle(Object.Layer())" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "LockedCameraAngle", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Up" + ] + }, + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Down" + ] + }, + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Left" + ] + }, + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Right" + ] + } + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Up" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetAngle", + "=", + "-90" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Down" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetAngle", + "=", + "90" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Left" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetAngle", + "=", + "180" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Right" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetAngle", + "=", + "0" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Up" + ] + }, + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Left" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetAngle", + "=", + "-135" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Up" + ] + }, + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Right" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetAngle", + "=", + "-45" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Down" + ] + }, + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Right" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetAngle", + "=", + "45" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Down" + ] + }, + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Left" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetAngle", + "=", + "135" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetAngle", + "+", + "CameraAngle" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::IsForwardAngleAround" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D", + "TargetAngle + 180", + "67.5" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SetCurrentForwardSpeed" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D", + "=", + "-Object.PhysicsCharacter3D::CurrentForwardSpeed()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SetForwardAngle" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D", + "=", + "TargetAngle" + ] + }, + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateForwardKey" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D" + ] + } + ] + } + ], + "variables": [ + { + "name": "TargetAngle", + "type": "number", + "value": 0 + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Jump" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateJumpKey" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "PhysicsCharacter3DKeyMapper::Platformer3DKeyboardMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if camera is locked for the frame.", + "fullName": "Camera is locked", + "functionType": "Condition", + "name": "LockedCameraAngle", + "private": true, + "sentence": "_PARAM0_ camera is locked for the frame", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "LockedCameraAngle", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "PhysicsCharacter3DKeyMapper::Platformer3DKeyboardMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Change if camera is locked for the frame.", + "fullName": "Lock the camera", + "functionType": "Action", + "name": "SetLockedCameraAngle", + "private": true, + "sentence": "_PARAM0_ camera is locked for the frame: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "LockedCameraAngle", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "LockedCameraAngle", + "True", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "PhysicsCharacter3DKeyMapper::Platformer3DKeyboardMapper", + "type": "behavior" + }, + { + "defaultValue": "yes", + "description": "Lock the camera", + "name": "Value", + "optional": true, + "type": "yesorno" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "LockedCameraAngle" + }, + { + "functionName": "SetLockedCameraAngle" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics character", + "extraInformation": [ + "Physics3D::PhysicsCharacter3D" + ], + "choices": [], + "name": "PhysicsCharacter3D" + }, + { + "value": "Space", + "type": "KeyboardKey", + "label": "Jump key", + "name": "Jump" + }, + { + "value": "w", + "type": "KeyboardKey", + "label": "Up key", + "name": "Up" + }, + { + "value": "a", + "type": "KeyboardKey", + "label": "Left key", + "name": "Left" + }, + { + "value": "s", + "type": "KeyboardKey", + "label": "Down key", + "name": "Down" + }, + { + "value": "d", + "type": "KeyboardKey", + "label": "Right key", + "name": "Right" + }, + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "", + "hidden": true, + "name": "CameraAngle" + }, + { + "value": "", + "type": "Boolean", + "label": "Camera is locked for the frame", + "hidden": true, + "name": "LockedCameraAngle" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "PhysicsCharacter3D" + }, + { + "propertyName": "Jump" + }, + { + "propertyName": "Up" + }, + { + "propertyName": "Left" + }, + { + "propertyName": "Down" + }, + { + "propertyName": "Right" + }, + { + "propertyName": "CameraAngle" + }, + { + "propertyName": "LockedCameraAngle" + } + ] + } + }, + { + "description": "Control a 3D physics character with a keyboard for a first or third person shooter.", + "fullName": "3D shooter keyboard mapper", + "helpPath": "", + "iconUrl": "", + "name": "Shooter3DKeyboardMapper", + "objectType": "", + "previewIconUrl": "", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Up" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateForwardKey" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Down" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateBackwardKey" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Left" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateLeftKey" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Right" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateRightKey" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Jump" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateJumpKey" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "PhysicsCharacter3DKeyMapper::Shooter3DKeyboardMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics character", + "extraInformation": [ + "Physics3D::PhysicsCharacter3D" + ], + "choices": [], + "name": "PhysicsCharacter3D" + }, + { + "value": "Space", + "type": "KeyboardKey", + "label": "Jump key", + "name": "Jump" + }, + { + "value": "w", + "type": "KeyboardKey", + "label": "Up key", + "name": "Up" + }, + { + "value": "a", + "type": "KeyboardKey", + "label": "Left key", + "name": "Left" + }, + { + "value": "s", + "type": "KeyboardKey", + "label": "Down key", + "name": "Down" + }, + { + "value": "d", + "type": "KeyboardKey", + "label": "Right key", + "name": "Right" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "PhysicsCharacter3D" + }, + { + "propertyName": "Jump" + }, + { + "propertyName": "Up" + }, + { + "propertyName": "Left" + }, + { + "propertyName": "Down" + }, + { + "propertyName": "Right" + } + ] + } + } + ], + "eventsBasedObjects": [] + }, + { + "author": "", + "category": "Camera", + "dimension": "3D", + "extensionNamespace": "", + "fullName": "Third person camera", + "gdevelopVersion": ">=5.5.269", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSI3LDEwIDEsMTMgNywxNiAxMywxMyAiLz4NCjxwb2x5bGluZSBjbGFzcz0ic3QwIiBwb2ludHM9IjEsMTMgMSwyMCA3LDIzIDEzLDIwIDEzLDEzICIvPg0KPGxpbmUgY2xhc3M9InN0MCIgeDE9IjciIHkxPSIxNiIgeDI9IjciIHkyPSIyMyIvPg0KPGxpbmUgY2xhc3M9InN0MCIgeDE9IjMxIiB5MT0iMTYiIHgyPSIyMSIgeTI9IjgiLz4NCjxsaW5lIGNsYXNzPSJzdDAiIHgxPSIyMSIgeTE9IjI0IiB4Mj0iMzEiIHkyPSIxNiIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTIyLjcsMjIuNkMyMCwyMS43LDE4LDE5LjEsMTgsMTZjMC0zLjEsMi01LjcsNC43LTYuNiIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTE5LjgsMTEuM2MxLjQsMS4xLDIuMiwyLjgsMi4yLDQuN2MwLDEuOS0wLjksMy42LTIuMiw0LjciLz4NCjwvc3ZnPg0K", + "name": "ThirdPersonCamera", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/Line Hero Pack/Master/SVG/Virtual Reality/94e95d2c318e1f3dc7151a351024e13c574e1e44669c6696aa107d60230073f6_Virtual Reality_3d_vision_eye_vr.svg", + "shortDescription": "Third-person camera orbiting an object at configurable distance, elevation, and rotation.", + "version": "1.9.0", + "description": [ + "Move the camera to look at an object from a given distance with a rotation and an elevation angles.", + "", + "It can be useful for:", + "- 3D platformer games ([open the project online](https://editor.gdevelop.io/?project=example://3d-platformer))", + "- 3D racing games ([open the project online](https://editor.gdevelop.io/?project=example://3d-car-coin-hunt))", + "- Orbit camera controls ([open the project online](https://editor.gdevelop.io/?project=example://3d-tile-based-city-builder))", + "- Isometric-like point of view ([open the project online](https://editor.gdevelop.io/?project=example://3d-road-crosser))" + ], + "origin": { + "identifier": "ThirdPersonCamera", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d", + "camera" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "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": [ + "//@ts-ignore", + "if (gdjs.__thirdPersonCameraExtension) {", + " //@ts-ignore", + " return;", + "}", + "", + "/**", + " * It can be built the follow:", + " * ", + " * `objectDefaultRotation.setFromAxisAngle(new THREE.Vector3(0, 0, 1), -Math.PI/2);`", + " * ", + " * `objectDefaultRotation.multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), Math.PI/2));`", + " * ", + " */", + "const objectDefaultRotation = new THREE.Quaternion(0.5, -0.5, -0.5, 0.5);", + "const targetQuaternion = new THREE.Quaternion();", + "const yAxis = new THREE.Vector3(0, 1, 0);", + "const zAxis = new THREE.Vector3(0, 0, 1);", + "const quaternion = new THREE.Quaternion();", + "const objectRotation = new THREE.Euler();", + "", + "/**", + " * @param object {gdjs.RuntimeObject}", + " * @param ratio {number}", + " * @param elevationAngleOffset {number}", + " */", + "function slerpCamera(object, ratio, rotationAngleOffset, elevationAngleOffset) {", + " const layer = object.getInstanceContainer().getLayer(object.getLayer());", + " const threeCamera = layer.getRenderer().getThreeCamera();", + " const threeObject = object.get3DRendererObject();", + "", + " objectRotation.copy(threeObject.rotation);", + " objectRotation.z = -objectRotation.z;", + " objectRotation.x = -objectRotation.x;", + " targetQuaternion.setFromEuler(objectRotation);", + " targetQuaternion.multiply(quaternion.setFromAxisAngle(zAxis, -gdjs.toRad(rotationAngleOffset)));", + " targetQuaternion.multiply(quaternion.setFromAxisAngle(yAxis, gdjs.toRad(elevationAngleOffset)));", + " targetQuaternion.multiply(objectDefaultRotation);", + "", + " threeCamera.quaternion.slerp(targetQuaternion, ratio);", + "", + " // The layer angle takes over the 3D camera Z rotation.", + " layer.setCameraRotation(gdjs.toDegrees(-threeCamera.rotation.z));", + "}", + "", + "//@ts-ignore", + "gdjs.__thirdPersonCameraExtension = {", + " slerpCamera,", + "};", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Move the camera to look at a position from a distance.", + "fullName": "Look at a position from a distance (deprecated)", + "functionType": "Action", + "name": "LookFromDistanceAtPosition", + "private": true, + "sentence": "Move the camera of _PARAM6_ to look at _PARAM1_; _PARAM2_ from _PARAM3_ pixels with a rotation of _PARAM4_° and an elevation of _PARAM5_°", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "=", + "PositionX + Distance * cos(ToRad(RotationAngle + 90)) * cos(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "=", + "PositionY + Distance * sin(ToRad(RotationAngle + 90)) * cos(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "Distance * sin(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::TurnCameraTowardPosition" + }, + "parameters": [ + "", + "PositionX", + "PositionY", + "0", + "Layer", + "", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Position on X axis", + "name": "PositionX", + "type": "expression" + }, + { + "description": "Position on Y axis", + "name": "PositionY", + "type": "expression" + }, + { + "description": "Distance", + "name": "Distance", + "type": "expression" + }, + { + "description": "Rotation angle (around Z axis)", + "name": "RotationAngle", + "type": "expression" + }, + { + "description": "Elevation angle (around Y axis)", + "name": "ElevationAngle", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera to look at an object from a distance.", + "fullName": "Look at an object from a distance (deprecated)", + "functionType": "Action", + "name": "LookFromDistanceAtObject", + "private": true, + "sentence": "Move the camera of _PARAM5_ to look at _PARAM1_ from _PARAM2_ pixels with a rotation of _PARAM3_° and an elevation of _PARAM4_°", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition" + }, + "parameters": [ + "", + "Object.CenterX()", + "Object.CenterY()", + "Distance", + "RotationAngle", + "ElevationAngle", + "Layer", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "Distance", + "name": "Distance", + "type": "expression" + }, + { + "description": "Rotation angle (around Z axis)", + "name": "RotationAngle", + "type": "expression" + }, + { + "description": "Elevation angle (around Y axis)", + "name": "ElevationAngle", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera to look at a position from a distance.", + "fullName": "Look at a position from a distance", + "functionType": "Action", + "name": "LookFromDistanceAtPosition3D", + "sentence": "Move the camera of _PARAM7_ to look at _PARAM1_; _PARAM2_; _PARAM3_ from _PARAM4_ pixels with a rotation of _PARAM5_° and an elevation of _PARAM6_°", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "=", + "PositionX + Distance * cos(ToRad(RotationAngle + 90)) * cos(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "=", + "PositionY + Distance * sin(ToRad(RotationAngle + 90)) * cos(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "PositionZ + Distance * sin(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationX" + }, + "parameters": [ + "", + "=", + "90 - ElevationAngle", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationY" + }, + "parameters": [ + "", + "=", + "0", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraAngle" + }, + "parameters": [ + "", + "=", + "RotationAngle", + "Layer", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Position on X axis", + "name": "PositionX", + "type": "expression" + }, + { + "description": "Position on Y axis", + "name": "PositionY", + "type": "expression" + }, + { + "description": "Position on Z axis", + "name": "PositionZ", + "type": "expression" + }, + { + "description": "Distance", + "name": "Distance", + "type": "expression" + }, + { + "description": "Rotation angle (around Z axis)", + "name": "RotationAngle", + "type": "expression" + }, + { + "description": "Elevation angle (around Y axis)", + "name": "ElevationAngle", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera to look at an object from a distance.", + "fullName": "Look at an object from a distance", + "functionType": "Action", + "group": "Layers and cameras", + "name": "LookFromDistanceAtObject3D", + "sentence": "Move the camera of _PARAM6_ to look at _PARAM1_ from _PARAM3_ pixels with a rotation of _PARAM4_° and an elevation of _PARAM5_°", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition3D" + }, + "parameters": [ + "", + "Object.CenterX()", + "Object.CenterY()", + "Object.Object3D::CenterZ()", + "Distance", + "RotationAngle", + "ElevationAngle", + "Layer", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Distance", + "name": "Distance", + "type": "expression" + }, + { + "description": "Rotation angle (around Z axis)", + "name": "RotationAngle", + "type": "expression" + }, + { + "description": "Elevation angle (around Y axis)", + "name": "ElevationAngle", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Expression", + "name": "RotatedX", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Cos", + "=", + "cos(ToRad(RotationAngle))" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Sin", + "=", + "sin(ToRad(RotationAngle))" + ] + }, + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Cos * X - Sin * Y" + ] + } + ], + "variables": [ + { + "folded": true, + "name": "Cos", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "Sin", + "type": "number", + "value": 0 + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Rotation angle", + "name": "RotationAngle", + "supplementaryInformation": "Tank::Tank", + "type": "expression" + }, + { + "description": "", + "name": "X", + "type": "expression" + }, + { + "description": "", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Expression", + "name": "RotatedY", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Cos", + "=", + "cos(ToRad(RotationAngle))" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Sin", + "=", + "sin(ToRad(RotationAngle))" + ] + }, + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Sin * X + Cos * Y" + ] + } + ], + "variables": [ + { + "folded": true, + "name": "Cos", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "Sin", + "type": "number", + "value": 0 + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Rotation angle", + "name": "RotationAngle", + "supplementaryInformation": "Tank::Tank", + "type": "expression" + }, + { + "description": "", + "name": "X", + "type": "expression" + }, + { + "description": "", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "DefineHelperClasses" + }, + { + "functionName": "LookFromDistanceAtPosition" + }, + { + "functionName": "LookFromDistanceAtObject" + }, + { + "functionName": "LookFromDistanceAtPosition3D" + }, + { + "folderName": "Layers and cameras", + "children": [ + { + "functionName": "LookFromDistanceAtObject3D" + } + ] + }, + { + "functionName": "RotatedX" + }, + { + "functionName": "RotatedY" + } + ] + }, + "eventsBasedBehaviors": [ + { + "description": "Smoothly follow an object at a distance.", + "fullName": "Third person camera", + "helpPath": "", + "iconUrl": "", + "name": "ThirdPersonCamera", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Update private properties through setters to check their values and initialize state." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetRotationHalfwayDuration" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "RotationHalfwayDuration", + "" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetElevationHalfwayDuration" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "ElevationHalfwayDuration", + "" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetTranslationZHalfwayDuration" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "TranslationZHalfwayDuration", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "HasJustBeenCreated", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "HasJustBeenCreated", + "False", + "" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraZ", + "=", + "Object.Object3D::CenterZ()" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::JumpToTargetedRotation" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "IsCalledManually", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::DoMoveCameraCloser" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera closer to the object. This action must be called after the object has moved for the frame.", + "fullName": "Move the camera closer", + "functionType": "Action", + "name": "MoveCameraCloser", + "sentence": "Move the camera closer to _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "The camera following is called with an action, the call from doStepPreEvents must be disabled to avoid to do it twice." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BehaviorActivated" + }, + "parameters": [ + "Object", + "Behavior" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsCalledManually", + "False", + "" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::DoMoveCameraCloser" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera closer to the object.", + "fullName": "Do move the camera closer", + "functionType": "Action", + "name": "DoMoveCameraCloser", + "private": true, + "sentence": "Do move the camera closer _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "At each frame, the camera must catchup the target by a given ratio (speed)\ncameraX(t) - targetX = (cameraX(t - 1) - targetX) * (1 - speed)\n\nThe frame rate must not impact on the catch-up speed, we don't want a speed in ratio per frame but a speed ratio per second, like this:\ncameraX(t) - targetX = (cameraX(t - 1s) - targetX) * (1 - speed)\n\nOk, but we still need to process each frame, we can use a exponent for this:\ncameraX(t) - targetX = (cameraX(t - timeDelta) - targetX) * (1 - speed)^timeDelta\ncameraX(t) = targetX + (cameraX(t - timeDelta) - targetX) * exp(timeDelta * ln((1 - speed)))\n\npow is probably more efficient than precalculated log if the speed is changed continuously but this might be rare enough." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "RotationMode", + "=", + "\"ZYX\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::Slerp" + }, + "parameters": [ + "Object", + "Behavior", + "1 - exp(TimeDelta() * RotationLogSpeed)", + "RotationAngleOffset", + "ElevationAngleOffset", + "" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::MoveToObject" + }, + "parameters": [ + "Object", + "Behavior", + "OffsetY", + "OffsetX", + "OffsetZ", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "-", + "Distance * Scene3D::CameraForwardX(Object.Layer())", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "-", + "Distance * Scene3D::CameraForwardY(Object.Layer())", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "-", + "Distance * Scene3D::CameraForwardZ(Object.Layer())", + "Object.Layer()", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "RotationMode", + "!=", + "\"ZYX\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "False", + "" + ] + } + ] + } + ], + "actions": [], + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Z translation", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CameraZ", + "<", + "Object.Behavior::FreeAreaZMin()" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraZ", + "=", + "Object.Behavior::FreeAreaZMin() + (CameraZ - Object.Behavior::FreeAreaZMin()) * exp(TimeDelta() * TranslationZLogSpeed)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CameraZ", + ">", + "Object.Behavior::FreeAreaZMax()" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraZ", + "=", + "Object.Behavior::FreeAreaZMax() + (CameraZ - Object.Behavior::FreeAreaZMax()) * exp(TimeDelta() * TranslationZLogSpeed)" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Z rotation", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedRotationAngle", + "=", + "Object.Angle()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "RotationMode", + "!=", + "\"Z\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsElevatingWithObject", + "True", + "" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedElevationAngle", + "=", + "Object.Object3D::RotationY()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraRotationAngle", + "=", + "TargetedRotationAngle + 90 + RotationAngleOffset + AngleDifference(CameraAngle(Object.Layer()), TargetedRotationAngle + 90 + RotationAngleOffset) * exp(TimeDelta() * RotationLogSpeed)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraElevationAngle", + "=", + "TargetedElevationAngle + ElevationAngleOffset + AngleDifference(90 - Scene3D::CameraRotationX(Object.Layer()), TargetedElevationAngle + ElevationAngleOffset) * exp(TimeDelta() * ElevationLogSpeed)" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition3D" + }, + "parameters": [ + "", + "Object.CenterX() + ThirdPersonCamera::RotatedX(CameraRotationAngle, OffsetX, -OffsetY)", + "Object.CenterY() + ThirdPersonCamera::RotatedY(CameraRotationAngle, OffsetX, -OffsetY)", + "CameraZ + OffsetZ", + "Distance", + "CameraRotationAngle", + "CameraElevationAngle", + "Object.Layer()", + "" + ] + } + ], + "variables": [ + { + "folded": true, + "name": "CameraRotationAngle", + "type": "number", + "value": 0 + }, + { + "name": "CameraElevationAngle", + "type": "number", + "value": 0 + } + ] + } + ], + "parameters": [] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Lerp camera", + "functionType": "Action", + "name": "Slerp", + "private": true, + "sentence": "Lerp the camera rotation to _PARAM0_ with a ratio of _PARAM2_ with rotation offset _PARAM3_° and elevation offset _PARAM4_°", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.RuntimeObject3D} */", + "const object = objects[0];", + "const ratio = eventsFunctionContext.getArgument(\"Ratio\");", + "const rotationAngleOffset = eventsFunctionContext.getArgument(\"NewRotationAngleOffset\");", + "const elevationAngleOffset = eventsFunctionContext.getArgument(\"NewElevationAngleOffset\");", + "", + "gdjs.__thirdPersonCameraExtension.slerpCamera(object, ratio, rotationAngleOffset, elevationAngleOffset);", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + }, + { + "description": "Ratio", + "name": "Ratio", + "type": "expression" + }, + { + "description": "Rotation angle offset", + "name": "NewRotationAngleOffset", + "type": "expression" + }, + { + "description": "Elevation angle offset", + "name": "NewElevationAngleOffset", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "fullName": "Move to object", + "functionType": "Action", + "name": "MoveToObject", + "private": true, + "sentence": "Change the camera position to _PARAM0_ with offset _PARAM2_, _PARAM3_, _PARAM4_ ", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "=", + "Object.CenterX() + LocalOffsetX * Object.Object3D::ForwardX() + LocalOffsetY * Object.Object3D::RightX() + LocalOffsetZ * Object.Object3D::UpX()", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "=", + "Object.CenterY() + LocalOffsetX * Object.Object3D::ForwardY() + LocalOffsetY * Object.Object3D::RightY() + LocalOffsetZ * Object.Object3D::UpY()", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "Object.Object3D::CenterZ() + LocalOffsetX * Object.Object3D::ForwardZ() + LocalOffsetY * Object.Object3D::RightZ() + LocalOffsetZ * Object.Object3D::UpZ()", + "Object.Layer()", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + }, + { + "description": "X offset", + "name": "LocalOffsetX", + "type": "expression" + }, + { + "description": "Y offset", + "name": "LocalOffsetY", + "type": "expression" + }, + { + "description": "Z offset", + "name": "LocalOffsetZ", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Rotate the camera all the way to the targeted angle.", + "fullName": "Rotate the camera all the way", + "functionType": "Action", + "name": "JumpToTargetedRotation", + "sentence": "Rotate the camera all the way to the targeted angle of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedRotationAngle", + "=", + "Object.Angle()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsElevatingWithObject", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedElevationAngle", + "=", + "Object.Object3D::RotationY()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraAngle", + "=", + "TargetedRotationAngle + 90 + RotationAngleOffset" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition3D" + }, + "parameters": [ + "", + "Object.CenterX() + ThirdPersonCamera::RotatedX(CameraAngle, OffsetX, -OffsetY)", + "Object.CenterY() + ThirdPersonCamera::RotatedY(CameraAngle, OffsetX, -OffsetY)", + "CameraZ + OffsetZ", + "Distance", + "CameraAngle", + "Object.Object3D::RotationY() + ElevationAngleOffset", + "", + "" + ] + } + ], + "variables": [ + { + "folded": true, + "name": "CameraAngle", + "type": "number", + "value": 0 + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the camera rotation.", + "fullName": "Camera rotation", + "functionType": "ExpressionAndCondition", + "name": "RotationAngle", + "private": true, + "sentence": "the camera rotation", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "CameraAngle(Object.Layer())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Rotate the camera all the way to the targeted angle.", + "fullName": "Rotate the camera all the way", + "functionType": "ActionWithOperator", + "getterName": "RotationAngle", + "name": "SetCameraRotation", + "sentence": "Rotate the camera all the way to the targeted angle of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition3D" + }, + "parameters": [ + "", + "Object.CenterX() + ThirdPersonCamera::RotatedX(Value, OffsetX, -OffsetY)", + "Object.CenterY() + ThirdPersonCamera::RotatedY(Value, OffsetX, -OffsetY)", + "CameraZ + OffsetZ", + "Distance", + "Value", + "Object.Object3D::RotationY() + ElevationAngleOffset", + "", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the halfway time for rotation of the object.", + "fullName": "Halfway time for rotation", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "RotationHalfwayDuration", + "sentence": "the halfway time for rotation", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "RotationHalfwayDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "RotationHalfwayDuration", + "group": "Third person camera configuration", + "name": "SetRotationHalfwayDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "ln((1 - speed)) = ln(1 / 2) / halfwatTime" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "RotationHalfwayDuration", + "=", + "Value" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "RotationLogSpeed", + "=", + "log(0.5) / Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the halfway time for elevation rotation of the object.", + "fullName": "Halfway time for elevation rotation", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "ElevationHalfwayDuration", + "sentence": "the halfway time for elevation rotation", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ElevationHalfwayDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ElevationHalfwayDuration", + "group": "Third person camera configuration", + "name": "SetElevationHalfwayDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "ln((1 - speed)) = ln(1 / 2) / halfwatTime" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ElevationHalfwayDuration", + "=", + "Value" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ElevationLogSpeed", + "=", + "log(0.5) / Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the halfway time on Z axis of the object.", + "fullName": "Halfway time on Z axis", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "TranslationZHalfwayDuration", + "sentence": "the halfway time on Z axis", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "TranslationZHalfwayDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "TranslationZHalfwayDuration", + "group": "Third person camera configuration", + "name": "SetTranslationZHalfwayDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "ln((1 - speed)) = ln(1 / 2) / halfwatTime" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TranslationZHalfwayDuration", + "=", + "Value" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TranslationZLogSpeed", + "=", + "log(0.5) / Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Return follow free area bottom border Z.", + "fullName": "Free area Z min", + "functionType": "Expression", + "group": "Private", + "name": "FreeAreaZMin", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Object3D::CenterZ() + OffsetZ - FollowFreeAreaZMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Return follow free area top border Z.", + "fullName": "Free area Z max", + "functionType": "Expression", + "group": "Private", + "name": "FreeAreaZMax", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Object3D::CenterZ() + OffsetZ + FollowFreeAreaZMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the follow free area top border on Z axis of the object.", + "fullName": "Follow free area top border on Z axis", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "FollowFreeAreaZMax", + "sentence": "the follow free area top border on Z axis", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FollowFreeAreaZMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FollowFreeAreaZMax", + "group": "Third person camera configuration", + "name": "SetFollowFreeAreaZMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FollowFreeAreaZMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the follow free area bottom border on Z axis of the object.", + "fullName": "Follow free area bottom border on Z axis", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "FollowFreeAreaZMin", + "sentence": "the follow free area bottom border on Z axis", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FollowFreeAreaZMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FollowFreeAreaZMin", + "group": "Third person camera configuration", + "name": "SetFollowFreeAreaZMin", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FollowFreeAreaZMin", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the camera distance of the object.", + "fullName": "Camera distance", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "Distance", + "sentence": "the camera distance", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Distance" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Distance", + "group": "Third person camera configuration", + "name": "SetDistance", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Distance", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the lateral distance offset of the object.", + "fullName": "Lateral distance offset", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "OffsetX", + "sentence": "the lateral distance offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "OffsetX" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "OffsetX", + "group": "Third person camera configuration", + "name": "SetOffsetX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OffsetX", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the ahead distance offset of the object.", + "fullName": "Ahead distance offset", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "OffsetY", + "sentence": "the ahead distance offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "OffsetY" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "OffsetY", + "group": "Third person camera configuration", + "name": "SetOffsetY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OffsetY", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the z offset of the object.", + "fullName": "Z offset", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "OffsetZ", + "sentence": "the z offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "OffsetZ" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "OffsetZ", + "group": "Third person camera configuration", + "name": "SetOffsetZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OffsetZ", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the rotation angle offset of the object.", + "fullName": "Rotation angle offset", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "RotationAngleOffset", + "sentence": "the rotation angle offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "RotationAngleOffset" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "RotationAngleOffset", + "group": "Third person camera configuration", + "name": "SetRotationAngleOffset", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "RotationAngleOffset", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the elevation angle offset of the object.", + "fullName": "Elevation angle offset", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "ElevationAngleOffset", + "sentence": "the elevation angle offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ElevationAngleOffset" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ElevationAngleOffset", + "group": "Third person camera configuration", + "name": "SetElevationAngleOffset", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ElevationAngleOffset", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the targeted camera rotation angle of the object. When this angle is set, the camera follow this value instead of the object angle.", + "fullName": "Targeted rotation angle", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "TargetedRotationAngle", + "sentence": "the targeted camera rotation angle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "TargetedRotationAngle" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "TargetedRotationAngle", + "group": "Third person camera configuration", + "name": "SetTargetedRotationAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedRotationAngle", + "=", + "Value" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "False", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "MoveCameraCloser" + }, + { + "functionName": "DoMoveCameraCloser" + }, + { + "functionName": "Slerp" + }, + { + "functionName": "MoveToObject" + }, + { + "functionName": "JumpToTargetedRotation" + }, + { + "functionName": "RotationAngle" + }, + { + "functionName": "SetCameraRotation" + }, + { + "folderName": "Third person camera configuration", + "children": [ + { + "functionName": "RotationHalfwayDuration" + }, + { + "functionName": "SetRotationHalfwayDuration" + }, + { + "functionName": "ElevationHalfwayDuration" + }, + { + "functionName": "SetElevationHalfwayDuration" + }, + { + "functionName": "TranslationZHalfwayDuration" + }, + { + "functionName": "SetTranslationZHalfwayDuration" + }, + { + "functionName": "FollowFreeAreaZMax" + }, + { + "functionName": "SetFollowFreeAreaZMax" + }, + { + "functionName": "FollowFreeAreaZMin" + }, + { + "functionName": "SetFollowFreeAreaZMin" + }, + { + "functionName": "Distance" + }, + { + "functionName": "SetDistance" + }, + { + "functionName": "OffsetX" + }, + { + "functionName": "SetOffsetX" + }, + { + "functionName": "OffsetY" + }, + { + "functionName": "SetOffsetY" + }, + { + "functionName": "OffsetZ" + }, + { + "functionName": "SetOffsetZ" + }, + { + "functionName": "RotationAngleOffset" + }, + { + "functionName": "SetRotationAngleOffset" + }, + { + "functionName": "ElevationAngleOffset" + }, + { + "functionName": "SetElevationAngleOffset" + }, + { + "functionName": "TargetedRotationAngle" + }, + { + "functionName": "SetTargetedRotationAngle" + } + ] + }, + { + "folderName": "Private", + "children": [ + { + "functionName": "FreeAreaZMin" + }, + { + "functionName": "FreeAreaZMax" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D capability", + "extraInformation": [ + "Scene3D::Base3DBehavior" + ], + "choices": [], + "name": "Object3D" + }, + { + "value": "0.125", + "type": "Number", + "unit": "Second", + "label": "Halfway time for rotation", + "group": "Catch-up speed", + "quickCustomizationVisibility": "hidden", + "name": "RotationHalfwayDuration" + }, + { + "value": "0", + "type": "Number", + "unit": "Second", + "label": "Halfway time for elevation rotation", + "description": "Only used by Z and ZY rotation modes", + "group": "Catch-up speed", + "quickCustomizationVisibility": "hidden", + "name": "ElevationHalfwayDuration" + }, + { + "value": "0.125", + "type": "Number", + "unit": "Second", + "label": "Halfway time on Z axis", + "description": "Only used by Z and ZY rotation modes", + "group": "Catch-up speed", + "quickCustomizationVisibility": "hidden", + "name": "TranslationZHalfwayDuration" + }, + { + "value": "500", + "type": "Number", + "unit": "Pixel", + "label": "Camera distance", + "group": "Position", + "name": "Distance" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Lateral distance offset", + "group": "Position", + "name": "OffsetX" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Ahead distance offset", + "group": "Position", + "name": "OffsetY" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Z offset", + "group": "Position", + "name": "OffsetZ" + }, + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "Rotation angle offset", + "group": "Position", + "advanced": true, + "name": "RotationAngleOffset" + }, + { + "value": "20", + "type": "Number", + "unit": "DegreeAngle", + "label": "Elevation angle offset", + "group": "Position", + "name": "ElevationAngleOffset" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Follow free area top border on Z axis", + "group": "Position", + "advanced": true, + "quickCustomizationVisibility": "hidden", + "name": "FollowFreeAreaZMax" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Follow free area bottom border on Z axis", + "group": "Position", + "advanced": true, + "quickCustomizationVisibility": "hidden", + "name": "FollowFreeAreaZMin" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "RotationLogSpeed" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "ElevationLogSpeed" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "TranslationZLogSpeed" + }, + { + "value": "", + "type": "Boolean", + "label": "", + "hidden": true, + "name": "IsCalledManually" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "", + "hidden": true, + "name": "CameraZ" + }, + { + "value": "true", + "type": "Boolean", + "label": "", + "hidden": true, + "name": "HasJustBeenCreated" + }, + { + "value": "true", + "type": "Boolean", + "label": "Automatically rotate the camera with the object", + "quickCustomizationVisibility": "hidden", + "name": "IsRotatingWithObject" + }, + { + "value": "false", + "type": "Boolean", + "label": "Automatically rotate the camera with the object (elevation)", + "description": "Best left unchecked, use the rotation mode instead", + "deprecated": true, + "quickCustomizationVisibility": "hidden", + "name": "IsElevatingWithObject" + }, + { + "value": "Z", + "type": "Choice", + "label": "Rotation mode", + "choices": [ + { + "label": "Horizontal rotation (good for characters)", + "value": "Z" + }, + { + "label": "Vertical and horizontal rotations (good for cars)", + "value": "ZY" + }, + { + "label": "All rotations (good for spaceships)", + "value": "ZYX" + } + ], + "name": "RotationMode" + }, + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "Targeted camera rotation angle", + "hidden": true, + "name": "TargetedRotationAngle" + }, + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "Targeted camera rotation angle", + "hidden": true, + "name": "TargetedElevationAngle" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Forward X", + "hidden": true, + "name": "ForwardX" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Forward Y", + "hidden": true, + "name": "ForwardY" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Forward Z", + "hidden": true, + "name": "ForwardZ" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Object3D" + }, + { + "folderName": "Catch-up speed", + "children": [ + { + "propertyName": "RotationHalfwayDuration" + }, + { + "propertyName": "ElevationHalfwayDuration" + }, + { + "propertyName": "TranslationZHalfwayDuration" + } + ] + }, + { + "folderName": "Position", + "children": [ + { + "propertyName": "Distance" + }, + { + "propertyName": "OffsetX" + }, + { + "propertyName": "OffsetY" + }, + { + "propertyName": "OffsetZ" + }, + { + "propertyName": "RotationAngleOffset" + }, + { + "propertyName": "ElevationAngleOffset" + }, + { + "propertyName": "FollowFreeAreaZMax" + }, + { + "propertyName": "FollowFreeAreaZMin" + } + ] + }, + { + "propertyName": "RotationLogSpeed" + }, + { + "propertyName": "ElevationLogSpeed" + }, + { + "propertyName": "TranslationZLogSpeed" + }, + { + "propertyName": "IsCalledManually" + }, + { + "propertyName": "CameraZ" + }, + { + "propertyName": "HasJustBeenCreated" + }, + { + "propertyName": "IsRotatingWithObject" + }, + { + "propertyName": "IsElevatingWithObject" + }, + { + "propertyName": "RotationMode" + }, + { + "propertyName": "TargetedRotationAngle" + }, + { + "propertyName": "TargetedElevationAngle" + }, + { + "propertyName": "ForwardX" + }, + { + "propertyName": "ForwardY" + }, + { + "propertyName": "ForwardZ" + } + ] + } + } + ], + "eventsBasedObjects": [] + }, + { + "author": "", + "category": "General", + "extensionNamespace": "", + "fullName": "3D collision", + "gdevelopVersion": ">=5.5.222", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXZlY3Rvci1pbnRlcnNlY3Rpb24iIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMy4xNCwxQTIuMTQsMi4xNCAwIDAsMCAxLDMuMTRWNUgzVjNINVYxSDMuMTRNNywxVjNIMTBWMUg3TTEyLDFWM0gxNFY1SDE2VjMuMTRDMTYsMS45NiAxNS4wNCwxIDEzLjg2LDFIMTJNMSw3VjEwSDNWN0gxTTksN0M3Ljg5LDcgNyw3Ljg5IDcsOUM3LDExLjMzIDcsMTYgNywxNkM3LDE2IDExLjU3LDE2IDEzLjg2LDE2QTIuMTQsMi4xNCAwIDAsMCAxNiwxMy44NkMxNiwxMS41NyAxNiw3IDE2LDdDMTYsNyAxMS4zMyw3IDksN00xOCw3VjlIMjBWMTFIMjJWOUMyMiw3Ljg5IDIxLjExLDcgMjAsN0gxOE05LDlIMTRWMTRIOVY5TTEsMTJWMTMuODZDMSwxNS4wNCAxLjk2LDE2IDMuMTQsMTZINVYxNEgzVjEySDFNMjAsMTNWMTZIMjJWMTNIMjBNNywxOFYyMEM3LDIxLjExIDcuODksMjIgOSwyMkgxMVYyMEg5VjE4SDdNMjAsMThWMjBIMThWMjJIMjBDMjEuMTEsMjIgMjIsMjEuMTEgMjIsMjBWMThIMjBNMTMsMjBWMjJIMTZWMjBIMTNaIiAvPjwvc3ZnPg==", + "name": "Collision3D", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/7a5696a515bf40813692e118147568392a854f65f5b50750c9b9aaa967aba7df_vector-intersection.svg", + "shortDescription": "Check collision and distance between 3D objects.", + "version": "0.2.1", + "description": "Check collision and distance between 3D objects and separate them from each other.", + "origin": { + "identifier": "Collision3D", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d", + "collision" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2", + "PEzPAaWHgYgk5UwIPppsL6f2ugp2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "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._collision3DExtension) {", + " return;", + "}", + "", + "/**", + " * @param {gdjs.RuntimeObject} object", + " * @param {boolean} shouldUseCustomCenter", + " * @return {number}", + " */", + "const getCenterX = (object, shouldUseCustomCenter) =>", + " shouldUseCustomCenter ?", + " object.getCenterXInScene() :", + " object.getDrawableX() + object.getWidth() / 2", + "", + "/**", + " * @param {gdjs.RuntimeObject} object", + " * @param {boolean} shouldUseCustomCenter", + " * @return {number}", + " */", + "const getCenterY = (object, shouldUseCustomCenter) =>", + " shouldUseCustomCenter ?", + " object.getCenterYInScene() :", + " object.getDrawableY() + object.getHeight() / 2", + "", + "/**", + " * @param {gdjs.RuntimeObject3D} object", + " * @param {boolean} shouldUseCustomCenter", + " * @return {number}", + " */", + "const getCenterZ = (object, shouldUseCustomCenter) =>", + " !object.getCenterZInScene ? 0 :", + " shouldUseCustomCenter ?", + " object.getCenterZInScene() :", + " object.getDrawableZ() + object.getDepth() / 2", + "", + "/**", + " * @param {gdjs.RuntimeObject} object", + " * @param {float} x", + " * @param {float} y", + " * @param {float} z", + " * @param {boolean} shouldUseCustomCenter", + " * @return {number}", + " */", + "const getSqDistanceToPosition = (object, x, y, z, shouldUseCustomCenter = true) => {", + " const deltaX = getCenterX(object, shouldUseCustomCenter) - x;", + " const deltaY = getCenterY(object, shouldUseCustomCenter) - y;", + " const deltaZ = getCenterZ(object, shouldUseCustomCenter) - z;", + " return deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;", + "};", + "", + "/**", + " * @param {gdjs.RuntimeObject} object", + " * @param {float} x", + " * @param {float} y", + " * @param {float} z", + " * @return {number}", + " */", + "const getDistanceToPosition = (object, x, y, z) => Math.sqrt(getSqDistanceToPosition(object, x, y, z));", + "", + "/**", + " * @param {gdjs.RuntimeObject} object", + " * @param {gdjs.RuntimeObject} otherObject", + " * @param {boolean} shouldUseCustomCenter", + " * @return {number}", + " */", + "const getSqDistanceToObject = (object, otherObject, shouldUseCustomCenter = true) => {", + " return getSqDistanceToPosition(", + " object,", + " getCenterX(otherObject, shouldUseCustomCenter),", + " getCenterY(otherObject, shouldUseCustomCenter),", + " getCenterZ(otherObject, shouldUseCustomCenter)", + " );", + "};", + "", + "/**", + " * @param {gdjs.RuntimeObject} object", + " * @param {gdjs.RuntimeObject} otherObject", + " * @return {number}", + " */", + "const getDistanceToObject = (object, otherObject) => Math.sqrt(getSqDistanceToObject(object, otherObject));", + "", + "/**", + " * @param {gdjs.RuntimeObject} object", + " * @param {gdjs.RuntimeObject} otherObject", + " * @param {{distanceSq: number, shouldUseCustomCenter: boolean}} distanceSettings", + " * @return {boolean}", + " */", + "const areObjectsWithinDistance = (object, otherObject, distanceSettings) => {", + " return getSqDistanceToObject(", + " object, otherObject, distanceSettings.shouldUseCustomCenter) <= distanceSettings.distanceSq;", + "};", + "", + "const distanceSettings = {distanceSq: 0, shouldUseCustomCenter: true};", + "", + "/**", + " * @param {Hashtable} objectsLists1", + " * @param {Hashtable} objectsLists2", + " * @param {number} distance", + " * @param {boolean} shouldUseCustomCenter", + " * @param {boolean} inverted", + " * @return {boolean}", + " */", + "const pickObjectsWithinDistance = (objectsLists1, objectsLists2, distance, shouldUseCustomCenter, inverted) => {", + " distanceSettings.distanceSq = distance * distance;", + " distanceSettings.shouldUseCustomCenter = shouldUseCustomCenter;", + " return gdjs.evtTools.object.twoListsTest(", + " areObjectsWithinDistance,", + " objectsLists1,", + " objectsLists2,", + " inverted,", + " distanceSettings", + " );", + "}", + "", + "/**", + " * @param {Hashtable} objectsLists", + " * @param {number} x", + " * @param {number} y", + " * @param {number} z", + " * @param {boolean} inverted", + " * @return {boolean}", + " */", + "const pickNearestObject = function (objectsLists, x, y, z, inverted) {", + " let bestObject = null;", + " let best = 0;", + " let first = true;", + " const lists = gdjs.staticArray(gdjs._collision3DExtension.pickNearestObject);", + " objectsLists.values(lists);", + " for (let i = 0, len = lists.length; i < len; ++i) {", + " const list = lists[i];", + " for (let j = 0; j < list.length; ++j) {", + " const object = list[j];", + " const distance = getSqDistanceToPosition(object, x, y, z);", + " // @ts-ignore", + " if (first || (distance < best) ^ inverted) {", + " best = distance;", + " bestObject = object;", + " }", + " first = false;", + " }", + " }", + " if (bestObject) {", + " gdjs.evtTools.object.pickOnly(objectsLists, bestObject);", + " }", + " return !!bestObject;", + "}", + "", + "/**", + " * @param {gdjs.RuntimeObject} object", + " * @return {float}", + " */", + "const getObjectMinZ = (object) =>", + " object.getDrawableZ ? object.getDrawableZ() : 0", + "", + "/**", + " * @param {gdjs.RuntimeObject} object", + " * @return {float}", + " */", + "const getObjectMaxZ = (object) =>", + " object.getDrawableZ ? object.getDrawableZ() + object.getDepth() : 0;", + "", + "/**", + " * @param {gdjs.RuntimeObject} object1", + " * @param {gdjs.RuntimeObject} object2", + " * @param {boolean} ignoreTouchingEdges If true, then edges that are touching each other, without the hitbox polygons actually overlapping, won't be considered in collision.", + " * @return {boolean}", + " */", + "const areObjectsInCollision = (", + " object1,", + " object2,", + " ignoreTouchingEdges,", + ") => {", + " const object1MinZ = getObjectMinZ(object1);", + " const object1MaxZ = getObjectMaxZ(object1);", + " const object2MinZ = getObjectMinZ(object2);", + " const object2MaxZ = getObjectMaxZ(object2);", + "", + " const canObjectsOverlapOnZ = ignoreTouchingEdges ?", + " (object1MinZ < object2MaxZ && object1MaxZ > object2MinZ) :", + " (object1MinZ <= object2MaxZ && object1MaxZ >= object2MinZ);", + "", + " return canObjectsOverlapOnZ &&", + " gdjs.RuntimeObject.collisionTest(", + " object1,", + " object2,", + " ignoreTouchingEdges", + " );", + "}", + "", + "/**", + " * @param {Hashtable} objectsLists1", + " * @param {Hashtable} objectsLists2", + " * @param {boolean} inverted", + " * @return {boolean}", + " */", + "const pickObjectsInCollision = (", + " objectsLists1,", + " objectsLists2,", + " inverted,", + ") => {", + " return gdjs.evtTools.object.twoListsTest(", + " areObjectsInCollision,", + " objectsLists1,", + " objectsLists2,", + " inverted,", + " false", + " );", + "}", + "", + "/**", + " * @param {gdjs.RuntimeObject} movingObject", + " * @param {gdjs.RuntimeObject} object", + " * @return {float}", + " */", + "const getSeparationDeltaZ = (", + " movingObject,", + " object,", + ") => {", + " const movingObjectMinZ = getObjectMinZ(movingObject);", + " const movingObjectMaxZ = getObjectMaxZ(movingObject);", + " const objectMinZ = getObjectMinZ(object);", + " const objectMaxZ = getObjectMaxZ(object);", + "", + " const downwardDeltaZ = Math.min(0, objectMinZ - movingObjectMaxZ);", + " const upwardDeltaZ = Math.max(0, objectMaxZ - movingObjectMinZ);", + "", + " return -downwardDeltaZ < upwardDeltaZ ? downwardDeltaZ : upwardDeltaZ", + "}", + "", + "// This function is an exact copy from GDJS/Runtime/runtimeobject.ts", + "/**", + " * Move the object using the results from collisionTest call.", + " * This moves the object according to the direction of the longest vector,", + " * and projects the others on the orthogonal vector.", + " *", + " * See {@link RuntimeObject.separateFromObjects}", + " *", + " * @param {gdjs.RuntimeObject} object The object to move.", + " * @param {float[]} moveXArray The X coordinates of the vectors to move the object.", + " * @param {float[]} moveYArray The Y coordinates of the vectors to move the object.", + " * @return {boolean} true if the object was moved.", + " */", + "const moveFollowingSeparatingVectors = (", + " object,", + " moveXArray,", + " moveYArray", + ") => {", + " if (moveXArray.length === 0) {", + " moveXArray.length = 0;", + " moveYArray.length = 0;", + " return false;", + " }", + " if (moveXArray.length === 1) {", + " // Move according to the results returned by the collision algorithm.", + " object.setPosition(", + " object.getX() + moveXArray[0],", + " object.getY() + moveYArray[0]", + " );", + " moveXArray.length = 0;", + " moveYArray.length = 0;", + " return true;", + " }", + "", + " // Find the longest vector", + " let squaredDistanceMax = 0;", + " let distanceMaxIndex = 0;", + " for (let index = 0; index < moveXArray.length; index++) {", + " const moveX = moveXArray[index];", + " const moveY = moveYArray[index];", + "", + " const squaredDistance = moveX * moveX + moveY * moveY;", + " if (squaredDistance > squaredDistanceMax) {", + " squaredDistanceMax = squaredDistance;", + " distanceMaxIndex = index;", + " }", + " }", + "", + " const distanceMax = Math.sqrt(squaredDistanceMax);", + " // unit vector of the longest vector", + " const uX = moveXArray[distanceMaxIndex] / distanceMax;", + " const uY = moveYArray[distanceMaxIndex] / distanceMax;", + "", + " // normal vector of the longest vector", + " const vX = -uY;", + " const vY = uX;", + "", + " // Project other vectors on the normal", + " let scalarProductMin = 0;", + " let scalarProductMax = 0;", + " for (let index = 0; index < moveXArray.length; index++) {", + " const moveX = moveXArray[index];", + " const moveY = moveYArray[index];", + "", + " const scalarProduct = moveX * vX + moveY * vY;", + " scalarProductMin = Math.min(scalarProductMin, scalarProduct);", + " scalarProductMax = Math.max(scalarProductMax, scalarProduct);", + " }", + "", + " // Apply the longest vector", + " let deltaX = moveXArray[distanceMaxIndex];", + " let deltaY = moveYArray[distanceMaxIndex];", + "", + " // Apply the longest projected vector if they all are in the same direction", + " // Some projections could have rounding errors,", + " // they are considered negligible under a 1 for 1,000,000 ratio.", + " const scalarProductMinIsNegligible =", + " -scalarProductMin < scalarProductMax / 1048576;", + " const scalarProductMaxIsNegligible =", + " scalarProductMax < -scalarProductMin / 1048576;", + " if (scalarProductMinIsNegligible !== scalarProductMaxIsNegligible) {", + " if (scalarProductMaxIsNegligible) {", + " deltaX += scalarProductMin * vX;", + " deltaY += scalarProductMin * vY;", + " } else {", + " deltaX += scalarProductMax * vX;", + " deltaY += scalarProductMax * vY;", + " }", + " }", + " object.setPosition(object.getX() + deltaX, object.getY() + deltaY);", + " moveXArray.length = 0;", + " moveYArray.length = 0;", + " return true;", + "};", + "", + "/**", + " * Arrays and data structure that are (re)used by", + " * {@link separateFromObjects} to avoid any allocation.", + " * @type {{moveXArray: float[], moveYArray: float[], moveZArray: float[]}}", + " */", + "const separateFromObjectsStatics = {", + " moveXArray: [],", + " moveYArray: [],", + " moveZArray: [],", + "};", + "", + "/**", + " * Separate the object from others objects, using their hitboxes.", + " * @param {gdjs.RuntimeObject} object The object that moves.", + " * @param {gdjs.RuntimeObject[]} objects The objects that stay still.", + " * @return {boolean} true if the object was moved", + " */", + "const separateFromObjects = (", + " object,", + " objects", + ") => {", + " const moveXArray = separateFromObjectsStatics.moveXArray;", + " const moveYArray = separateFromObjectsStatics.moveYArray;", + " moveXArray.length = 0;", + " moveYArray.length = 0;", + " let moveZMax = 0;", + "", + " // We can assume that the moving object is not grid based,", + " // so there is no need for optimization:", + " // getHitBoxes can be called directly.", + " const hitBoxes = object.getHitBoxes();", + " /** @type {gdjs.AABB | null} */", + " let aabb = null;", + "", + " // Check if there is a collision with each object", + " for (const otherObject of objects) {", + " if (otherObject.id === object.id) {", + " continue;", + " }", + " const moveZ = getSeparationDeltaZ(object, otherObject);", + " if (moveZ === 0) {", + " continue;", + " }", + " const moveZSq = moveZ * moveZ;", + "", + " let otherHitBoxesArray = otherObject.getHitBoxes();", + " /** @type {Iterable} */", + " let otherHitBoxes = otherHitBoxesArray;", + " if (otherHitBoxesArray.length > 4) {", + " // The other object has a lot of hit boxes.", + " // Try to reduce the amount of hitboxes to check.", + " if (!aabb) {", + " aabb = object.getAABB();", + " }", + " otherHitBoxes = otherObject.getHitBoxesAround(", + " aabb.min[0],", + " aabb.min[1],", + " aabb.max[0],", + " aabb.max[1]", + " );", + " }", + " for (const hitBox of hitBoxes) {", + " for (const otherHitBox of otherHitBoxes) {", + " const result = gdjs.Polygon.collisionTest(", + " hitBox,", + " otherHitBox,", + " true", + " );", + " if (result.collision) {", + " const moveX = result.move_axis[0];", + " const moveY = result.move_axis[1];", + " if (moveZSq < moveX * moveX + moveY * moveY) {", + " if (Math.abs(moveZ) > Math.abs(moveZMax)) {", + " moveZMax = moveZ;", + " }", + " }", + " else {", + " moveXArray.push(moveX);", + " moveYArray.push(moveY);", + " }", + " }", + " }", + " }", + " }", + " const hasObjectMovedOnZ = moveZMax !== 0;", + " if (hasObjectMovedOnZ) {", + " object.setZ(object.getZ() + moveZMax);", + " }", + " const hasObjectMovedOnXY = moveFollowingSeparatingVectors(object, moveXArray, moveYArray);", + " return hasObjectMovedOnXY || hasObjectMovedOnZ;", + "}", + "", + "gdjs._collision3DExtension = {", + " getSqDistanceToPosition,", + " getDistanceToPosition,", + " getSqDistanceToObject,", + " getDistanceToObject,", + " pickObjectsWithinDistance,", + " pickNearestObject,", + " pickObjectsInCollision,", + " separateFromObjects", + "}" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onFirstSceneLoaded", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Collision3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Compare the distance between two objects in 3D.", + "fullName": "Distance between two objects in 3D", + "functionType": "Condition", + "group": "Position", + "name": "AreWithinDistance", + "sentence": "_PARAM1_ distance to _PARAM2_ is below _PARAM3_ pixels", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "// TODO If condition is inverted, only objects that have a distance greater than specified to any other object will be picked.\r", + "const inverted = false;\r", + "\r", + "eventsFunctionContext.returnValue = gdjs._collision3DExtension.pickObjectsWithinDistance(\r", + " eventsFunctionContext.getObjectsLists(\"Object\"),\r", + " eventsFunctionContext.getObjectsLists(\"OtherObject\"),\r", + " eventsFunctionContext.getArgument(\"Distance\"),\r", + " eventsFunctionContext.getArgument(\"ShouldUseCustomCenter\"),\r", + " inverted\r", + ");\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "Other object", + "name": "OtherObject", + "type": "objectList" + }, + { + "description": "Distance", + "name": "Distance", + "type": "expression" + }, + { + "defaultValue": "yes", + "description": "Use custom center", + "name": "ShouldUseCustomCenter", + "optional": true, + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Distance between two objects in 3D.", + "fullName": "Distance between two objects in 3D", + "functionType": "Expression", + "group": "Position", + "name": "Distance", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const otherObjects = eventsFunctionContext.getObjects(\"OtherObject\");\r", + "\r", + "if (objects.length > 0 && otherObjects.length > 0) {\r", + " eventsFunctionContext.returnValue = gdjs._collision3DExtension.getDistanceToObject(\r", + " objects[0],\r", + " otherObjects[0]\r", + " );\r", + "}\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "Other object", + "name": "OtherObject", + "type": "objectList" + } + ], + "objectGroups": [] + }, + { + "description": "Square distance between two objects in 3D.", + "fullName": "Square distance between two objects", + "functionType": "Expression", + "group": "Position", + "name": "SqDistance", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const otherObjects = eventsFunctionContext.getObjects(\"OtherObject\");\r", + "\r", + "if (objects.length > 0 && otherObjects.length > 0) {\r", + " eventsFunctionContext.returnValue = gdjs._collision3DExtension.getSqDistanceToObject(\r", + " objects[0],\r", + " otherObjects[0]\r", + " );\r", + "}\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "Other object", + "name": "OtherObject", + "type": "objectList" + } + ], + "objectGroups": [] + }, + { + "description": "Distance between an object and a position in 3D.", + "fullName": "Distance between an object and a position", + "functionType": "Expression", + "group": "Position", + "name": "DistanceToPosition", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "if (objects.length > 0) {\r", + " eventsFunctionContext.returnValue = gdjs._collision3DExtension.getDistanceToPosition(\r", + " objects[0],\r", + " eventsFunctionContext.getArgument(\"PositionX\"),\r", + " eventsFunctionContext.getArgument(\"PositionY\"),\r", + " eventsFunctionContext.getArgument(\"PositionZ\"),\r", + " );\r", + "}\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "X position", + "name": "PositionX", + "type": "expression" + }, + { + "description": "Y position", + "name": "PositionY", + "type": "expression" + }, + { + "description": "Z position", + "name": "PositionZ", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Square distance between an object and a position in 3D.", + "fullName": "Square distance between an object and a position", + "functionType": "Expression", + "group": "Position", + "name": "SqDistanceToPosition", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "if (objects.length > 0) {\r", + " eventsFunctionContext.returnValue = gdjs._collision3DExtension.getSqDistanceToPosition(\r", + " objects[0],\r", + " eventsFunctionContext.getArgument(\"PositionX\"),\r", + " eventsFunctionContext.getArgument(\"PositionY\"),\r", + " eventsFunctionContext.getArgument(\"PositionZ\"),\r", + " );\r", + "}\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "X position", + "name": "PositionX", + "type": "expression" + }, + { + "description": "Y position", + "name": "PositionY", + "type": "expression" + }, + { + "description": "Z position", + "name": "PositionZ", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Pick the object of this type that is nearest to the specified position in 3D.", + "fullName": "Pick nearest object in 3D", + "functionType": "Condition", + "group": "Objects", + "name": "PickNearest", + "sentence": "Pick the _PARAM1_ that is nearest to _PARAM2_ ; _PARAM3_ ; _PARAM4_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "// TODO If the condition is inverted, the object farthest from the specified position is picked instead.\r", + "const inverted = false;\r", + "\r", + "eventsFunctionContext.returnValue = gdjs._collision3DExtension.pickNearestObject(\r", + " eventsFunctionContext.getObjectsLists(\"Object\"),\r", + " eventsFunctionContext.getArgument(\"PositionX\"),\r", + " eventsFunctionContext.getArgument(\"PositionY\"),\r", + " eventsFunctionContext.getArgument(\"PositionZ\"),\r", + " inverted\r", + ");" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "X position", + "name": "PositionX", + "type": "expression" + }, + { + "description": "Y position", + "name": "PositionY", + "type": "expression" + }, + { + "description": "Z position", + "name": "PositionZ", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Check the collision between two objects using their collision masks. Object rotation around X and Y axes are ignored.", + "fullName": "Collision in 3D", + "functionType": "Condition", + "group": "Collision", + "name": "AreInCollision", + "sentence": "_PARAM1_ is in collision with _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "// TODO If the condition is inverted, the object farthest from the specified position is picked instead.\r", + "const inverted = false;\r", + "\r", + "eventsFunctionContext.returnValue = gdjs._collision3DExtension.pickObjectsInCollision(\r", + " eventsFunctionContext.getObjectsLists(\"Object\"),\r", + " eventsFunctionContext.getObjectsLists(\"OtherObject\"),\r", + " eventsFunctionContext.getArgument(\"IgnoreEdges\"),\r", + " inverted\r", + ");" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Other object", + "name": "OtherObject", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "OtherObjectCapability", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Ignore objects that are touching each other on their edges, but are not overlapping (default: no)", + "name": "IgnoreEdges", + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Move an object away from another using their collision masks. Object rotation around X and Y axes are ignored.\nBe sure to call this action on a reasonable number of objects to avoid slowing down the game.", + "fullName": "Separate objects in 3D", + "functionType": "Action", + "group": "Position", + "name": "SeparateFromObjects", + "sentence": "Move _PARAM1_ away from _PARAM3_ (only _PARAM1_ will move)", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const otherObjects = eventsFunctionContext.getObjects(\"OtherObject\");\r", + "\r", + "for (const object of objects) {\r", + " gdjs._collision3DExtension.separateFromObjects(\r", + " object,\r", + " otherObjects,\r", + " );\r", + "}" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Objects (won't move)", + "name": "OtherObject", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "OtherObject3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsBasedBehaviors": [], + "eventsBasedObjects": [] + }, + { + "author": "", + "category": "Input", + "dimension": "", + "extensionNamespace": "", + "fullName": "Multitouch joystick and buttons (sprite)", + "gdevelopVersion": ">=5.5.222", + "helpPath": "/objects/multitouch-joystick", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPGNpcmNsZSBjbGFzcz0ic3QwIiBjeD0iMTYiIGN5PSIxNiIgcj0iMTMiLz4NCjxwb2x5bGluZSBjbGFzcz0ic3QwIiBwb2ludHM9IjI4LjQsMTIgMjAsMTIgMjAsMy42ICIvPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMjAsMjguNCAyMCwyMCAyOC40LDIwICIvPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMy42LDIwIDEyLDIwIDEyLDI4LjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSIxMiwzLjYgMTIsMTIgMy42LDEyICIvPg0KPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSIxNiw2IDE2LjcsNyAxNS4zLDcgIi8+DQo8cG9seWdvbiBjbGFzcz0ic3QwIiBwb2ludHM9IjE2LDI2IDE1LjMsMjUgMTYuNywyNSAiLz4NCjxwb2x5Z29uIGNsYXNzPSJzdDAiIHBvaW50cz0iNiwxNiA3LDE1LjMgNywxNi43ICIvPg0KPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSIyNiwxNiAyNSwxNi43IDI1LDE1LjMgIi8+DQo8L3N2Zz4NCg==", + "name": "SpriteMultitouchJoystick", + "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Videogames/Videogames_controller_joystick_arrows_direction.svg", + "shortDescription": "On-screen multitouch joystick and buttons.", + "version": "1.9.2", + "description": [ + "Multitouch joysticks are objects showing a joystick on the screen, useful for mobile. They work like a physical gamepad:", + "- 4 or 8 directions", + "- Analogus pads", + "- Player selection", + "- Automatic \"mapper\" behaviors for 2D and 3D movement behaviors (platformer characters, top-down movement, 3D character etc...). Add one of these to the object with the movement behavior (i.e: the player most of the time) and the behavior will then be controlled automatically by the virtual joystick (it works by reading the multitouch joystick state and simulating controls). No need for additional events to make it work once the behaviors are set up.", + "", + "There are ready-to-use joysticks in the asset-store [multitouch joysticks pack](https://editor.gdevelop.io/?initial-dialog=asset-store&asset-pack=multitouch-joysticks-multitouch-joysticks)." + ], + "origin": { + "identifier": "SpriteMultitouchJoystick", + "name": "gdevelop-extension-store" + }, + "tags": [ + "multitouch", + "joystick", + "thumbstick", + "controller", + "touchscreen", + "twin stick", + "shooter", + "virtual", + "platformer", + "platform", + "top-down" + ], + "authorIds": [ + "gqDaZjCfevOOxBYkK6zlhtZnXCg1", + "1OgYzWp5UeVPbiWGJwI6vqfgZLC3", + "v0YRpdAnIucZFgiRCCecqVnGKno2", + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [ + { + "name": "Controllers", + "type": "array", + "children": [ + { + "type": "structure", + "children": [ + { + "name": "Buttons", + "type": "array", + "children": [ + { + "type": "structure", + "children": [ + { + "name": "State", + "type": "string", + "value": "Idle" + } + ] + } + ] + }, + { + "name": "Joystick", + "type": "structure", + "children": [] + } + ] + } + ] + } + ], + "eventsFunctions": [ + { + "description": "Check if the events are running for the editor.", + "fullName": "Editor is running", + "functionType": "Condition", + "name": "IsInGameEdition", + "private": true, + "sentence": "Events are running for the editor", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const game = runtimeScene.getGame();\r", + "eventsFunctionContext.returnValue = game.isInGameEdition && game.isInGameEdition();" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "fullName": "Accelerated speed", + "functionType": "Expression", + "name": "AcceleratedSpeed", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "CurrentSpeed" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TargetedSpeed", + "<", + "0" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Reduce the speed to match the stick force." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + "<", + "TargetedSpeed" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "min(TargetedSpeed, CurrentSpeed + Acceleration * TimeDelta())" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + ">", + "TargetedSpeed" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + "<", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "-", + "Acceleration * TimeDelta()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Turn back at least as fast as it would stop." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + ">=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "max(TargetedSpeed, CurrentSpeed - max(Acceleration , Deceleration) * TimeDelta())" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TargetedSpeed", + ">", + "0" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Reduce the speed to match the stick force." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + ">", + "TargetedSpeed" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "max(TargetedSpeed, CurrentSpeed - Acceleration * TimeDelta())" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + "<", + "TargetedSpeed" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "+", + "Acceleration * TimeDelta()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Turn back at least as fast as it would stop." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + "<=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "min(TargetedSpeed, CurrentSpeed + max(Acceleration , Deceleration) * TimeDelta())" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TargetedSpeed", + "=", + "0" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + "<", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "min(0, CurrentSpeed + Acceleration * TimeDelta())" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "max(0, CurrentSpeed - Acceleration * TimeDelta())" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "clamp(AcceleratedSpeed, -SpeedMax, SpeedMax)" + ] + } + ] + } + ], + "variables": [ + { + "name": "AcceleratedSpeed", + "type": "number", + "value": 0 + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Current speed", + "name": "CurrentSpeed", + "type": "expression" + }, + { + "description": "Targeted speed", + "name": "TargetedSpeed", + "type": "expression" + }, + { + "description": "Max speed", + "name": "SpeedMax", + "type": "expression" + }, + { + "description": "Acceleration", + "name": "Acceleration", + "type": "expression" + }, + { + "description": "Deceleration", + "name": "Deceleration", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a button was just pressed on a multitouch controller.", + "fullName": "Multitouch controller button just pressed", + "functionType": "Condition", + "name": "IsButtonJustPressed", + "sentence": "Button _PARAM2_ of multitouch controller _PARAM1_ was just pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Buttons[Button].State", + "=", + "\"JustPressed\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Button name", + "name": "Button", + "supplementaryInformation": "[\"A\",\"CROSS\",\"B\",\"CIRCLE\",\"X\",\"SQUARE\",\"Y\",\"TRIANGLE\",\"LB\",\"L1\",\"RB\",\"R1\",\"LT\",\"L2\",\"RT\",\"R2\",\"UP\",\"DOWN\",\"LEFT\",\"RIGHT\",\"BACK\",\"SHARE\",\"START\",\"OPTIONS\",\"CLICK_STICK_LEFT\",\"CLICK_STICK_RIGHT\",\"PS_BUTTON\",\"CLICK_TOUCHPAD\"]", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a button is pressed on a multitouch controller.", + "fullName": "Multitouch controller button pressed", + "functionType": "Condition", + "name": "IsButtonPressed", + "sentence": "Button _PARAM2_ of multitouch controller _PARAM1_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Buttons[Button].State", + "=", + "\"Pressed\"" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Buttons[Button].State", + "=", + "\"JustPressed\"" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Button name", + "name": "Button", + "supplementaryInformation": "[\"A\",\"CROSS\",\"B\",\"CIRCLE\",\"X\",\"SQUARE\",\"Y\",\"TRIANGLE\",\"LB\",\"L1\",\"RB\",\"R1\",\"LT\",\"L2\",\"RT\",\"R2\",\"UP\",\"DOWN\",\"LEFT\",\"RIGHT\",\"BACK\",\"SHARE\",\"START\",\"OPTIONS\",\"CLICK_STICK_LEFT\",\"CLICK_STICK_RIGHT\",\"PS_BUTTON\",\"CLICK_TOUCHPAD\"]", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a button is released on a multitouch controller.", + "fullName": "Multitouch controller button released", + "functionType": "Condition", + "name": "IsButtonReleased", + "sentence": "Button _PARAM2_ of multitouch controller _PARAM1_ is released", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Buttons[Button].State", + "=", + "\"Released\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Button name", + "name": "Button", + "supplementaryInformation": "[\"A\",\"CROSS\",\"B\",\"CIRCLE\",\"X\",\"SQUARE\",\"Y\",\"TRIANGLE\",\"LB\",\"L1\",\"RB\",\"R1\",\"LT\",\"L2\",\"RT\",\"R2\",\"UP\",\"DOWN\",\"LEFT\",\"RIGHT\",\"BACK\",\"SHARE\",\"START\",\"OPTIONS\",\"CLICK_STICK_LEFT\",\"CLICK_STICK_RIGHT\",\"PS_BUTTON\",\"CLICK_TOUCHPAD\"]", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Change a button state for a multitouch controller.", + "fullName": "Button state", + "functionType": "Action", + "name": "SetButtonState", + "private": true, + "sentence": "Mark _PARAM2_ button as _PARAM3_ for multitouch controller _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Buttons[Button].State", + "=", + "ButtonState" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Button name", + "name": "Button", + "type": "string" + }, + { + "description": "Button state", + "name": "ButtonState", + "supplementaryInformation": "[\"Idle\",\"Pressed\",\"JustPressed\",\"Released\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Change the dead zone radius of a joystick. The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved).", + "fullName": "Dead zone radius", + "functionType": "Action", + "name": "SetDeadZone", + "private": true, + "sentence": "Change the dead zone of multitouch joystick _PARAM2_ of multitouch controller _PARAM1_ to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].DeadZone", + "=", + "DeadZoneRadius" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "type": "string" + }, + { + "description": "Dead zone radius", + "name": "DeadZoneRadius", + "supplementaryInformation": "[]", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the dead zone radius of a joystick. The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved).", + "fullName": "Dead zone radius", + "functionType": "Expression", + "name": "DeadZone", + "private": true, + "sentence": "Change multitouch joystick _PARAM2_ of multitouch controller _PARAM1_ dead zone to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].DeadZone" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "the direction index (right = 0, bottom = 1, left = 2, top = 3) for an angle (in degrees).", + "fullName": "Angle to 4-way index", + "functionType": "ExpressionAndCondition", + "name": "AngleTo4Way", + "private": true, + "sentence": "The angle _PARAM1_ 4-way index", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "mod(round(Angle * 4 / 360), 4)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Angle", + "name": "Angle", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the direction index (right = 0, bottom-right = 1... top-right = 7) for an angle (in degrees).", + "fullName": "Angle to 8-way index", + "functionType": "ExpressionAndCondition", + "name": "AngleTo8Way", + "private": true, + "sentence": "The angle _PARAM1_ 8-way index", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "mod(round(Angle * 8 / 360), 8)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Angle", + "name": "Angle", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Check if angle is in a given direction.", + "fullName": "Angle 4-way direction", + "functionType": "Condition", + "name": "IsAngleIn4WayDirection", + "private": true, + "sentence": "The angle _PARAM1_ is the 4-way direction _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Right\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo4Way" + }, + "parameters": [ + "", + "=", + "0", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Down\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo4Way" + }, + "parameters": [ + "", + "=", + "1", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Left\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo4Way" + }, + "parameters": [ + "", + "=", + "2", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Up\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo4Way" + }, + "parameters": [ + "", + "=", + "3", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Angle", + "name": "Angle", + "type": "expression" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if angle is in a given direction.", + "fullName": "Angle 8-way direction", + "functionType": "Condition", + "name": "IsAngleIn8WayDirection", + "private": true, + "sentence": "The angle _PARAM1_ is the 8-way direction _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Right\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "0", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"DownRight\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "1", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Down\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "2", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"DownLeft\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "3", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Left\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "4", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"UpLeft\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "5", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Up\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "6", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"UpRight\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "7", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Angle", + "name": "Angle", + "type": "expression" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\",\"UpLeft\",\"UpRight\",\"DownLeft\",\"DownRight\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (4-way)", + "functionType": "Condition", + "name": "IsDirectionPushed4Way", + "sentence": "Joystick _PARAM2_ of multitouch controller _PARAM1_ is pushed in direction _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Make sure the joystick has moved from center" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::JoystickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "JoystickIdentifier", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::IsAngleIn4WayDirection" + }, + "parameters": [ + "", + "SpriteMultitouchJoystick::JoystickAngle(ControllerIdentifier, JoystickIdentifier)", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (8-way)", + "functionType": "Condition", + "name": "IsDirectionPushed8Way", + "sentence": "Joystick _PARAM2_ of multitouch controller _PARAM1_ is pushed in direction _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Make sure the joystick has moved from center" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::JoystickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "JoystickIdentifier", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::IsAngleIn8WayDirection" + }, + "parameters": [ + "", + "SpriteMultitouchJoystick::JoystickAngle(ControllerIdentifier, JoystickIdentifier)", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\",\"UpLeft\",\"UpRight\",\"DownLeft\",\"DownRight\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "the percentage the thumb has been pulled away from the joystick center (Range: 0 to 1).", + "fullName": "Joystick force (deprecated)", + "functionType": "ExpressionAndCondition", + "name": "JoystickForce", + "private": true, + "sentence": "Joystick _PARAM2_ of multitouch controller _PARAM1_ force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + }, + { + "description": "", + "name": "Coucou", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the force of multitouch contoller stick (from 0 to 1).", + "fullName": "Stick force", + "functionType": "ExpressionAndCondition", + "name": "StickForce", + "sentence": "multitouch controller _PARAM1_ _PARAM2_ stick force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "max(0, Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].Force - SpriteMultitouchJoystick::DeadZone(ControllerIdentifier, JoystickIdentifier)) / (1 - SpriteMultitouchJoystick::DeadZone(ControllerIdentifier, JoystickIdentifier))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Stick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Change the percentage the thumb has been pulled away from the joystick center (Range: 0 to 1).", + "fullName": "Joystick force", + "functionType": "Action", + "name": "SetJoystickForce", + "private": true, + "sentence": "Change the force of the joystick _PARAM2_ of multitouch controller _PARAM1_ to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].Force", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "type": "string" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle the joystick is pointing towards (Range: -180 to 180).", + "fullName": "Joystick angle (deprecated)", + "functionType": "Expression", + "name": "JoystickAngle", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle the multitouch controller stick is pointing towards (Range: -180 to 180).", + "fullName": "Stick angle", + "functionType": "Expression", + "name": "StickAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].Angle" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Change the angle the joystick is pointing towards (Range: -180 to 180).", + "fullName": "Joystick angle", + "functionType": "Action", + "name": "SetJoystickAngle", + "private": true, + "sentence": "Change the angle of the joystick _PARAM2_ of multitouch controller _PARAM1_ to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].Angle", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "type": "string" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the multitouch contoller stick force on X axis (from -1 at the left to 1 at the right).", + "fullName": "Stick X force", + "functionType": "Expression", + "name": "StickForceX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "XFromAngleAndDistance(SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier), SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Return the multitouch contoller stick force on Y axis (from -1 at the top to 1 at the bottom).", + "fullName": "Stick Y force", + "functionType": "Expression", + "name": "StickForceY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "YFromAngleAndDistance(SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier), SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a new touch has started on the right or left side of the screen.", + "fullName": "New touch on a screen side", + "functionType": "Condition", + "group": "Multitouch Joystick", + "name": "HasTouchStartedOnScreenSide", + "sentence": "A new touch has started on the _PARAM2_ side of the screen on _PARAM1_'s layer", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Side", + "=", + "\"Left\"" + ] + }, + { + "type": { + "value": "TouchX" + }, + "parameters": [ + "", + "StartedTouchOrMouseId(0)", + "<", + "CameraCenterX(Object.Layer())", + "Object.Layer()", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Side", + "=", + "\"Right\"" + ] + }, + { + "type": { + "value": "TouchX" + }, + "parameters": [ + "", + "StartedTouchOrMouseId(0)", + ">=", + "CameraCenterX(Object.Layer())", + "Object.Layer()", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch joystick", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "objectList" + }, + { + "description": "Screen side", + "name": "Side", + "supplementaryInformation": "[\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "IsInGameEdition" + }, + { + "functionName": "AcceleratedSpeed" + }, + { + "functionName": "IsButtonJustPressed" + }, + { + "functionName": "IsButtonPressed" + }, + { + "functionName": "IsButtonReleased" + }, + { + "functionName": "SetButtonState" + }, + { + "functionName": "SetDeadZone" + }, + { + "functionName": "DeadZone" + }, + { + "functionName": "AngleTo4Way" + }, + { + "functionName": "AngleTo8Way" + }, + { + "functionName": "IsAngleIn4WayDirection" + }, + { + "functionName": "IsAngleIn8WayDirection" + }, + { + "functionName": "IsDirectionPushed4Way" + }, + { + "functionName": "IsDirectionPushed8Way" + }, + { + "functionName": "JoystickForce" + }, + { + "functionName": "StickForce" + }, + { + "functionName": "SetJoystickForce" + }, + { + "functionName": "JoystickAngle" + }, + { + "functionName": "StickAngle" + }, + { + "functionName": "SetJoystickAngle" + }, + { + "functionName": "StickForceX" + }, + { + "functionName": "StickForceY" + }, + { + "folderName": "Multitouch Joystick", + "children": [ + { + "functionName": "HasTouchStartedOnScreenSide" + } + ] + } + ] + }, + "eventsBasedBehaviors": [ + { + "description": "Joystick that can be controlled by interacting with a touchscreen.", + "fullName": "Multitouch Joystick", + "helpPath": "", + "iconUrl": "", + "name": "MultitouchJoystick", + "objectType": "", + "previewIconUrl": "", + "private": true, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::SetDeadZone" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "DeadZoneRadius", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::Reset" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDeActivate", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::Reset" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::IsInGameEdition" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasGameJustResumed" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::Reset" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Manage touches", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchIndex", + "=", + "0" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "StartedTouchOrMouseCount()", + "conditions": [], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "0" + ] + }, + { + "type": { + "value": "CollisionPoint" + }, + "parameters": [ + "Object", + "TouchX(StartedTouchOrMouseId(TouchIndex), Object.Layer(), 0)", + "TouchY(StartedTouchOrMouseId(TouchIndex), Object.Layer(), 0)" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "StartedTouchOrMouseId(TouchIndex)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchIndex", + "+", + "1" + ] + } + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Move thumb back to center when not being pressed (acts like a spring on a real controller)" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasTouchEnded" + }, + "parameters": [ + "", + "TouchId" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::Reset" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Update joystick position", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetJoystickAngle" + }, + "parameters": [ + "Object", + "Behavior", + "AngleBetweenPositions(Object.CenterX(), Object.CenterY(), TouchX(TouchId, Object.Layer(), 0), TouchY(TouchId, Object.Layer(), 0))", + "AngleBetweenPositions(Object.CenterX(), Object.CenterY(), TouchX(TouchId, Object.Layer(), 0), TouchY(TouchId, Object.Layer(), 0))" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetJoystickForce" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "clamp(2 * DistanceBetweenPositions(Object.CenterX(), Object.CenterY(), TouchX(TouchId, Object.Layer(), 0), TouchY(TouchId, Object.Layer(), 0)) / Object.Width(), 0, 1)", + "" + ] + } + ] + } + ], + "parameters": [] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the joystick force (from 0 to 1).", + "fullName": "Joystick force", + "functionType": "ExpressionAndCondition", + "name": "JoystickForce", + "sentence": "the joystick force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "max(0, JoystickForce - DeadZoneRadius) / (1 - DeadZoneRadius)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "JoystickForce", + "name": "SetJoystickForce", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "JoystickForce", + "=", + "Value" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SetJoystickForce" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "JoystickForce", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + }, + { + "description": "", + "name": "Parameter", + "type": "objectList" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle the joystick is pointing towards (Range: -180 to 180).", + "fullName": "Joystick angle", + "functionType": "Expression", + "name": "JoystickAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "JoystickAngle" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Change the angle the joystick is pointing towards (Range: -180 to 180).", + "fullName": "Joystick angle", + "functionType": "Action", + "name": "SetJoystickAngle", + "private": true, + "sentence": "Change the joystick angle of _PARAM0_ to _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "JoystickAngle", + "=", + "Value" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SetJoystickAngle" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "JoystickAngle", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + }, + { + "description": "Angle", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the stick force on X axis (from -1 at the left to 1 at the right).", + "fullName": "Stick X force", + "functionType": "Expression", + "name": "StickForceX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Behavior::JoystickForce() * cos(ToRad(Object.Behavior::JoystickAngle()))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Return the stick force on Y axis (from -1 at the top to 1 at the bottom).", + "fullName": "Stick Y force", + "functionType": "Expression", + "name": "StickForceY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Behavior::JoystickForce() * sin(ToRad(Object.Behavior::JoystickAngle()))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (4-way movement)", + "functionType": "Condition", + "name": "IsDirectionPushed4Way", + "sentence": "_PARAM0_ is pushed in direction _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::JoystickForce" + }, + "parameters": [ + "Object", + "Behavior", + ">", + "0", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::IsAngleIn4WayDirection" + }, + "parameters": [ + "", + "JoystickAngle", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (8-way movement)", + "functionType": "Condition", + "name": "IsDirectionPushed8Way", + "sentence": "_PARAM0_ is pushed in direction _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::JoystickForce" + }, + "parameters": [ + "Object", + "Behavior", + ">", + "0", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::IsAngleIn8WayDirection" + }, + "parameters": [ + "", + "JoystickAngle", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\",\"UpLeft\",\"UpRight\",\"DownLeft\",\"DownRight\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a joystick is pressed.", + "fullName": "Joystick pressed", + "functionType": "Condition", + "name": "IsPressed", + "sentence": "Joystick _PARAM0_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Reset the joystick values (except for angle, which stays the same)", + "fullName": "Reset", + "functionType": "Action", + "name": "Reset", + "private": true, + "sentence": "Reset the joystick of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetJoystickForce" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "0", + "" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "0" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the multitouch controller identifier.", + "fullName": "Multitouch controller identifier", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "ControllerIdentifier", + "sentence": "the multitouch controller identifier", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ControllerIdentifier" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ControllerIdentifier", + "group": "Multitouch Joystick configuration", + "name": "SetControllerIdentifier", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ControllerIdentifier", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the joystick name.", + "fullName": "Joystick name", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "JoystickIdentifier", + "sentence": "the joystick name", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "JoystickIdentifier" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "JoystickIdentifier", + "group": "Multitouch Joystick configuration", + "name": "SetJoystickIdentifier", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "JoystickIdentifier", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the dead zone radius (range: 0 to 1) of the joystick. The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved).", + "fullName": "Dead zone radius", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "DeadZoneRadius", + "sentence": "the dead zone radius", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "DeadZoneRadius" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "DeadZoneRadius", + "group": "Multitouch Joystick configuration", + "name": "SetDeadZoneRadius", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DeadZoneRadius", + "=", + "Value" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SetDeadZone" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "Value", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Force the joystick into the pressing state.", + "fullName": "Force start pressing", + "functionType": "Action", + "name": "ForceStartPressing", + "sentence": "Force start pressing _PARAM0_ with touch identifier: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + }, + { + "description": "Touch identifier", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "onDeActivate" + }, + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "JoystickForce" + }, + { + "functionName": "SetJoystickForce" + }, + { + "functionName": "JoystickAngle" + }, + { + "functionName": "SetJoystickAngle" + }, + { + "functionName": "StickForceX" + }, + { + "functionName": "StickForceY" + }, + { + "functionName": "IsDirectionPushed4Way" + }, + { + "functionName": "IsDirectionPushed8Way" + }, + { + "functionName": "IsPressed" + }, + { + "functionName": "Reset" + }, + { + "folderName": "Multitouch Joystick configuration", + "children": [ + { + "functionName": "ControllerIdentifier" + }, + { + "functionName": "SetControllerIdentifier" + }, + { + "functionName": "JoystickIdentifier" + }, + { + "functionName": "SetJoystickIdentifier" + }, + { + "functionName": "DeadZoneRadius" + }, + { + "functionName": "SetDeadZoneRadius" + } + ] + }, + { + "functionName": "ForceStartPressing" + } + ] + }, + "propertyDescriptors": [ + { + "value": "1", + "type": "Number", + "label": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "String", + "label": "Joystick name", + "name": "JoystickIdentifier" + }, + { + "value": "0.4", + "type": "Number", + "label": "Dead zone radius (range: 0 to 1)", + "description": "The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved)", + "name": "DeadZoneRadius" + }, + { + "value": "0", + "type": "Number", + "label": "Joystick angle (range: -180 to 180)", + "hidden": true, + "name": "JoystickAngle" + }, + { + "value": "0", + "type": "Number", + "label": "Joystick force (range: 0 to 1)", + "hidden": true, + "name": "JoystickForce" + }, + { + "value": "0", + "type": "Number", + "label": "", + "hidden": true, + "name": "TouchId" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "TouchIndex" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ControllerIdentifier" + }, + { + "propertyName": "JoystickIdentifier" + }, + { + "propertyName": "DeadZoneRadius" + }, + { + "propertyName": "JoystickAngle" + }, + { + "propertyName": "JoystickForce" + }, + { + "propertyName": "TouchId" + }, + { + "propertyName": "TouchIndex" + } + ] + } + }, + { + "description": "Detect presses made on a touchscreen on the object so it acts like a button and automatically trigger the button having the same identifier for the mapper behaviors.", + "fullName": "Multitouch button", + "helpPath": "", + "iconUrl": "", + "name": "MultitouchButton", + "objectType": "", + "previewIconUrl": "", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsReleased", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsReleased", + "False", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchButton::SetButtonState" + }, + "parameters": [ + "Object", + "Behavior", + "\"Idle\"", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsJustPressed", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsJustPressed", + "False", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchButton::SetButtonState" + }, + "parameters": [ + "Object", + "Behavior", + "\"Pressed\"", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + }, + { + "type": { + "value": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchIndex", + "=", + "0" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "StartedTouchOrMouseCount()", + "conditions": [], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "CollisionPoint" + }, + "parameters": [ + "Object", + "TouchX(StartedTouchOrMouseId(TouchIndex), Object.Layer())", + "TouchY(StartedTouchOrMouseId(TouchIndex), Object.Layer())" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "Radius", + ">", + "DistanceBetweenPositions(Object.CenterX(), Object.CenterY(), TouchX(StartedTouchOrMouseId(TouchIndex), Object.Layer()), TouchY(StartedTouchOrMouseId(TouchIndex), Object.Layer()))" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "StartedTouchOrMouseId(TouchIndex)" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchButton::SetButtonState" + }, + "parameters": [ + "Object", + "Behavior", + "\"JustPressed\"", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsJustPressed", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchIndex", + "+", + "1" + ] + } + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + }, + { + "type": { + "value": "HasTouchEnded" + }, + "parameters": [ + "", + "TouchId" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchButton::SetButtonState" + }, + "parameters": [ + "Object", + "Behavior", + "\"Released\"", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsReleased", + "True", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsJustPressed", + "False", + "" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "0" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchButton", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button was just pressed.", + "fullName": "Button just pressed", + "functionType": "Condition", + "name": "IsJustPressed", + "sentence": "Button _PARAM0_ was just pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsJustPressed", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchButton", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is pressed.", + "fullName": "Button pressed", + "functionType": "Condition", + "name": "IsPressed", + "sentence": "Button _PARAM0_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchButton", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is released.", + "fullName": "Button released", + "functionType": "Condition", + "name": "IsReleased", + "sentence": "Button _PARAM0_ is released", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsReleased", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchButton", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Button state", + "functionType": "Action", + "name": "SetButtonState", + "private": true, + "sentence": "Mark the button _PARAM0_ as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::SetButtonState" + }, + "parameters": [ + "", + "ControllerIdentifier", + "ButtonIdentifier", + "ButtonState", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchButton", + "type": "behavior" + }, + { + "description": "Button state", + "name": "ButtonState", + "supplementaryInformation": "[\"Idle\",\"JustPressed\",\"Pressed\",\"Released\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "IsJustPressed" + }, + { + "functionName": "IsPressed" + }, + { + "functionName": "IsReleased" + }, + { + "functionName": "SetButtonState" + } + ] + }, + "propertyDescriptors": [ + { + "value": "1", + "type": "Number", + "label": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "A", + "type": "String", + "label": "Button identifier", + "name": "ButtonIdentifier" + }, + { + "value": "0", + "type": "Number", + "label": "TouchID", + "hidden": true, + "name": "TouchId" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "TouchIndex" + }, + { + "value": "", + "type": "Boolean", + "label": "Button released", + "hidden": true, + "name": "IsReleased" + }, + { + "value": "", + "type": "Boolean", + "label": "Button just pressed", + "hidden": true, + "name": "IsJustPressed" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Triggering circle radius", + "description": "This circle adds up to the object collision mask.", + "name": "Radius" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ControllerIdentifier" + }, + { + "propertyName": "ButtonIdentifier" + }, + { + "propertyName": "TouchId" + }, + { + "propertyName": "TouchIndex" + }, + { + "propertyName": "IsReleased" + }, + { + "propertyName": "IsJustPressed" + }, + { + "propertyName": "Radius" + } + ] + } + }, + { + "description": "Control a platformer character with a multitouch controller.", + "fullName": "Platformer multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "PlatformerMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Left\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "Property" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Right\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "Property" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Up\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "Property" + ] + }, + { + "type": { + "value": "PlatformBehavior::SimulateLadderKey" + }, + "parameters": [ + "Object", + "Property" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Down\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "Property" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsButtonPressed" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JumpButton", + "\"Down\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateJumpKey" + }, + "parameters": [ + "Object", + "Property" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::PlatformerMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Platform character behavior", + "extraInformation": [ + "PlatformBehavior::PlatformerObjectBehavior" + ], + "choices": [], + "name": "Property" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Joystick name", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "A", + "type": "String", + "label": "Jump button name", + "group": "Controls", + "name": "JumpButton" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Property" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "JoystickIdentifier" + }, + { + "propertyName": "JumpButton" + } + ] + } + ] + } + }, + { + "description": "Control a 3D physics character with a multitouch controller.", + "fullName": "3D platformer multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "Platformer3DMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::StickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "JoystickIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SetForwardAngle" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D", + "=", + "SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier) + CameraAngle(Object.Layer())" + ] + }, + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateStick" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D", + "-90", + "SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsButtonPressed" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JumpButton", + "\"Down\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateJumpKey" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::Platformer3DMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics character", + "extraInformation": [ + "Physics3D::PhysicsCharacter3D" + ], + "choices": [], + "name": "PhysicsCharacter3D" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Walk joystick", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "A", + "type": "String", + "label": "Jump button name", + "group": "Controls", + "name": "JumpButton" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "PhysicsCharacter3D" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "JoystickIdentifier" + }, + { + "propertyName": "JumpButton" + } + ] + } + ] + } + }, + { + "description": "Control a 3D physics character with a multitouch controller.", + "fullName": "3D shooter multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "Shooter3DMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::StickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "JoystickIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateStick" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D", + "SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier)", + "SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsButtonPressed" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JumpButton", + "\"Down\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateJumpKey" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::Shooter3DMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics character", + "extraInformation": [ + "Physics3D::PhysicsCharacter3D" + ], + "choices": [], + "name": "PhysicsCharacter3D" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Walk joystick", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "A", + "type": "String", + "label": "Jump button name", + "group": "Controls", + "name": "JumpButton" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "PhysicsCharacter3D" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "JoystickIdentifier" + }, + { + "propertyName": "JumpButton" + } + ] + } + ] + } + }, + { + "description": "Control camera rotations with a multitouch controller.", + "fullName": "First person camera multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "FirstPersonMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "TODO It's probably a bad idea to rotate the object around Y." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CurrentRotationSpeedZ", + "=", + "SpriteMultitouchJoystick::AcceleratedSpeed(CurrentRotationSpeedZ, SpriteMultitouchJoystick::StickForceX(ControllerIdentifier, CameraStick) * HorizontalRotationSpeedMax, HorizontalRotationSpeedMax, HorizontalRotationAcceleration, HorizontalRotationDeceleration)" + ] + }, + { + "type": { + "value": "SetAngle" + }, + "parameters": [ + "Object", + "+", + "CurrentRotationSpeedZ * TimeDelta()" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CurrentRotationSpeedY", + "=", + "SpriteMultitouchJoystick::AcceleratedSpeed(CurrentRotationSpeedY, SpriteMultitouchJoystick::StickForceY(ControllerIdentifier, CameraStick) * VerticalRotationSpeedMax, VerticalRotationSpeedMax, VerticalRotationAcceleration, VerticalRotationDeceleration)" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationY" + }, + "parameters": [ + "Object", + "Object3D", + "+", + "CurrentRotationSpeedY * TimeDelta()" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationY" + }, + "parameters": [ + "Object", + "Object3D", + "=", + "clamp(Object.Object3D::RotationY(), VerticalAngleMin, VerticalAngleMax)" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper::LookFromObjectEyes" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera to look though _PARAM1_ eyes. The object must look to the right when all its angles are 0 and the top of its head be toward Z+.", + "fullName": "Look through object eyes", + "functionType": "Action", + "group": "Layers and cameras", + "name": "LookFromObjectEyes", + "private": true, + "sentence": "Move the camera to look though _PARAM0_ eyes", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "CenterCameraOnObject" + }, + "parameters": [ + "", + "Object", + "", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "Object.Object3D::Z() + Object.Object3D::Depth() + OffsetZ", + "", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationX" + }, + "parameters": [ + "", + "=", + "- Object.Object3D::RotationY() + 90", + "GetArgumentAsString(\"Layer\")", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationY" + }, + "parameters": [ + "", + "=", + "Object.Object3D::RotationX()", + "GetArgumentAsString(\"Layer\")", + "" + ] + }, + { + "type": { + "value": "SetCameraAngle" + }, + "parameters": [ + "", + "=", + "Object.Angle() + 90", + "Object.Layer()", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the maximum horizontal rotation speed of the object.", + "fullName": "Maximum horizontal rotation speed", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "HorizontalRotationSpeedMax", + "sentence": "the maximum horizontal rotation speed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "HorizontalRotationSpeedMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "HorizontalRotationSpeedMax", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "SetHorizontalRotationSpeedMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "HorizontalRotationSpeedMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the horizontal rotation acceleration of the object.", + "fullName": "Horizontal rotation acceleration", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "HorizontalRotationAcceleration", + "sentence": "the horizontal rotation acceleration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "HorizontalRotationAcceleration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "HorizontalRotationAcceleration", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "SetHorizontalRotationAcceleration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "HorizontalRotationAcceleration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the horizontal rotation deceleration of the object.", + "fullName": "Horizontal rotation deceleration", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "HorizontalRotationDeceleration", + "sentence": "the horizontal rotation deceleration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "HorizontalRotationDeceleration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "HorizontalRotationDeceleration", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "SetHorizontalRotationDeceleration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "HorizontalRotationDeceleration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the maximum vertical rotation speed of the object.", + "fullName": "Maximum vertical rotation speed", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "VerticalRotationSpeedMax", + "sentence": "the maximum vertical rotation speed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalRotationSpeedMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalRotationSpeedMax", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "SetVerticalRotationSpeedMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalRotationSpeedMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the vertical rotation acceleration of the object.", + "fullName": "Vertical rotation acceleration", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "VerticalRotationAcceleration", + "sentence": "the vertical rotation acceleration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalRotationAcceleration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalRotationAcceleration", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "SetVerticalRotationAcceleration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalRotationAcceleration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the vertical rotation deceleration of the object.", + "fullName": "Vertical rotation deceleration", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "VerticalRotationDeceleration", + "sentence": "the vertical rotation deceleration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalRotationDeceleration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalRotationDeceleration", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "SetVerticalRotationDeceleration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalRotationDeceleration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the minimum vertical camera angle of the object.", + "fullName": "Minimum vertical camera angle", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "VerticalAngleMin", + "sentence": "the minimum vertical camera angle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalAngleMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalAngleMin", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "SetVerticalAngleMin", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalAngleMin", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the maximum vertical camera angle of the object.", + "fullName": "Maximum vertical camera angle", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "VerticalAngleMax", + "sentence": "the maximum vertical camera angle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalAngleMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalAngleMax", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "SetVerticalAngleMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalAngleMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the z position offset of the object.", + "fullName": "Z position offset", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper position configuration", + "name": "OffsetZ", + "sentence": "the z position offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "OffsetZ" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "OffsetZ", + "group": "First person camera multitouch controller mapper position configuration", + "name": "SetOffsetZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OffsetZ", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + }, + { + "folderName": "Layers and cameras", + "children": [ + { + "functionName": "LookFromObjectEyes" + } + ] + }, + { + "folderName": "First person camera multitouch controller mapper horizontal rotation configuration", + "children": [ + { + "functionName": "HorizontalRotationSpeedMax" + }, + { + "functionName": "SetHorizontalRotationSpeedMax" + }, + { + "functionName": "HorizontalRotationAcceleration" + }, + { + "functionName": "SetHorizontalRotationAcceleration" + }, + { + "functionName": "HorizontalRotationDeceleration" + }, + { + "functionName": "SetHorizontalRotationDeceleration" + } + ] + }, + { + "folderName": "First person camera multitouch controller mapper vertical rotation configuration", + "children": [ + { + "functionName": "VerticalRotationSpeedMax" + }, + { + "functionName": "SetVerticalRotationSpeedMax" + }, + { + "functionName": "VerticalRotationAcceleration" + }, + { + "functionName": "SetVerticalRotationAcceleration" + }, + { + "functionName": "VerticalRotationDeceleration" + }, + { + "functionName": "SetVerticalRotationDeceleration" + }, + { + "functionName": "VerticalAngleMin" + }, + { + "functionName": "SetVerticalAngleMin" + }, + { + "functionName": "VerticalAngleMax" + }, + { + "functionName": "SetVerticalAngleMax" + } + ] + }, + { + "folderName": "First person camera multitouch controller mapper position configuration", + "children": [ + { + "functionName": "OffsetZ" + }, + { + "functionName": "SetOffsetZ" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D capability", + "extraInformation": [ + "Scene3D::Base3DBehavior" + ], + "choices": [], + "name": "Object3D" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Secondary", + "type": "Choice", + "label": "Camera joystick", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "CameraStick" + }, + { + "value": "180", + "type": "Number", + "unit": "AngularSpeed", + "label": "Maximum rotation speed", + "group": "Horizontal rotation", + "name": "HorizontalRotationSpeedMax" + }, + { + "value": "360", + "type": "Number", + "label": "Rotation acceleration", + "group": "Horizontal rotation", + "name": "HorizontalRotationAcceleration" + }, + { + "value": "720", + "type": "Number", + "label": "Rotation deceleration", + "group": "Horizontal rotation", + "name": "HorizontalRotationDeceleration" + }, + { + "value": "120", + "type": "Number", + "unit": "AngularSpeed", + "label": "Maximum rotation speed", + "group": "Vertical rotation", + "name": "VerticalRotationSpeedMax" + }, + { + "value": "240", + "type": "Number", + "label": "Rotation acceleration", + "group": "Vertical rotation", + "name": "VerticalRotationAcceleration" + }, + { + "value": "480", + "type": "Number", + "label": "Rotation deceleration", + "group": "Vertical rotation", + "name": "VerticalRotationDeceleration" + }, + { + "value": "-90", + "type": "Number", + "unit": "DegreeAngle", + "label": "Minimum angle", + "group": "Vertical rotation", + "name": "VerticalAngleMin" + }, + { + "value": "90", + "type": "Number", + "unit": "DegreeAngle", + "label": "Maximum angle", + "group": "Vertical rotation", + "name": "VerticalAngleMax" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Z position offset", + "group": "Position", + "name": "OffsetZ" + }, + { + "value": "0", + "type": "Number", + "unit": "AngularSpeed", + "label": "Current rotation speed Z", + "hidden": true, + "name": "CurrentRotationSpeedZ" + }, + { + "value": "0", + "type": "Number", + "unit": "AngularSpeed", + "label": "Current rotation speed Y", + "hidden": true, + "name": "CurrentRotationSpeedY" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Object3D" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "CameraStick" + } + ] + }, + { + "folderName": "Horizontal rotation", + "children": [ + { + "propertyName": "HorizontalRotationSpeedMax" + }, + { + "propertyName": "HorizontalRotationAcceleration" + }, + { + "propertyName": "HorizontalRotationDeceleration" + } + ] + }, + { + "folderName": "Vertical rotation", + "children": [ + { + "propertyName": "VerticalRotationSpeedMax" + }, + { + "propertyName": "VerticalRotationAcceleration" + }, + { + "propertyName": "VerticalRotationDeceleration" + }, + { + "propertyName": "VerticalAngleMin" + }, + { + "propertyName": "VerticalAngleMax" + } + ] + }, + { + "folderName": "Position", + "children": [ + { + "propertyName": "OffsetZ" + } + ] + }, + { + "propertyName": "CurrentRotationSpeedZ" + }, + { + "propertyName": "CurrentRotationSpeedY" + } + ] + } + }, + { + "description": "Control a 3D physics car with a multitouch controller.", + "fullName": "3D car multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "PhysicsCar3DMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::StickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "SteerJoystickIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateSteeringStick" + }, + "parameters": [ + "Object", + "PhysicsCar3D", + "SpriteMultitouchJoystick::StickForceX(ControllerIdentifier, SteerJoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::StickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "SpeedJoystickIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateAcceleratorStick" + }, + "parameters": [ + "Object", + "PhysicsCar3D", + "-SpriteMultitouchJoystick::StickForceY(ControllerIdentifier, SpeedJoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsButtonPressed" + }, + "parameters": [ + "", + "ControllerIdentifier", + "HandBrakeButton", + "\"Down\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateHandBrakeKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::PhysicsCar3DMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics car", + "extraInformation": [ + "Physics3D::PhysicsCar3D" + ], + "choices": [], + "name": "PhysicsCar3D" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Steer joystick", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "SteerJoystickIdentifier" + }, + { + "value": "Secondary", + "type": "Choice", + "label": "Speed joystick", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "SpeedJoystickIdentifier" + }, + { + "value": "B", + "type": "String", + "label": "Hand brake button name", + "group": "Controls", + "name": "HandBrakeButton" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "PhysicsCar3D" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "SteerJoystickIdentifier" + }, + { + "propertyName": "SpeedJoystickIdentifier" + }, + { + "propertyName": "HandBrakeButton" + } + ] + } + ] + } + }, + { + "description": "Control a top-down character with a multitouch controller.", + "fullName": "Top-down multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "TopDownMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "StickMode", + "=", + "\"Analog\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateStick" + }, + "parameters": [ + "Object", + "TopDownMovement", + "SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier)", + "SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "StickMode", + "=", + "\"360°\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateStick" + }, + "parameters": [ + "Object", + "TopDownMovement", + "SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier)", + "sign(SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier))" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "StickMode", + "=", + "\"8 Directions\"" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "TopDownMovementBehavior::DiagonalsAllowed" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Left\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Right\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Up\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Down\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "TopDownMovementBehavior::DiagonalsAllowed" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Left\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Right\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Up\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Down\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"UpLeft\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + }, + { + "type": { + "value": "TopDownMovementBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"UpRight\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + }, + { + "type": { + "value": "TopDownMovementBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"DownLeft\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + }, + { + "type": { + "value": "TopDownMovementBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"DownRight\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + }, + { + "type": { + "value": "TopDownMovementBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::TopDownMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Top-down movement behavior", + "extraInformation": [ + "TopDownMovementBehavior::TopDownMovementBehavior" + ], + "choices": [], + "name": "TopDownMovement" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Joystick name", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "Analog", + "type": "Choice", + "label": "Stick mode", + "group": "Controls", + "choices": [ + { + "label": "Analog", + "value": "Analog" + }, + { + "label": "360°", + "value": "360°" + }, + { + "label": "8 Directions", + "value": "8 Directions" + } + ], + "name": "StickMode" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "TopDownMovement" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "propertyName": "JoystickIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "StickMode" + } + ] + } + ] + } + } + ], + "eventsBasedObjects": [ + { + "areaMaxX": 64, + "areaMaxY": 64, + "areaMaxZ": 64, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "assetStoreTag": "multitouch joysticks", + "defaultName": "Joystick", + "description": "Joystick for touchscreens.", + "fullName": "Multitouch Joystick", + "helpPath": "", + "iconUrl": "", + "isUsingLegacyInstancesRenderer": true, + "name": "SpriteMultitouchJoystick", + "previewIconUrl": "", + "objects": [ + { + "adaptCollisionMaskAutomatically": false, + "assetStoreId": "", + "name": "Thumb", + "type": "Sprite", + "updateIfNotVisible": false, + "variables": [], + "effects": [], + "behaviors": [], + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [] + } + ] + } + ] + }, + { + "adaptCollisionMaskAutomatically": false, + "assetStoreId": "", + "name": "Border", + "type": "Sprite", + "updateIfNotVisible": false, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "MultitouchJoystick", + "type": "SpriteMultitouchJoystick::MultitouchJoystick", + "ControllerIdentifier": 1, + "JoystickIdentifier": "Primary", + "FloatingEnabled": false, + "DeadZoneRadius": 0.4, + "JoystickAngle": 0, + "JoystickForce": 0, + "TouchId": 0, + "TouchIndex": 0 + } + ], + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [] + } + ] + } + ] + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Thumb" + }, + { + "objectName": "Border" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [], + "editionSettings": [], + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Create" + }, + "parameters": [ + "", + "Border", + "0", + "0", + "" + ] + }, + { + "type": { + "value": "Create" + }, + "parameters": [ + "", + "Thumb", + "0", + "0", + "" + ] + }, + { + "type": { + "value": "SetZOrder" + }, + "parameters": [ + "Border", + "=", + "1" + ] + }, + { + "type": { + "value": "SetZOrder" + }, + "parameters": [ + "Thumb", + "=", + "2" + ] + }, + { + "type": { + "value": "SetCenter" + }, + "parameters": [ + "Border", + "=", + "0", + "=", + "0" + ] + }, + { + "type": { + "value": "SetCenter" + }, + "parameters": [ + "Thumb", + "=", + "0", + "=", + "0" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::UpdateConfiguration" + }, + "parameters": [ + "Object", + "" + ] + }, + { + "type": { + "value": "SetIncludedInParentCollisionMask" + }, + "parameters": [ + "Thumb", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::IsInGameEdition" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "PutAroundObject" + }, + "parameters": [ + "Thumb", + "Border", + "Border.MultitouchJoystick::JoystickForce() * Border.Width() / 2", + "Border.MultitouchJoystick::JoystickAngle()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldBeHiddenWhenReleased", + "True", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::IsPressed" + }, + "parameters": [ + "Object", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Hide" + }, + "parameters": [ + "Object" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::ActivateControl" + }, + "parameters": [ + "Object", + "no", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "ShouldBeHiddenWhenReleased", + "False", + "" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::UpdateConfiguration" + }, + "parameters": [ + "Object", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Pass the object property values to the behavior.", + "fullName": "Update configuration", + "functionType": "Action", + "name": "UpdateConfiguration", + "private": true, + "sentence": "Update the configuration of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetControllerIdentifier" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "ControllerIdentifier", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetJoystickIdentifier" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "JoystickIdentifier", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetDeadZoneRadius" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "DeadZoneRadius", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Show the joystick until it is released.", + "fullName": "Show and start pressing", + "functionType": "Action", + "name": "TeleportAndPress", + "sentence": "Show _PARAM0_ at the cursor position and start pressing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetXY" + }, + "parameters": [ + "Object", + "=", + "Object.ParentTouchX(StartedTouchOrMouseId(0))", + "=", + "Object.ParentTouchY(StartedTouchOrMouseId(0))" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::ActivateControl" + }, + "parameters": [ + "Object", + "yes", + "" + ] + }, + { + "type": { + "value": "Show" + }, + "parameters": [ + "Object", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "ShouldBeHiddenWhenReleased", + "True", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::ForceStartPressing" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "StartedTouchOrMouseId(0)", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the X position of a specified touch", + "fullName": "Touch X position (on parent)", + "functionType": "Expression", + "name": "ParentTouchX", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];", + "const touchId = eventsFunctionContext.getArgument(\"TouchId\");", + "eventsFunctionContext.returnValue = gdjs.evtTools.input.getTouchX(object.getInstanceContainer(), touchId, object.getLayer());" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + }, + { + "description": "Touch identifier", + "name": "TouchId", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the X position of a specified touch", + "fullName": "Touch X position (on parent)", + "functionType": "Expression", + "name": "ParentTouchY", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];", + "const touchId = eventsFunctionContext.getArgument(\"TouchId\");", + "eventsFunctionContext.returnValue = gdjs.evtTools.input.getTouchY(object.getInstanceContainer(), touchId, object.getLayer());" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + }, + { + "description": "Touch identifier", + "name": "TouchId", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "De/activate control of the joystick.", + "fullName": "De/activate control", + "functionType": "Action", + "name": "ActivateControl", + "sentence": "Activate control of _PARAM0_: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldActivate", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "no" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldActivate", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "yes" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + }, + { + "description": "Activate", + "name": "ShouldActivate", + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a stick is pressed.", + "fullName": "Stick pressed", + "functionType": "Condition", + "name": "IsPressed", + "sentence": "Stick _PARAM0_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::IsPressed" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "!=" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the joystick force (from 0 to 1).", + "fullName": "Joystick force (deprecated)", + "functionType": "ExpressionAndCondition", + "name": "JoystickForce", + "private": true, + "sentence": "the joystick force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::JoystickForce()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the strick force (from 0 to 1).", + "fullName": "Stick force", + "functionType": "ExpressionAndCondition", + "name": "StickForce", + "sentence": "the stick force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::JoystickForce()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the stick force on X axis (from -1 at the left to 1 at the right).", + "fullName": "Stick X force", + "functionType": "ExpressionAndCondition", + "name": "StickForceX", + "sentence": "the stick X force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::StickForceX()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the stick force on Y axis (from -1 at the top to 1 at the bottom).", + "fullName": "Stick Y force", + "functionType": "ExpressionAndCondition", + "name": "StickForceY", + "sentence": "the stick Y force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::StickForceY()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle the joystick is pointing towards (from -180 to 180).", + "fullName": "Joystick angle (deprecated)", + "functionType": "Expression", + "name": "JoystickAngle", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::JoystickAngle()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle the stick is pointing towards (from -180 to 180).", + "fullName": "Stick angle", + "functionType": "Expression", + "name": "StickAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::JoystickAngle()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (4-way movement)", + "functionType": "Condition", + "name": "IsDirectionPushed4Way", + "sentence": "_PARAM0_ is pushed in direction _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (8-way movement)", + "functionType": "Condition", + "name": "IsDirectionPushed8Way", + "sentence": "_PARAM0_ is pushed in direction _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\",\"UpLeft\",\"UpRight\",\"DownLeft\",\"DownRight\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "the multitouch controller identifier (1, 2, 3, 4...).", + "fullName": "Multitouch controller identifier", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "ControllerIdentifier", + "sentence": "the multitouch controller identifier", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::PropertyControllerIdentifier()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ControllerIdentifier", + "group": "Multitouch Joystick configuration", + "name": "SetControllerIdentifier", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetControllerIdentifier" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "Value", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the joystick name of the object.", + "fullName": "Joystick name", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "JoystickIdentifier", + "sentence": "the joystick name", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "Border.MultitouchJoystick::PropertyJoystickIdentifier()" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "JoystickIdentifier", + "group": "Multitouch Joystick configuration", + "name": "SetJoystickIdentifier", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetJoystickIdentifier" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "Value", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the dead zone radius of the joystick (range: 0 to 1). The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved).", + "fullName": "Dead zone radius", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "DeadZoneRadius", + "sentence": "the dead zone radius", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::PropertyDeadZoneRadius()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "DeadZoneRadius", + "group": "Multitouch Joystick configuration", + "name": "SetDeadZoneRadius", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetDeadZoneRadius" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "Value", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPostEvents" + }, + { + "functionName": "onHotReloading" + }, + { + "functionName": "UpdateConfiguration" + }, + { + "functionName": "TeleportAndPress" + }, + { + "functionName": "ParentTouchX" + }, + { + "functionName": "ParentTouchY" + }, + { + "functionName": "ActivateControl" + }, + { + "functionName": "IsPressed" + }, + { + "functionName": "JoystickForce" + }, + { + "functionName": "StickForce" + }, + { + "functionName": "StickForceX" + }, + { + "functionName": "StickForceY" + }, + { + "functionName": "JoystickAngle" + }, + { + "functionName": "StickAngle" + }, + { + "functionName": "IsDirectionPushed4Way" + }, + { + "functionName": "IsDirectionPushed8Way" + }, + { + "folderName": "Multitouch Joystick configuration", + "children": [ + { + "functionName": "ControllerIdentifier" + }, + { + "functionName": "SetControllerIdentifier" + }, + { + "functionName": "JoystickIdentifier" + }, + { + "functionName": "SetJoystickIdentifier" + }, + { + "functionName": "DeadZoneRadius" + }, + { + "functionName": "SetDeadZoneRadius" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "1", + "type": "Number", + "label": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Joystick name", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "0.4", + "type": "Number", + "label": "Dead zone radius (range: 0 to 1)", + "description": "The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved)", + "name": "DeadZoneRadius" + }, + { + "value": "Center-center", + "type": "String", + "label": "", + "description": "Only used by the scene editor.", + "extraInformation": [ + "Thumb" + ], + "choices": [], + "hidden": true, + "name": "ThumbAnchorOrigin" + }, + { + "value": "Center-center", + "type": "Number", + "label": "", + "description": "Only used by the scene editor.", + "hidden": true, + "name": "ThumbAnchorTarget" + }, + { + "value": "true", + "type": "Boolean", + "label": "", + "description": "Only used by the scene editor.", + "extraInformation": [ + "Thumb" + ], + "choices": [], + "hidden": true, + "name": "ThumbIsScaledProportionally" + }, + { + "value": "Center-center", + "type": "String", + "label": "", + "description": "Only used by the scene editor.", + "hidden": true, + "name": "ParentOrigin" + }, + { + "value": "", + "type": "Boolean", + "label": "", + "hidden": true, + "name": "ShouldBeHiddenWhenReleased" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ControllerIdentifier" + }, + { + "propertyName": "JoystickIdentifier" + }, + { + "propertyName": "DeadZoneRadius" + }, + { + "propertyName": "ThumbAnchorOrigin" + }, + { + "propertyName": "ThumbAnchorTarget" + }, + { + "propertyName": "ThumbIsScaledProportionally" + }, + { + "propertyName": "ParentOrigin" + }, + { + "propertyName": "ShouldBeHiddenWhenReleased" + } + ] + } + } + ] + }, + { + "author": "PANDAKO-GitHub", + "category": "Input", + "extensionNamespace": "", + "fullName": "Mouse Pointer Lock", + "gdevelopVersion": ">=5.5.222", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMTksMTkgMjQsMTkgMjQsMjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSI2LDIzIDExLDIzIDExLDE4ICIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTI0LDE5LjRjLTAuNywwLjgtMS40LDEuNi0yLjIsMi40Yy03LDctMTUuMywxMC4yLTE4LjUsN3MtMC4xLTExLjUsNy0xOC41czE1LjMtMTAuMiwxOC41LTcNCgljMS40LDEuNCwxLjYsMy42LDAuOCw2LjMiLz4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xMSwyMi41Yy0wLjMtMC4yLTAuNS0wLjUtMC44LTAuOGMtNy03LTEwLjItMTUuMy03LTE4LjVzMTEuNS0wLjEsMTguNSw3czEwLjIsMTUuMyw3LDE4LjUNCgljLTEuNywxLjctNC44LDEuNi04LjQsMC4xIi8+DQo8L3N2Zz4NCg==", + "name": "MousePointerLock", + "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Virtual Reality/Virtual Reality_360_rotate_vr_movement.svg", + "shortDescription": "Lock and hide mouse pointer for unlimited movement (e.g., FPS mouse look).", + "version": "0.3.1", + "description": [ + "This behavior removes the limit on the distance the mouse can move and hides the cursor.", + "", + "For example, a user can rotate or continue to manipulate an object by moving the mouse endlessly ([open the project online](https://editor.gdevelop.io/?project=example://mouse-pointer-lock)).", + "Without the pointer lock, the rotation or operation will stop the moment the pointer reaches the edge of the browser or screen.", + "", + "Game players don't have to worry about leaving the gameplay area or accidentally clicking on another application that moves the mouse focus away from the game.", + "", + "Locking the pointer locks `CursorX()` and `CursorY()` expressions.", + "Use `MovementX()` and `MovementY()` instead.", + "" + ], + "origin": { + "identifier": "MousePointerLock", + "name": "gdevelop-extension-store" + }, + "tags": [ + "mouse", + "lock", + "cursor", + "pointer" + ], + "authorIds": [ + "Zu55H5hcb9YmZTltIVOTAFDJQyB2", + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [ + { + "name": "TouchId", + "type": "number", + "value": -1 + }, + { + "name": "OldX", + "type": "number", + "value": 0 + }, + { + "name": "OldY", + "type": "number", + "value": 0 + }, + { + "name": "TouchSpeedFactor", + "type": "number", + "value": 4 + } + ], + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onFirstSceneLoaded", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "class PointerLockHandler {", + " movementX = 0;", + " movementY = 0;", + " retried = false;", + " /**", + " * @type {gdjs.RuntimeGame}", + " */", + " game;", + "", + " /**", + " * @param {gdjs.RuntimeGame} game", + " */", + " constructor(game) {", + " this.game = game;", + " const canvas = game.getRenderer().getCanvas();", + "", + " canvas.addEventListener(\"pointermove\", event => {", + " if (!!document.pointerLockElement) {", + " this.movementX += event.movementX || 0;", + " this.movementY += event.movementY || 0;", + " }", + " }, false);", + "", + " document.addEventListener(\"pointerlockerror\", event => {", + " if (!this.retried) {", + " canvas.requestPointerLock();", + " this.retried = true;", + " }", + " });", + " }", + "", + " requestPointerLock() {", + " const canvas = this.game.getRenderer().getCanvas();", + " if (canvas.requestPointerLock && !document.pointerLockElement) {", + " this.retried = false;", + " canvas.requestPointerLock({ unadjustedMovement: true });", + " }", + " }", + "", + " exitPointerLock() {", + " if (document.exitPointerLock) {", + " document.exitPointerLock();", + " }", + " }", + "", + " isPointerLocked() {", + " return !!document.pointerLockElement;", + " }", + "", + " resetMovement() {", + " this.movementX = 0;", + " this.movementY = 0;", + " }", + "};", + "", + "gdjs._MousePointerLockExtension = { handler: new PointerLockHandler(runtimeScene.getGame()) };", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onScenePostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.resetMovement();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "MousePointerLock::isEmulatingPointerLock" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "MousePointerLock::SetMovementX" + }, + "parameters": [ + "", + "=", + "(MousePointerLock::TouchX(TouchId) - OldX) * TouchSpeedFactor", + "" + ] + }, + { + "type": { + "value": "MousePointerLock::SetMovementY" + }, + "parameters": [ + "", + "=", + "(MousePointerLock::TouchY(TouchId) - OldY) * TouchSpeedFactor", + "" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OldX", + "=", + "MousePointerLock::TouchX(TouchId)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OldY", + "=", + "MousePointerLock::TouchY(TouchId)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasTouchEnded" + }, + "parameters": [ + "", + "TouchId" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "-1" + ] + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Lock the mouse pointer to hide it.", + "fullName": "Request Pointer Lock", + "functionType": "Action", + "name": "RequestPointerLock", + "sentence": "Request Pointer Lock", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "-1" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "StartedTouchOrMouseId(0)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OldX", + "=", + "MousePointerLock::TouchX(TouchId)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OldY", + "=", + "MousePointerLock::TouchY(TouchId)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Unlocks the mouse pointer and show it.", + "fullName": "Exit pointer lock", + "functionType": "Action", + "name": "ExitPointerLock", + "sentence": "Exit pointer lock", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "-1" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.exitPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Check if the mouse pointer is locked.", + "fullName": "Pointer is locked", + "functionType": "Condition", + "name": "isPointerLocked", + "sentence": "The mouse pointer is locked", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "0" + ] + }, + { + "type": { + "value": "MousePointerLock::isPointerActuallyLocked" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Check if the mouse pointer is actually locked.", + "fullName": "Pointer is actually locked", + "functionType": "Condition", + "name": "isPointerActuallyLocked", + "private": true, + "sentence": "The mouse pointer actually is locked", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "eventsFunctionContext.returnValue = handler.isPointerLocked();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Check if the mouse pointer lock is emulated.", + "fullName": "Pointer lock is emulated", + "functionType": "Condition", + "name": "isEmulatingPointerLock", + "private": true, + "sentence": "The mouse pointer lock is emulated", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "MousePointerLock::isPointerActuallyLocked" + }, + "parameters": [ + "", + "" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Check if the locked pointer is moving.", + "fullName": "Locked pointer is moving", + "functionType": "Condition", + "name": "IsMoving", + "sentence": "Locked pointer is moving", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "MousePointerLock::MovementX" + }, + "parameters": [ + "", + "!=", + "0", + "" + ] + }, + { + "type": { + "value": "MousePointerLock::MovementY" + }, + "parameters": [ + "", + "!=", + "0", + "" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "the movement of the locked pointer on the X axis.", + "fullName": "Pointer X movement", + "functionType": "ExpressionAndCondition", + "name": "MovementX", + "sentence": "the movement of the locked pointer on the X axis", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "eventsFunctionContext.returnValue = gdjs._MousePointerLockExtension.handler.movementX || 0;\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [], + "objectGroups": [] + }, + { + "description": "the movement of the locked pointer on the X axis.", + "fullName": "Pointer X movement", + "functionType": "ActionWithOperator", + "getterName": "MovementX", + "name": "SetMovementX", + "private": true, + "sentence": "the movement of the locked pointer on the X axis", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "gdjs._MousePointerLockExtension.handler.movementX = value;\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "the movement of the pointer on the Y axis.", + "fullName": "Pointer Y movement", + "functionType": "ExpressionAndCondition", + "name": "MovementY", + "sentence": "the movement of the pointer on the Y axis", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "eventsFunctionContext.returnValue = gdjs._MousePointerLockExtension.handler.movementY || 0;\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [], + "objectGroups": [] + }, + { + "description": "the movement of the locked pointer on the X axis.", + "fullName": "Pointer X movement", + "functionType": "ActionWithOperator", + "getterName": "MovementY", + "name": "SetMovementY", + "private": true, + "sentence": "the movement of the locked pointer on the X axis", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "gdjs._MousePointerLockExtension.handler.movementY = value;\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Return the X position of a specific touch", + "fullName": "Touch X position", + "functionType": "Expression", + "name": "TouchX", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const touchId = eventsFunctionContext.getArgument(\"NewTouchId\");", + "eventsFunctionContext.returnValue = runtimeScene.getGame().getInputManager().getTouchX(touchId);", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Touch identifier", + "name": "NewTouchId", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Y position of a specific touch", + "fullName": "Touch Y position", + "functionType": "Expression", + "name": "TouchY", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const touchId = eventsFunctionContext.getArgument(\"NewTouchId\");", + "eventsFunctionContext.returnValue = runtimeScene.getGame().getInputManager().getTouchY(touchId);", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Touch identifier", + "name": "NewTouchId", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the speed factor for touch movement.", + "fullName": "Speed factor for touch movement", + "functionType": "ExpressionAndCondition", + "name": "TouchSpeedFactor", + "sentence": "the speed factor for touch movement", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "TouchSpeedFactor" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "TouchSpeedFactor", + "name": "SetTouchSpeedFactor", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchSpeedFactor", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [], + "objectGroups": [] + } + ], + "eventsBasedBehaviors": [ + { + "description": "Control camera rotations with a mouse.", + "fullName": "First person camera mouse mapper", + "name": "FirstPersonPointerMapper", + "objectType": "", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "TODO It's probably a bad idea to rotate the object around Y." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "MousePointerLock::isPointerLocked" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetAngle" + }, + "parameters": [ + "Object", + "+", + "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationY" + }, + "parameters": [ + "Object", + "Object3D", + "+", + "MousePointerLock::MovementY() * VerticalSpeed / SceneWindowHeight()" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationY" + }, + "parameters": [ + "Object", + "Object3D", + "=", + "clamp(Object.Object3D::RotationY(), VerticalAngleMin, VerticalAngleMax)" + ] + }, + { + "type": { + "value": "MousePointerLock::FirstPersonPointerMapper::LookFromObjectEyes" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "MouseButtonReleased" + }, + "parameters": [ + "", + "Left" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldLock", + "True", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "MousePointerLock::isPointerLocked" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "MousePointerLock::RequestPointerLock" + }, + "parameters": [ + "", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "MousePointerLock::FirstPersonPointerMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera to look though _PARAM1_ eyes. The object must look to the right when all its angles are 0 and the top of its head be toward Z+.", + "fullName": "Look through object eyes", + "functionType": "Action", + "group": "Layers and cameras", + "name": "LookFromObjectEyes", + "private": true, + "sentence": "Move the camera to look though _PARAM0_ eyes", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "CenterCameraOnObject" + }, + "parameters": [ + "", + "Object", + "", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "Object.Object3D::Z() + Object.Object3D::Depth() + OffsetZ", + "", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationX" + }, + "parameters": [ + "", + "=", + "- Object.Object3D::RotationY() + 90", + "GetArgumentAsString(\"Layer\")", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationY" + }, + "parameters": [ + "", + "=", + "Object.Object3D::RotationX()", + "GetArgumentAsString(\"Layer\")", + "" + ] + }, + { + "type": { + "value": "SetCameraAngle" + }, + "parameters": [ + "", + "=", + "Object.Angle() + 90", + "Object.Layer()", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "MousePointerLock::FirstPersonPointerMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the horizontal rotation speed factor of the object.", + "fullName": "Horizontal rotation speed factor", + "functionType": "ExpressionAndCondition", + "group": "First person camera mouse mapper configuration", + "name": "HorizontalSpeed", + "sentence": "the horizontal rotation speed factor", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "HorizontalSpeed" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "MousePointerLock::FirstPersonPointerMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "HorizontalSpeed", + "name": "SetHorizontalSpeed", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "HorizontalSpeed", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "MousePointerLock::FirstPersonPointerMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the vertical rotation speed factor of the object.", + "fullName": "Vertical rotation speed factor", + "functionType": "ExpressionAndCondition", + "group": "First person camera mouse mapper configuration", + "name": "VerticalSpeed", + "sentence": "the vertical rotation speed factor", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalSpeed" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "MousePointerLock::FirstPersonPointerMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalSpeed", + "name": "SetVerticalSpeed", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalSpeed", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "MousePointerLock::FirstPersonPointerMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the minimum vertical camera angle of the object.", + "fullName": "Minimum vertical camera angle", + "functionType": "ExpressionAndCondition", + "group": "First person camera mouse mapper configuration", + "name": "VerticalAngleMin", + "sentence": "the minimum vertical camera angle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalAngleMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "MousePointerLock::FirstPersonPointerMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalAngleMin", + "name": "SetVerticalAngleMin", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalAngleMin", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "MousePointerLock::FirstPersonPointerMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the maximum vertical camera angle of the object.", + "fullName": "Maximum vertical camera angle", + "functionType": "ExpressionAndCondition", + "group": "First person camera mouse mapper configuration", + "name": "VerticalAngleMax", + "sentence": "the maximum vertical camera angle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalAngleMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "MousePointerLock::FirstPersonPointerMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalAngleMax", + "name": "SetVerticalAngleMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalAngleMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "MousePointerLock::FirstPersonPointerMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the z position offset of the object.", + "fullName": "Z position offset", + "functionType": "ExpressionAndCondition", + "group": "First person camera mouse mapper configuration", + "name": "OffsetZ", + "sentence": "the z position offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "OffsetZ" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "MousePointerLock::FirstPersonPointerMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "OffsetZ", + "name": "SetOffsetZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OffsetZ", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "MousePointerLock::FirstPersonPointerMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D capability", + "extraInformation": [ + "Scene3D::Base3DBehavior" + ], + "choices": [], + "name": "Object3D" + }, + { + "value": "200", + "type": "Number", + "unit": "Dimensionless", + "label": "Horizontal rotation speed factor", + "name": "HorizontalSpeed" + }, + { + "value": "100", + "type": "Number", + "unit": "Dimensionless", + "label": "Vertical rotation speed factor", + "name": "VerticalSpeed" + }, + { + "value": "-90", + "type": "Number", + "unit": "DegreeAngle", + "label": "Minimum vertical camera angle", + "name": "VerticalAngleMin" + }, + { + "value": "90", + "type": "Number", + "unit": "DegreeAngle", + "label": "Maximum vertical camera angle", + "name": "VerticalAngleMax" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Z position offset", + "name": "OffsetZ" + }, + { + "value": "true", + "type": "Boolean", + "label": "Lock the pointer on click", + "name": "ShouldLock" + } + ], + "sharedPropertyDescriptors": [] + } + ], + "eventsBasedObjects": [] + }, + { + "author": "", + "category": "User interface", + "dimension": "", + "extensionNamespace": "", + "fullName": "Toggle switch", + "gdevelopVersion": ">=5.5.230", + "helpPath": "/objects/toggle-switch", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTIzLDIzSDljLTMuOSwwLTctMy4xLTctN3YwYzAtMy45LDMuMS03LDctN2gxNGMzLjksMCw3LDMuMSw3LDd2MEMzMCwxOS45LDI2LjksMjMsMjMsMjN6Ii8+DQo8Y2lyY2xlIGNsYXNzPSJzdDAiIGN4PSI5IiBjeT0iMTYiIHI9IjQiLz4NCjwvc3ZnPg0K", + "name": "SpriteToggleSwitch", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/Line Hero Pack/Master/SVG/Interface Elements/8d0cd8cd0c9318f4f6efde6ee6b4c192bd19306467f80c9970387a259300f895_Interface Elements_interface_ui_toggle_switch_on_off.svg", + "shortDescription": "Toggle switch that users can click or touch.", + "version": "2.0.2", + "description": [ + "Toggle switch that users can click or touch. The switch can be customized with sprites.", + "", + "There are ready-to-use toggle switches in the asset-store [settings UI pack](https://editor.gdevelop.io/?initial-dialog=asset-store&asset-pack=settings-ui-settings-ui)." + ], + "origin": { + "identifier": "SpriteToggleSwitch", + "name": "gdevelop-extension-store" + }, + "tags": [ + "ui", + "widget", + "toggle", + "switch", + "check box" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2", + "gqDaZjCfevOOxBYkK6zlhtZnXCg1" + ], + "changelog": [ + { + "version": "2.0.0", + "breaking": "- Toggle switches now use \"variants\", allowing easy swapping of their visual aspect. You will have to make some adjustments to existing toggle switches in your project. Follow this [migration documentation](https://wiki.gdevelop.io/gdevelop5/objects/custom-objects-prefab-template/migrate-to-variants/) to do these changes." + } + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [ + { + "description": "Check if the events are running for the editor.", + "fullName": "Editor is running", + "functionType": "Condition", + "name": "IsInGameEdition", + "private": true, + "sentence": "Events are running for the editor", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const game = runtimeScene.getGame();\r", + "eventsFunctionContext.returnValue = game.isInGameEdition && game.isInGameEdition();" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "IsInGameEdition" + } + ] + }, + "eventsBasedBehaviors": [ + { + "description": "The finite state machine used internally by the switch object.", + "fullName": "Switch finite state machine", + "helpPath": "", + "iconUrl": "", + "name": "SwitchFSM", + "objectType": "", + "previewIconUrl": "", + "private": true, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "WasChecked", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::SwitchFSM::IsChecked" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "WasChecked", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::ButtonFSM::IsClicked" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsChecked", + "Toggle", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteToggleSwitch::SwitchFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the toggle switch is checked.", + "fullName": "Is checked", + "functionType": "Condition", + "name": "IsChecked", + "sentence": "_PARAM0_ is checked", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsChecked", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteToggleSwitch::SwitchFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the toggle switch was checked in the current frame.", + "fullName": "Has just been checked", + "functionType": "Condition", + "name": "HasJustBeenChecked", + "sentence": "_PARAM0_ has just been checked", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsChecked", + "True", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "WasChecked", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteToggleSwitch::SwitchFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the toggle switch was unchecked in the current frame.", + "fullName": "Has just been unchecked", + "functionType": "Condition", + "name": "HasJustBeenUnchecked", + "sentence": "_PARAM0_ has just been unchecked", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "IsChecked", + "True", + "" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "WasChecked", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteToggleSwitch::SwitchFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the toggle switch was toggled in the current frame.", + "fullName": "Has just been toggled", + "functionType": "Condition", + "name": "HasJustBeenToggled", + "sentence": "_PARAM0_ has just been toggled", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::SwitchFSM::HasJustBeenChecked" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::SwitchFSM::HasJustBeenUnchecked" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteToggleSwitch::SwitchFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check (or uncheck) the toggle switch.", + "fullName": "Check (or uncheck)", + "functionType": "Action", + "name": "SetChecked", + "sentence": "Check _PARAM0_: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "False", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsChecked", + "False", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Avoid a started touch to interfere with a state change from events." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::SwitchFSM::IsChecked" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::ButtonFSM::ResetState" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsChecked", + "True", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteToggleSwitch::SwitchFSM::IsChecked" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::ButtonFSM::ResetState" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteToggleSwitch::SwitchFSM", + "type": "behavior" + }, + { + "defaultValue": "yes", + "description": "IsChecked", + "name": "Value", + "optional": true, + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Toggle the switch.", + "fullName": "Toggle", + "functionType": "Action", + "name": "ToggleChecked", + "sentence": "Toggle _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "PreviousIsChecked", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::SwitchFSM::IsChecked" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "PreviousIsChecked", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "PreviousIsChecked", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SwitchFSM::SetChecked" + }, + "parameters": [ + "Object", + "Behavior", + "no", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "PreviousIsChecked", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SwitchFSM::SetChecked" + }, + "parameters": [ + "Object", + "Behavior", + "yes", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteToggleSwitch::SwitchFSM", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "IsChecked" + }, + { + "functionName": "HasJustBeenChecked" + }, + { + "functionName": "HasJustBeenUnchecked" + }, + { + "functionName": "HasJustBeenToggled" + }, + { + "functionName": "SetChecked" + }, + { + "functionName": "ToggleChecked" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Button finite state machine", + "extraInformation": [ + "SpriteToggleSwitch::ButtonFSM" + ], + "choices": [], + "name": "ButtonFSM" + }, + { + "value": "false", + "type": "Boolean", + "label": "Is checked", + "name": "IsChecked" + }, + { + "value": "", + "type": "Boolean", + "label": "", + "hidden": true, + "name": "PreviousIsChecked" + }, + { + "value": "", + "type": "Boolean", + "label": "", + "hidden": true, + "name": "WasChecked" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ButtonFSM" + }, + { + "propertyName": "IsChecked" + }, + { + "propertyName": "PreviousIsChecked" + }, + { + "propertyName": "WasChecked" + } + ] + } + }, + { + "description": "The finite state machine used internally by the button object.", + "fullName": "Button finite state machine", + "helpPath": "", + "iconUrl": "", + "name": "ButtonFSM", + "objectType": "", + "previewIconUrl": "", + "private": true, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteToggleSwitch::IsInGameEdition" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Finite state machine", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "The \"Validated\" state only last one frame." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Validated\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"Idle\"" + ] + } + ] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Check position", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Make sure the cursor position is only checked once per frame." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "MouseIsInside", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldCheckHovering", + "True", + "" + ] + }, + { + "type": { + "value": "CollisionPoint" + }, + "parameters": [ + "Object", + "MouseOnlyCursorX(Object.Layer(), 0)", + "MouseOnlyCursorY(Object.Layer(), 0)" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "MouseIsInside", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Touches are always pressed, so ShouldCheckHovering doesn't matter." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "TouchIsInside", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "!=", + "0" + ] + }, + { + "type": { + "value": "CollisionPoint" + }, + "parameters": [ + "Object", + "TouchX(TouchId, Object.Layer(), 0)", + "TouchY(TouchId, Object.Layer(), 0)" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "TouchIsInside", + "True", + "" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Handle touch start", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Index", + "=", + "0" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "StartedTouchOrMouseCount()", + "conditions": [], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CollisionPoint" + }, + "parameters": [ + "Object", + "TouchX(StartedTouchOrMouseId(Index), Object.Layer(), 0)", + "TouchY(StartedTouchOrMouseId(Index), Object.Layer(), 0)" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "StartedTouchOrMouseId(Index)" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "TouchIsInside", + "True", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "SpriteToggleSwitch::ButtonFSM::PropertyState" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "\"Hovered\"" + ] + }, + { + "type": { + "value": "SpriteToggleSwitch::ButtonFSM::PropertyState" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "\"Idle\"" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedInside\"" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Index", + "+", + "1" + ] + } + ] + } + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Apply position changes", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "MouseIsInside", + "True", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Hovered\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"Idle\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "MouseIsInside", + "True", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Idle\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"Hovered\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "TouchIsInside", + "True", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedInside\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedOutside\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "TouchIsInside", + "True", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedOutside\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedInside\"" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Handle touch end", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasTouchEnded" + }, + "parameters": [ + "", + "TouchId" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "0" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedInside\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"Validated\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedInside\"" + ] + }, + { + "type": { + "inverted": true, + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Validated\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"Idle\"" + ] + } + ] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteToggleSwitch::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDeActivate", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::ButtonFSM::ResetState" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteToggleSwitch::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Reset the state of the button.", + "fullName": "Reset state", + "functionType": "Action", + "name": "ResetState", + "sentence": "Reset the button state of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"Idle\"" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "0" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteToggleSwitch::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is not used.", + "fullName": "Is idle", + "functionType": "Condition", + "name": "IsIdle", + "sentence": "_PARAM0_ is idle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Idle\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteToggleSwitch::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button was just clicked.", + "fullName": "Is clicked", + "functionType": "Condition", + "name": "IsClicked", + "sentence": "_PARAM0_ is clicked", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Validated\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteToggleSwitch::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the cursor is hovered over the button.", + "fullName": "Is hovered", + "functionType": "Condition", + "name": "IsHovered", + "sentence": "_PARAM0_ is hovered", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Hovered\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteToggleSwitch::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is either hovered or pressed but not hovered.", + "fullName": "Is focused", + "functionType": "Condition", + "name": "IsFocused", + "sentence": "_PARAM0_ is focused", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Hovered\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedOutside\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteToggleSwitch::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is currently being pressed with mouse or touch.", + "fullName": "Is pressed", + "functionType": "Condition", + "name": "IsPressed", + "sentence": "_PARAM0_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedInside\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteToggleSwitch::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is currently being pressed outside with mouse or touch.", + "fullName": "Is held outside", + "functionType": "Condition", + "name": "IsPressedOutside", + "sentence": "_PARAM0_ is held outside", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedOutside\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteToggleSwitch::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the touch id that is using the button or 0 if none.", + "fullName": "Touch id", + "functionType": "ExpressionAndCondition", + "name": "TouchId", + "sentence": "the touch id", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "TouchId" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteToggleSwitch::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPostEvents" + }, + { + "functionName": "onDeActivate" + }, + { + "functionName": "ResetState" + }, + { + "functionName": "IsIdle" + }, + { + "functionName": "IsClicked" + }, + { + "functionName": "IsHovered" + }, + { + "functionName": "IsFocused" + }, + { + "functionName": "IsPressed" + }, + { + "functionName": "IsPressedOutside" + }, + { + "functionName": "TouchId" + } + ] + }, + "propertyDescriptors": [ + { + "value": "true", + "type": "Boolean", + "label": "", + "description": "Should check hovering", + "hidden": true, + "name": "ShouldCheckHovering" + }, + { + "value": "Idle", + "type": "Choice", + "label": "State", + "choices": [ + { + "label": "Idle", + "value": "Idle" + }, + { + "label": "Hovered", + "value": "Hovered" + }, + { + "label": "PressedInside", + "value": "PressedInside" + }, + { + "label": "PressedOutside", + "value": "PressedOutside" + }, + { + "label": "Validated", + "value": "Validated" + } + ], + "hidden": true, + "name": "State" + }, + { + "value": "0", + "type": "Number", + "label": "Touch id", + "hidden": true, + "name": "TouchId" + }, + { + "value": "", + "type": "Boolean", + "label": "Touch is inside", + "hidden": true, + "name": "TouchIsInside" + }, + { + "value": "", + "type": "Boolean", + "label": "Mouse is inside", + "hidden": true, + "name": "MouseIsInside" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "Index" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ShouldCheckHovering" + }, + { + "propertyName": "State" + }, + { + "propertyName": "TouchId" + }, + { + "propertyName": "TouchIsInside" + }, + { + "propertyName": "MouseIsInside" + }, + { + "propertyName": "Index" + } + ] + } + } + ], + "eventsBasedObjects": [ + { + "areaMaxX": 64, + "areaMaxY": 64, + "areaMaxZ": 64, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "defaultName": "ToggleSwitch", + "description": "A toggle switch that users can click or touch.", + "fullName": "Toggle switch", + "helpPath": "", + "iconUrl": "", + "isUsingLegacyInstancesRenderer": false, + "name": "SpriteToggleSwitch", + "previewIconUrl": "", + "objects": [ + { + "adaptCollisionMaskAutomatically": true, + "assetStoreId": "", + "name": "State", + "type": "Sprite", + "updateIfNotVisible": false, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "ButtonFSM", + "type": "SpriteToggleSwitch::ButtonFSM", + "ShouldCheckHovering": true, + "State": "Idle", + "TouchId": 0, + "TouchIsInside": false, + "MouseIsInside": false, + "Index": 0 + }, + { + "name": "SwitchFSM", + "type": "SpriteToggleSwitch::SwitchFSM", + "ButtonFSM": "ButtonFSM", + "IsChecked": false, + "WasChecked": false + } + ], + "animations": [ + { + "name": "Unchecked", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [] + } + ] + }, + { + "name": "Checked", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [] + } + ] + } + ] + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "State" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [ + { + "angle": 0, + "customSize": true, + "depth": 0, + "height": 64, + "layer": "", + "name": "State", + "persistentUuid": "bda60bb1-c312-4710-baf7-ec7a3887c076", + "width": 64, + "x": 0, + "y": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": [], + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Pass the configuration to the behavior.\nThe PreviousIsChecked property allows to detect a change of \"intial value\" on hot reload." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsChecked", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SwitchFSM::SetChecked" + }, + "parameters": [ + "State", + "SwitchFSM", + "", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "PreviousIsChecked", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsChecked", + "False", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SwitchFSM::SetChecked" + }, + "parameters": [ + "State", + "SwitchFSM", + "no", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "PreviousIsChecked", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::UpdateStateAnimation" + }, + "parameters": [ + "Object", + "" + ] + }, + { + "type": { + "value": "ChangeSprite" + }, + "parameters": [ + "State", + "=", + "State.AnimationFrameCount() - 1" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteToggleSwitch::SpriteToggleSwitch", + "type": "object" + } + ], + "objectGroups": [ + { + "name": "Background", + "objects": [ + { + "name": "Idle" + }, + { + "name": "Hovered" + }, + { + "name": "Pressed" + } + ] + } + ] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsChecked", + "True", + "" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "PreviousIsChecked", + "False", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::SetChecked" + }, + "parameters": [ + "Object", + "yes", + "no" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "PreviousIsChecked", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsChecked", + "False", + "" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "PreviousIsChecked", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::SetChecked" + }, + "parameters": [ + "Object", + "SwitchFSM", + "no" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "PreviousIsChecked", + "False", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteToggleSwitch::SpriteToggleSwitch", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::UpdateStateAnimation" + }, + "parameters": [ + "Object", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteToggleSwitch::SpriteToggleSwitch", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the toggle switch is checked.", + "fullName": "Is checked", + "functionType": "Condition", + "name": "IsChecked", + "sentence": "_PARAM0_ is checked", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::SwitchFSM::IsChecked" + }, + "parameters": [ + "State", + "SwitchFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteToggleSwitch::SpriteToggleSwitch", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the toggle switch was checked in the current frame.", + "fullName": "Has just been checked", + "functionType": "Condition", + "name": "HasJustBeenChecked", + "sentence": "_PARAM0_ has just been checked", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::SwitchFSM::HasJustBeenChecked" + }, + "parameters": [ + "State", + "SwitchFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteToggleSwitch::SpriteToggleSwitch", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the toggle switch was unchecked in the current frame.", + "fullName": "Has just been unchecked", + "functionType": "Condition", + "name": "HasJustBeenUnchecked", + "sentence": "_PARAM0_ has just been unchecked", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::SwitchFSM::HasJustBeenUnchecked" + }, + "parameters": [ + "State", + "SwitchFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteToggleSwitch::SpriteToggleSwitch", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the toggle switch was checked or unchecked in the current frame.", + "fullName": "Has just been toggled", + "functionType": "Condition", + "name": "HasJustBeenToggled", + "sentence": "_PARAM0_ has just been toggled", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::SwitchFSM::HasJustBeenToggled" + }, + "parameters": [ + "State", + "SwitchFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteToggleSwitch::SpriteToggleSwitch", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check (or uncheck) the toggle switch.", + "fullName": "Check (or uncheck)", + "functionType": "Action", + "name": "SetChecked", + "sentence": "Check _PARAM0_: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SwitchFSM::SetChecked" + }, + "parameters": [ + "State", + "SwitchFSM", + "no", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SwitchFSM::SetChecked" + }, + "parameters": [ + "State", + "SwitchFSM", + "yes", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteToggleSwitch::SpriteToggleSwitch", + "type": "object" + }, + { + "defaultValue": "yes", + "description": "IsChecked", + "name": "Value", + "optional": true, + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Toggle the switch.", + "fullName": "Toggle", + "functionType": "Action", + "name": "ToggleChecked", + "sentence": "Toggle _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SwitchFSM::ToggleChecked" + }, + "parameters": [ + "State", + "SwitchFSM", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteToggleSwitch::SpriteToggleSwitch", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is not used.", + "fullName": "Is idle", + "functionType": "Condition", + "name": "IsIdle", + "private": true, + "sentence": "_PARAM0_ is idle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::ButtonFSM::IsIdle" + }, + "parameters": [ + "State", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteToggleSwitch::SpriteToggleSwitch", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button was just clicked.", + "fullName": "Is clicked", + "functionType": "Condition", + "name": "IsClicked", + "private": true, + "sentence": "_PARAM0_ is clicked", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::ButtonFSM::IsClicked" + }, + "parameters": [ + "State", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteToggleSwitch::SpriteToggleSwitch", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the cursor is hovered over the button.", + "fullName": "Is hovered", + "functionType": "Condition", + "name": "IsHovered", + "sentence": "_PARAM0_ is hovered", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::ButtonFSM::IsHovered" + }, + "parameters": [ + "State", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteToggleSwitch::SpriteToggleSwitch", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is either hovered or pressed but not hovered.", + "fullName": "Is focused", + "functionType": "Condition", + "name": "IsFocused", + "private": true, + "sentence": "_PARAM0_ is focused", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::ButtonFSM::IsFocused" + }, + "parameters": [ + "State", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteToggleSwitch::SpriteToggleSwitch", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is currently being pressed with mouse or touch.", + "fullName": "Is pressed", + "functionType": "Condition", + "name": "IsPressed", + "sentence": "_PARAM0_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::ButtonFSM::IsPressed" + }, + "parameters": [ + "State", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteToggleSwitch::SpriteToggleSwitch", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is currently being pressed outside with mouse or touch.", + "fullName": "Is held outside", + "functionType": "Condition", + "name": "IsPressedOutside", + "sentence": "_PARAM0_ is held outside", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::ButtonFSM::IsPressedOutside" + }, + "parameters": [ + "State", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteToggleSwitch::SpriteToggleSwitch", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Update the state animation.", + "fullName": "Update state animation", + "functionType": "Action", + "name": "UpdateStateAnimation", + "private": true, + "sentence": "Update the state animation of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::IsChecked" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "AnimationName" + }, + "parameters": [ + "State", + "\"Unchecked\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetAnimationName" + }, + "parameters": [ + "State", + "\"Checked\"" + ] + }, + { + "type": { + "value": "PlayAnimation" + }, + "parameters": [ + "State" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteToggleSwitch::SpriteToggleSwitch::IsChecked" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "AnimationName" + }, + "parameters": [ + "State", + "\"Checked\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetAnimationName" + }, + "parameters": [ + "State", + "\"Unchecked\"" + ] + }, + { + "type": { + "value": "PlayAnimation" + }, + "parameters": [ + "State" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteToggleSwitch::SpriteToggleSwitch", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "De/activate interactions with the button.", + "fullName": "De/activate interactions", + "functionType": "Action", + "name": "Activate", + "sentence": "Activate interactions with _PARAM0_: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldActivate", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "State", + "ButtonFSM", + "yes" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldActivate", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "State", + "ButtonFSM", + "no" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteToggleSwitch::SpriteToggleSwitch", + "type": "object" + }, + { + "description": "Activate", + "name": "ShouldActivate", + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Check if interactions are activated on the button.", + "fullName": "Interactions activated", + "functionType": "Condition", + "name": "IsActivated", + "sentence": "Interactions on _PARAM0_ are activated", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BehaviorActivated" + }, + "parameters": [ + "State", + "ButtonFSM" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteToggleSwitch::SpriteToggleSwitch", + "type": "object" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "onHotReloading" + }, + { + "functionName": "doStepPostEvents" + }, + { + "functionName": "IsChecked" + }, + { + "functionName": "HasJustBeenChecked" + }, + { + "functionName": "HasJustBeenUnchecked" + }, + { + "functionName": "HasJustBeenToggled" + }, + { + "functionName": "SetChecked" + }, + { + "functionName": "ToggleChecked" + }, + { + "functionName": "IsIdle" + }, + { + "functionName": "IsClicked" + }, + { + "functionName": "IsHovered" + }, + { + "functionName": "IsFocused" + }, + { + "functionName": "IsPressed" + }, + { + "functionName": "IsPressedOutside" + }, + { + "functionName": "UpdateStateAnimation" + }, + { + "functionName": "Activate" + }, + { + "functionName": "IsActivated" + } + ] + }, + "propertyDescriptors": [ + { + "value": "true", + "type": "Boolean", + "label": "Is checked", + "name": "IsChecked" + }, + { + "value": "", + "type": "Boolean", + "label": "", + "description": "It's used to detect a change at hot reload.", + "hidden": true, + "name": "PreviousIsChecked" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "IsChecked" + }, + { + "propertyName": "PreviousIsChecked" + } + ] + }, + "variants": [ + { + "areaMaxX": 202, + "areaMaxY": 64, + "areaMaxZ": 64, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "name": "Controls toggle", + "objects": [ + { + "adaptCollisionMaskAutomatically": true, + "assetStoreId": "", + "name": "State", + "type": "Sprite", + "updateIfNotVisible": false, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "ButtonFSM", + "type": "SpriteToggleSwitch::ButtonFSM", + "ShouldCheckHovering": true, + "State": "Idle", + "TouchId": 0, + "TouchIsInside": false, + "MouseIsInside": false, + "Index": 0 + }, + { + "name": "SwitchFSM", + "type": "SpriteToggleSwitch::SwitchFSM", + "ButtonFSM": "ButtonFSM", + "IsChecked": false, + "WasChecked": false + } + ], + "animations": [ + { + "name": "Unchecked", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": true, + "image": "assets\\StarterToggle_Keyboard.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 0, + "y": 0 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [ + [ + { + "x": 2, + "y": 1 + }, + { + "x": 201, + "y": 1 + }, + { + "x": 201, + "y": 63 + }, + { + "x": 2, + "y": 63 + } + ] + ] + } + ] + } + ] + }, + { + "name": "Checked", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": true, + "image": "assets\\StarterToggle_Touch.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 0, + "y": 0 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [ + [ + { + "x": 2, + "y": 1 + }, + { + "x": 201, + "y": 1 + }, + { + "x": 201, + "y": 63 + }, + { + "x": 2, + "y": 63 + } + ] + ] + } + ] + } + ] + } + ] + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "State" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [ + { + "angle": 0, + "customSize": true, + "depth": 0, + "height": 64, + "layer": "", + "name": "State", + "persistentUuid": "bda60bb1-c312-4710-baf7-ec7a3887c076", + "width": 202, + "x": 0, + "y": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": {} + } + ] + } + ] + }, + { + "author": "", + "category": "User interface", + "dimension": "2D", + "extensionNamespace": "", + "fullName": "Button states and effects", + "gdevelopVersion": ">=5.5.222", + "helpPath": "/objects/button", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLWdlc3R1cmUtdGFwLWJ1dHRvbiIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0xMyA1QzE1LjIxIDUgMTcgNi43OSAxNyA5QzE3IDEwLjUgMTYuMiAxMS43NyAxNSAxMi40NlYxMS4yNEMxNS42MSAxMC42OSAxNiA5Ljg5IDE2IDlDMTYgNy4zNCAxNC42NiA2IDEzIDZTMTAgNy4zNCAxMCA5QzEwIDkuODkgMTAuMzkgMTAuNjkgMTEgMTEuMjRWMTIuNDZDOS44IDExLjc3IDkgMTAuNSA5IDlDOSA2Ljc5IDEwLjc5IDUgMTMgNU0yMCAyMC41QzE5Ljk3IDIxLjMyIDE5LjMyIDIxLjk3IDE4LjUgMjJIMTNDMTIuNjIgMjIgMTIuMjYgMjEuODUgMTIgMjEuNTdMOCAxNy4zN0w4Ljc0IDE2LjZDOC45MyAxNi4zOSA5LjIgMTYuMjggOS41IDE2LjI4SDkuN0wxMiAxOFY5QzEyIDguNDUgMTIuNDUgOCAxMyA4UzE0IDguNDUgMTQgOVYxMy40N0wxNS4yMSAxMy42TDE5LjE1IDE1Ljc5QzE5LjY4IDE2LjAzIDIwIDE2LjU2IDIwIDE3LjE0VjIwLjVNMjAgMkg0QzIuOSAyIDIgMi45IDIgNFYxMkMyIDEzLjExIDIuOSAxNCA0IDE0SDhWMTJMNCAxMkw0IDRIMjBMMjAgMTJIMThWMTRIMjBWMTMuOTZMMjAuMDQgMTRDMjEuMTMgMTQgMjIgMTMuMDkgMjIgMTJWNEMyMiAyLjkgMjEuMTEgMiAyMCAyWiIgLz48L3N2Zz4=", + "name": "ButtonStates", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/753a9a794bd885058159b7509f06f5a8f67f72decfccb9a1b0efee26f41c3c4c_gesture-tap-button.svg", + "shortDescription": "Use any object as a button and change appearance according to user interactions.", + "version": "1.4.1", + "description": [ + "Use the \"Button states\" behavior to track user interactions with an object, including:", + "", + "- Hovered", + "- Pressed", + "- Clicked", + "- Idle", + "", + "Add additional behaviors to make juicy buttons with animated responses to user input:", + "", + "- Size", + "- Color", + "- Animation", + "- Object effects" + ], + "origin": { + "identifier": "ButtonStates", + "name": "gdevelop-extension-store" + }, + "tags": [ + "ui", + "button" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2", + "gqDaZjCfevOOxBYkK6zlhtZnXCg1" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT" + }, + "eventsBasedBehaviors": [ + { + "description": "Use objects as buttons.", + "fullName": "Button states", + "helpPath": "", + "iconUrl": "", + "name": "ButtonFSM", + "objectType": "", + "previewIconUrl": "", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Finite state machine", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "The \"Validated\" state only last one frame." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Validated\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"Idle\"" + ] + } + ] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Check position", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Make sure the cursor position is only checked once per frame." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "MouseIsInside", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldCheckHovering", + "True", + "" + ] + }, + { + "type": { + "value": "CollisionPoint" + }, + "parameters": [ + "Object", + "MouseOnlyCursorX(Object.Layer(), 0)", + "MouseOnlyCursorY(Object.Layer(), 0)" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "MouseIsInside", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Touches are always pressed, so ShouldCheckHovering doesn't matter." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "TouchIsInside", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "!=", + "0" + ] + }, + { + "type": { + "value": "CollisionPoint" + }, + "parameters": [ + "Object", + "TouchX(TouchId, Object.Layer(), 0)", + "TouchY(TouchId, Object.Layer(), 0)" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "TouchIsInside", + "True", + "" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Handle touch start", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Index", + "=", + "0" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "StartedTouchOrMouseCount()", + "conditions": [], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CollisionPoint" + }, + "parameters": [ + "Object", + "TouchX(StartedTouchOrMouseId(Index), Object.Layer(), 0)", + "TouchY(StartedTouchOrMouseId(Index), Object.Layer(), 0)" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "StartedTouchOrMouseId(Index)" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "TouchIsInside", + "True", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Hovered\"" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Idle\"" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedInside\"" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Index", + "+", + "1" + ] + } + ] + } + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Apply position changes", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "MouseIsInside", + "True", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Hovered\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"Idle\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "MouseIsInside", + "True", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Idle\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"Hovered\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "TouchIsInside", + "True", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedInside\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedOutside\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "TouchIsInside", + "True", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedOutside\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedInside\"" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Handle touch end", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasTouchEnded" + }, + "parameters": [ + "", + "TouchId" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "0" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedInside\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"Validated\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedInside\"" + ] + }, + { + "type": { + "inverted": true, + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Validated\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"Idle\"" + ] + } + ] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDeActivate", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::ResetState" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Reset the state of the button.", + "fullName": "Reset state", + "functionType": "Action", + "name": "ResetState", + "private": true, + "sentence": "Reset the button state of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"Idle\"" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "0" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is not used.", + "fullName": "Is idle", + "functionType": "Condition", + "name": "IsIdle", + "sentence": "_PARAM0_ is idle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Idle\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button was just clicked.", + "fullName": "Is clicked", + "functionType": "Condition", + "name": "IsClicked", + "sentence": "_PARAM0_ is clicked", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Validated\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the cursor is hovered over the button.", + "fullName": "Is hovered", + "functionType": "Condition", + "name": "IsHovered", + "sentence": "_PARAM0_ is hovered", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Hovered\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is either hovered or pressed but not hovered.", + "fullName": "Is focused", + "functionType": "Condition", + "name": "IsFocused", + "sentence": "_PARAM0_ is focused", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Hovered\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedOutside\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is currently being pressed with mouse or touch.", + "fullName": "Is pressed", + "functionType": "Condition", + "name": "IsPressed", + "sentence": "_PARAM0_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedInside\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is currently being pressed outside with mouse or touch.", + "fullName": "Is held outside", + "functionType": "Condition", + "name": "IsPressedOutside", + "sentence": "_PARAM0_ is held outside", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedOutside\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the touch id that is using the button or 0 if none.", + "fullName": "Touch id", + "functionType": "ExpressionAndCondition", + "name": "TouchId", + "sentence": "the touch id", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "TouchId" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "onDeActivate" + }, + { + "functionName": "ResetState" + }, + { + "functionName": "IsIdle" + }, + { + "functionName": "IsClicked" + }, + { + "functionName": "IsHovered" + }, + { + "functionName": "IsFocused" + }, + { + "functionName": "IsPressed" + }, + { + "functionName": "IsPressedOutside" + }, + { + "functionName": "TouchId" + } + ] + }, + "propertyDescriptors": [ + { + "value": "true", + "type": "Boolean", + "label": "", + "description": "Should check hovering", + "hidden": true, + "name": "ShouldCheckHovering" + }, + { + "value": "Idle", + "type": "Choice", + "label": "State", + "choices": [ + { + "label": "Idle", + "value": "Idle" + }, + { + "label": "Hovered", + "value": "Hovered" + }, + { + "label": "PressedInside", + "value": "PressedInside" + }, + { + "label": "PressedOutside", + "value": "PressedOutside" + }, + { + "label": "Validated", + "value": "Validated" + } + ], + "hidden": true, + "name": "State" + }, + { + "value": "0", + "type": "Number", + "label": "Touch id", + "hidden": true, + "name": "TouchId" + }, + { + "value": "", + "type": "Boolean", + "label": "Touch is inside", + "hidden": true, + "name": "TouchIsInside" + }, + { + "value": "", + "type": "Boolean", + "label": "Mouse is inside", + "hidden": true, + "name": "MouseIsInside" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "Index" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ShouldCheckHovering" + }, + { + "propertyName": "State" + }, + { + "propertyName": "TouchId" + }, + { + "propertyName": "TouchIsInside" + }, + { + "propertyName": "MouseIsInside" + }, + { + "propertyName": "Index" + } + ] + } + }, + { + "description": "Enable effects on buttons based on their state.", + "fullName": "Button object effects", + "helpPath": "", + "iconUrl": "", + "name": "ButtonObjectEffects", + "objectType": "", + "previewIconUrl": "", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "IdleEffect", + "yes" + ] + }, + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "FocusedEffect", + "no" + ] + }, + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "PressedEffect", + "no" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffects", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "States", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "IdleEffect", + "no" + ] + }, + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "FocusedEffect", + "no" + ] + }, + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "PressedEffect", + "no" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "IdleEffect", + "!=", + "\"\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonFSM::IsIdle" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "IdleEffect", + "yes" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "FocusedEffect", + "!=", + "\"\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonFSM::IsFocused" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "FocusedEffect", + "yes" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PressedEffect", + "!=", + "\"\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "PressedEffect", + "yes" + ] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffects", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the idle state effect of the object.", + "fullName": "Idle state effect", + "functionType": "ExpressionAndCondition", + "group": "Button object effects configuration", + "name": "IdleEffect", + "sentence": "the idle state effect", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "IdleEffect" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffects", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "IdleEffect", + "group": "Button object effects configuration", + "name": "SetIdleEffect", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "IdleEffect", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffects", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the focused state effect of the object. The state is Focused when the button is hovered or held outside.", + "fullName": "Focused state effect", + "functionType": "ExpressionAndCondition", + "group": "Button object effects configuration", + "name": "FocusedEffect", + "sentence": "the focused state effect", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "FocusedEffect" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffects", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FocusedEffect", + "group": "Button object effects configuration", + "name": "SetFocusedEffect", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "FocusedEffect", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffects", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the pressed state effect of the object.", + "fullName": "Pressed state effect", + "functionType": "ExpressionAndCondition", + "group": "Button object effects configuration", + "name": "PressedEffect", + "sentence": "the pressed state effect", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "PressedEffect" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffects", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "PressedEffect", + "group": "Button object effects configuration", + "name": "SetPressedEffect", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PressedEffect", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffects", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPreEvents" + }, + { + "folderName": "Button object effects configuration", + "children": [ + { + "functionName": "IdleEffect" + }, + { + "functionName": "SetIdleEffect" + }, + { + "functionName": "FocusedEffect" + }, + { + "functionName": "SetFocusedEffect" + }, + { + "functionName": "PressedEffect" + }, + { + "functionName": "SetPressedEffect" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Button states", + "extraInformation": [ + "ButtonStates::ButtonFSM" + ], + "choices": [], + "name": "ButtonFSM" + }, + { + "value": "", + "type": "Behavior", + "label": "Effect capability", + "extraInformation": [ + "EffectCapability::EffectBehavior" + ], + "choices": [], + "name": "Effect" + }, + { + "value": "", + "type": "String", + "label": "Idle state effect", + "group": "Effects", + "name": "IdleEffect" + }, + { + "value": "", + "type": "String", + "label": "Focused state effect", + "description": "The state is Focused when the button is hovered or held outside.", + "group": "Effects", + "name": "FocusedEffect" + }, + { + "value": "", + "type": "String", + "label": "Pressed state effect", + "group": "Effects", + "name": "PressedEffect" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ButtonFSM" + }, + { + "propertyName": "Effect" + }, + { + "folderName": "Effects", + "children": [ + { + "propertyName": "IdleEffect" + }, + { + "propertyName": "FocusedEffect" + }, + { + "propertyName": "PressedEffect" + } + ] + } + ] + } + }, + { + "description": "Change the animation of buttons according to their state.", + "fullName": "Button animation", + "helpPath": "", + "iconUrl": "", + "name": "ButtonAnimationName", + "objectType": "", + "previewIconUrl": "", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "AnimatableCapability::AnimatableBehavior::SetName" + }, + "parameters": [ + "Object", + "Animation", + "=", + "IdleAnimationName" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonAnimationName", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Change based on state", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsIdle" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "AnimatableCapability::AnimatableBehavior::SetName" + }, + "parameters": [ + "Object", + "Animation", + "=", + "IdleAnimationName" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsFocused" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "AnimatableCapability::AnimatableBehavior::SetName" + }, + "parameters": [ + "Object", + "Animation", + "=", + "FocusedAnimationName" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "AnimatableCapability::AnimatableBehavior::SetName" + }, + "parameters": [ + "Object", + "Animation", + "=", + "PressedAnimationName" + ] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonAnimationName", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the idle state animation name of the object.", + "fullName": "Idle state animation name", + "functionType": "ExpressionAndCondition", + "group": "Button animation configuration", + "name": "IdleAnimationName", + "sentence": "the idle state animation name", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "IdleAnimationName" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonAnimationName", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "IdleAnimationName", + "group": "Button animation configuration", + "name": "SetIdleAnimationName", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "IdleAnimationName", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonAnimationName", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the focused state animation name of the object. The state is Focused when the button is hovered or held outside.", + "fullName": "Focused state animation name", + "functionType": "ExpressionAndCondition", + "group": "Button animation configuration", + "name": "FocusedAnimationName", + "sentence": "the focused state animation name", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "FocusedAnimationName" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonAnimationName", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FocusedAnimationName", + "group": "Button animation configuration", + "name": "SetFocusedAnimationName", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "FocusedAnimationName", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonAnimationName", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the pressed state animation name of the object.", + "fullName": "Pressed state animation name", + "functionType": "ExpressionAndCondition", + "group": "Button animation configuration", + "name": "PressedAnimationName", + "sentence": "the pressed state animation name", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "PressedAnimationName" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonAnimationName", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "PressedAnimationName", + "group": "Button animation configuration", + "name": "SetPressedAnimationName", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PressedAnimationName", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonAnimationName", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPreEvents" + }, + { + "folderName": "Button animation configuration", + "children": [ + { + "functionName": "IdleAnimationName" + }, + { + "functionName": "SetIdleAnimationName" + }, + { + "functionName": "FocusedAnimationName" + }, + { + "functionName": "SetFocusedAnimationName" + }, + { + "functionName": "PressedAnimationName" + }, + { + "functionName": "SetPressedAnimationName" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Animatable capability", + "extraInformation": [ + "AnimatableCapability::AnimatableBehavior" + ], + "choices": [], + "name": "Animation" + }, + { + "value": "", + "type": "Behavior", + "label": "Button states", + "extraInformation": [ + "ButtonStates::ButtonFSM" + ], + "choices": [], + "name": "ButtonFSM" + }, + { + "value": "Idle", + "type": "String", + "label": "Idle state animation name", + "group": "Animation", + "name": "IdleAnimationName" + }, + { + "value": "Focused", + "type": "String", + "label": "Focused state animation name", + "description": "The state is Focused when the button is hovered or held outside.", + "group": "Animation", + "name": "FocusedAnimationName" + }, + { + "value": "Pressed", + "type": "String", + "label": "Pressed state animation name", + "group": "Animation", + "name": "PressedAnimationName" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Animation" + }, + { + "propertyName": "ButtonFSM" + }, + { + "folderName": "Animation", + "children": [ + { + "propertyName": "IdleAnimationName" + }, + { + "propertyName": "FocusedAnimationName" + }, + { + "propertyName": "PressedAnimationName" + } + ] + } + ] + } + }, + { + "description": "Smoothly change an effect on buttons according to their state.", + "fullName": "Button object effect tween", + "helpPath": "", + "iconUrl": "", + "name": "ButtonObjectEffectTween", + "objectType": "", + "previewIconUrl": "", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "EffectValue", + "=", + "IdleValue" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "States", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsIdle" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "!=", + "\"Idle\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Idle\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonObjectEffectTween::FadeOut" + }, + "parameters": [ + "Object", + "Behavior", + "IdleValue", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsFocused" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Idle\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Focused\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonObjectEffectTween::FadeIn" + }, + "parameters": [ + "Object", + "Behavior", + "FocusedValue", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Pressed\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Focused\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonObjectEffectTween::FadeOut" + }, + "parameters": [ + "Object", + "Behavior", + "FocusedValue", + "" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "!=", + "\"Pressed\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Pressed\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonObjectEffectTween::FadeIn" + }, + "parameters": [ + "Object", + "Behavior", + "PressedValue", + "" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Tween", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "TweenState", + "=", + "\"FadeIn\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "ButtonStates::ButtonObjectEffectTween::PlayTween" + }, + "parameters": [ + "Object", + "Behavior", + "FadeInDuration", + "FadeInEasing", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "TweenState", + "=", + "\"FadeOut\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "ButtonStates::ButtonObjectEffectTween::PlayTween" + }, + "parameters": [ + "Object", + "Behavior", + "FadeOutDuration", + "FadeOutEasing", + "" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Toggle effect", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldDisableEffectInIdle", + "True", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsIdle" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "TweenState", + "=", + "\"NoTween\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "EffectName", + "no" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "ButtonStates::ButtonFSM::IsIdle" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "EffectName", + "yes" + ] + } + ] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Time delta", + "functionType": "Expression", + "name": "TimeDelta", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "TimeDelta() * LayerTimeScale(Object.Layer())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Fade in", + "functionType": "Action", + "name": "FadeIn", + "private": true, + "sentence": "_PARAM0_ fade in to _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TweenTime", + "=", + "0" + ] + }, + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "TweenState", + "=", + "\"FadeIn\"" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TweenInitialValue", + "=", + "EffectValue" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TweenTargetedValue", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "fullName": "Fade out", + "functionType": "Action", + "name": "FadeOut", + "private": true, + "sentence": "_PARAM0_ fade out to _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TweenTime", + "=", + "0" + ] + }, + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "TweenState", + "=", + "\"FadeOut\"" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TweenInitialValue", + "=", + "EffectValue" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TweenTargetedValue", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "fullName": "Play tween", + "functionType": "Action", + "name": "PlayTween", + "private": true, + "sentence": "Tween the effect property of object _PARAM0_ over _PARAM2_ seconds with _PARAM3_ easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TweenTime", + "<", + "Duration" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TweenTime", + "+", + "Object.Behavior::TimeDelta()" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "EffectValue", + "=", + "Tween::Ease(Easing, TweenInitialValue, TweenTargetedValue, TweenTime / Duration)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TweenTime", + ">=", + "Duration" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "TweenState", + "=", + "\"NoTween\"" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "EffectValue", + "=", + "TweenTargetedValue" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "EffectCapability::EffectBehavior::SetEffectDoubleParameter" + }, + "parameters": [ + "Object", + "Effect", + "EffectName", + "EffectProperty", + "EffectValue" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "supplementaryInformation": "[]", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "the effect name of the object.", + "fullName": "Effect name", + "functionType": "ExpressionAndCondition", + "group": "Button effect tween configuration", + "name": "EffectName", + "sentence": "the effect name", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "EffectName" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the effect parameter of the object. The effect parameter names can be found in the effects tab with the \"Show parameter names\" action of the drop down menu.", + "fullName": "Effect parameter", + "functionType": "ExpressionAndCondition", + "group": "Button effect tween configuration", + "name": "EffectProperty", + "sentence": "the effect parameter", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "EffectProperty" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Change the effect parameter of the object. The effect parameter names can be found in the effects tab with the \"Show parameter names\" action of the drop down menu.", + "fullName": "Effect parameter", + "functionType": "Action", + "getterName": "EffectName", + "group": "Button effect tween configuration", + "name": "SetEffectProperty", + "sentence": "Change the tweened effect of _PARAM0_ to _PARAM2_ with parameter _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "EffectName", + "=", + "NewEffectName" + ] + }, + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "EffectProperty", + "=", + "NewPropertyName" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + }, + { + "description": "Effect name", + "name": "NewEffectName", + "type": "string" + }, + { + "description": "Parameter name", + "name": "NewPropertyName", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "the idle effect parameter value of the object.", + "fullName": "Idle effect parameter value", + "functionType": "ExpressionAndCondition", + "group": "Button effect tween configuration", + "name": "IdleValue", + "sentence": "the idle effect parameter value", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "IdleValue" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "IdleValue", + "group": "Button effect tween configuration", + "name": "SetIdleValue", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "IdleValue", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the focused effect parameter value of the object. The state is Focused when the button is hovered or held outside.", + "fullName": "Focused effect parameter value", + "functionType": "ExpressionAndCondition", + "group": "Button effect tween configuration", + "name": "FocusedValue", + "sentence": "the focused effect parameter value", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FocusedValue" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FocusedValue", + "group": "Button effect tween configuration", + "name": "SetFocusedValue", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FocusedValue", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the pressed effect parameter value of the object.", + "fullName": "Pressed effect parameter value", + "functionType": "ExpressionAndCondition", + "group": "Button effect tween configuration", + "name": "PressedValue", + "sentence": "the pressed effect parameter value", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "PressedValue" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "PressedValue", + "group": "Button effect tween configuration", + "name": "SetPressedValue", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "PressedValue", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-in easing of the object.", + "fullName": "Fade-in easing", + "functionType": "ExpressionAndCondition", + "group": "Button effect tween configuration", + "name": "FadeInEasing", + "sentence": "the fade-in easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "FadeInEasing" + ] + } + ] + } + ], + "expressionType": { + "supplementaryInformation": "[\"linear\",\"easeInQuad\",\"easeOutQuad\",\"easeInOutQuad\",\"easeInCubic\",\"easeOutCubic\",\"easeInOutCubic\",\"easeInQuart\",\"easeOutQuart\",\"easeInOutQuart\",\"easeInQuint\",\"easeOutQuint\",\"easeInOutQuint\",\"easeInOutSine\",\"easeInExpo\",\"easeOutExpo\",\"easeInOutExpo\",\"easeInCirc\",\"easeOutCirc\",\"easeInOutCirc\",\"easeOutBounce\",\"easeInBack\",\"easeOutBack\",\"easeInOutBack\",\"elastic\",\"swingFromTo\",\"swingFrom\",\"swingTo\",\"bounce\",\"bouncePast\",\"easeFromTo\",\"easeFrom\",\"easeTo\"]", + "type": "stringWithSelector" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeInEasing", + "group": "Button effect tween configuration", + "name": "SetFadeInEasing", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "FadeInEasing", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-out easing of the object.", + "fullName": "Fade-out easing", + "functionType": "ExpressionAndCondition", + "group": "Button effect tween configuration", + "name": "FadeOutEasing", + "sentence": "the fade-out easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "FadeOutEasing" + ] + } + ] + } + ], + "expressionType": { + "supplementaryInformation": "[\"linear\",\"easeInQuad\",\"easeOutQuad\",\"easeInOutQuad\",\"easeInCubic\",\"easeOutCubic\",\"easeInOutCubic\",\"easeInQuart\",\"easeOutQuart\",\"easeInOutQuart\",\"easeInQuint\",\"easeOutQuint\",\"easeInOutQuint\",\"easeInOutSine\",\"easeInExpo\",\"easeOutExpo\",\"easeInOutExpo\",\"easeInCirc\",\"easeOutCirc\",\"easeInOutCirc\",\"easeOutBounce\",\"easeInBack\",\"easeOutBack\",\"easeInOutBack\",\"elastic\",\"swingFromTo\",\"swingFrom\",\"swingTo\",\"bounce\",\"bouncePast\",\"easeFromTo\",\"easeFrom\",\"easeTo\"]", + "type": "stringWithSelector" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeOutEasing", + "group": "Button effect tween configuration", + "name": "SetFadeOutEasing", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "FadeOutEasing", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-in duration of the object.", + "fullName": "Fade-in duration", + "functionType": "ExpressionAndCondition", + "group": "Button effect tween configuration", + "name": "FadeInDuration", + "sentence": "the fade-in duration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FadeInDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeInDuration", + "group": "Button effect tween configuration", + "name": "SetFadeInDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FadeInDuration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-out duration of the object.", + "fullName": "Fade-out duration", + "functionType": "ExpressionAndCondition", + "group": "Button effect tween configuration", + "name": "FadeOutDuration", + "sentence": "the fade-out duration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FadeOutDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeOutDuration", + "group": "Button effect tween configuration", + "name": "SetFadeOutDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FadeOutDuration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "TimeDelta" + }, + { + "functionName": "FadeIn" + }, + { + "functionName": "FadeOut" + }, + { + "functionName": "PlayTween" + }, + { + "folderName": "Button effect tween configuration", + "children": [ + { + "functionName": "EffectName" + }, + { + "functionName": "EffectProperty" + }, + { + "functionName": "SetEffectProperty" + }, + { + "functionName": "IdleValue" + }, + { + "functionName": "SetIdleValue" + }, + { + "functionName": "FocusedValue" + }, + { + "functionName": "SetFocusedValue" + }, + { + "functionName": "PressedValue" + }, + { + "functionName": "SetPressedValue" + }, + { + "functionName": "FadeInEasing" + }, + { + "functionName": "SetFadeInEasing" + }, + { + "functionName": "FadeOutEasing" + }, + { + "functionName": "SetFadeOutEasing" + }, + { + "functionName": "FadeInDuration" + }, + { + "functionName": "SetFadeInDuration" + }, + { + "functionName": "FadeOutDuration" + }, + { + "functionName": "SetFadeOutDuration" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Button states", + "extraInformation": [ + "ButtonStates::ButtonFSM" + ], + "choices": [], + "name": "ButtonFSM" + }, + { + "value": "", + "type": "Behavior", + "label": "Effect capability", + "extraInformation": [ + "EffectCapability::EffectBehavior" + ], + "choices": [], + "name": "Effect" + }, + { + "value": "Effect", + "type": "String", + "label": "Effect name", + "group": "Effect", + "name": "EffectName" + }, + { + "value": "", + "type": "String", + "label": "Effect parameter", + "description": "The effect parameter names can be found in the effects tab with the \"Show parameter names\" action of the drop down menu.", + "group": "Effect", + "name": "EffectProperty" + }, + { + "value": "0", + "type": "Number", + "label": "Idle effect parameter value", + "group": "Value", + "name": "IdleValue" + }, + { + "value": "0", + "type": "Number", + "label": "Focused effect parameter value", + "description": "The state is Focused when the button is hovered or held outside.", + "group": "Value", + "name": "FocusedValue" + }, + { + "value": "0", + "type": "Number", + "label": "Pressed effect parameter value", + "group": "Value", + "name": "PressedValue" + }, + { + "value": "easeInOutQuad", + "type": "Choice", + "label": "Fade-in easing", + "group": "Speed", + "choices": [ + { + "label": "linear", + "value": "linear" + }, + { + "label": "easeInQuad", + "value": "easeInQuad" + }, + { + "label": "easeOutQuad", + "value": "easeOutQuad" + }, + { + "label": "easeInOutQuad", + "value": "easeInOutQuad" + }, + { + "label": "easeInCubic", + "value": "easeInCubic" + }, + { + "label": "easeOutCubic", + "value": "easeOutCubic" + }, + { + "label": "easeInOutCubic", + "value": "easeInOutCubic" + }, + { + "label": "easeInQuart", + "value": "easeInQuart" + }, + { + "label": "easeOutQuart", + "value": "easeOutQuart" + }, + { + "label": "easeInOutQuart", + "value": "easeInOutQuart" + }, + { + "label": "easeInQuint", + "value": "easeInQuint" + }, + { + "label": "easeOutQuint", + "value": "easeOutQuint" + }, + { + "label": "easeInOutQuint", + "value": "easeInOutQuint" + }, + { + "label": "easeInOutSine", + "value": "easeInOutSine" + }, + { + "label": "easeInExpo", + "value": "easeInExpo" + }, + { + "label": "easeOutExpo", + "value": "easeOutExpo" + }, + { + "label": "easeInOutExpo", + "value": "easeInOutExpo" + }, + { + "label": "easeInCirc", + "value": "easeInCirc" + }, + { + "label": "easeOutCirc", + "value": "easeOutCirc" + }, + { + "label": "easeInOutCirc", + "value": "easeInOutCirc" + }, + { + "label": "easeOutBounce", + "value": "easeOutBounce" + }, + { + "label": "easeInBack", + "value": "easeInBack" + }, + { + "label": "easeOutBack", + "value": "easeOutBack" + }, + { + "label": "easeInOutBack", + "value": "easeInOutBack" + }, + { + "label": "elastic", + "value": "elastic" + }, + { + "label": "swingFromTo", + "value": "swingFromTo" + }, + { + "label": "swingFrom", + "value": "swingFrom" + }, + { + "label": "swingTo", + "value": "swingTo" + }, + { + "label": "bounce", + "value": "bounce" + }, + { + "label": "bouncePast", + "value": "bouncePast" + }, + { + "label": "easeFromTo", + "value": "easeFromTo" + }, + { + "label": "easeFrom", + "value": "easeFrom" + }, + { + "label": "easeTo", + "value": "easeTo" + } + ], + "name": "FadeInEasing" + }, + { + "value": "easeInOutQuad", + "type": "Choice", + "label": "Fade-out easing", + "group": "Speed", + "choices": [ + { + "label": "linear", + "value": "linear" + }, + { + "label": "easeInQuad", + "value": "easeInQuad" + }, + { + "label": "easeOutQuad", + "value": "easeOutQuad" + }, + { + "label": "easeInOutQuad", + "value": "easeInOutQuad" + }, + { + "label": "easeInCubic", + "value": "easeInCubic" + }, + { + "label": "easeOutCubic", + "value": "easeOutCubic" + }, + { + "label": "easeInOutCubic", + "value": "easeInOutCubic" + }, + { + "label": "easeInQuart", + "value": "easeInQuart" + }, + { + "label": "easeOutQuart", + "value": "easeOutQuart" + }, + { + "label": "easeInOutQuart", + "value": "easeInOutQuart" + }, + { + "label": "easeInQuint", + "value": "easeInQuint" + }, + { + "label": "easeOutQuint", + "value": "easeOutQuint" + }, + { + "label": "easeInOutQuint", + "value": "easeInOutQuint" + }, + { + "label": "easeInOutSine", + "value": "easeInOutSine" + }, + { + "label": "easeInExpo", + "value": "easeInExpo" + }, + { + "label": "easeOutExpo", + "value": "easeOutExpo" + }, + { + "label": "easeInOutExpo", + "value": "easeInOutExpo" + }, + { + "label": "easeInCirc", + "value": "easeInCirc" + }, + { + "label": "easeOutCirc", + "value": "easeOutCirc" + }, + { + "label": "easeInOutCirc", + "value": "easeInOutCirc" + }, + { + "label": "easeOutBounce", + "value": "easeOutBounce" + }, + { + "label": "easeInBack", + "value": "easeInBack" + }, + { + "label": "easeOutBack", + "value": "easeOutBack" + }, + { + "label": "easeInOutBack", + "value": "easeInOutBack" + }, + { + "label": "elastic", + "value": "elastic" + }, + { + "label": "swingFromTo", + "value": "swingFromTo" + }, + { + "label": "swingFrom", + "value": "swingFrom" + }, + { + "label": "swingTo", + "value": "swingTo" + }, + { + "label": "bounce", + "value": "bounce" + }, + { + "label": "bouncePast", + "value": "bouncePast" + }, + { + "label": "easeFromTo", + "value": "easeFromTo" + }, + { + "label": "easeFrom", + "value": "easeFrom" + }, + { + "label": "easeTo", + "value": "easeTo" + } + ], + "name": "FadeOutEasing" + }, + { + "value": "0.125", + "type": "Number", + "unit": "Second", + "label": "Fade-in duration", + "group": "Speed", + "name": "FadeInDuration" + }, + { + "value": "0.5", + "type": "Number", + "unit": "Second", + "label": "Fade-out duration", + "group": "Speed", + "name": "FadeOutDuration" + }, + { + "value": "", + "type": "Boolean", + "label": "Disable the effect in idle state", + "advanced": true, + "name": "ShouldDisableEffectInIdle" + }, + { + "value": "Idle", + "type": "Choice", + "label": "", + "choices": [ + { + "label": "Idle", + "value": "Idle" + }, + { + "label": "Focused", + "value": "Focused" + }, + { + "label": "Pressed", + "value": "Pressed" + } + ], + "hidden": true, + "name": "PreviousState" + }, + { + "value": "0", + "type": "Number", + "label": "", + "hidden": true, + "name": "TweenInitialValue" + }, + { + "value": "0", + "type": "Number", + "label": "", + "hidden": true, + "name": "TweenTargetedValue" + }, + { + "value": "0", + "type": "Number", + "label": "", + "hidden": true, + "name": "TweenTime" + }, + { + "value": "NoTween", + "type": "Choice", + "label": "", + "choices": [ + { + "label": "NoTween", + "value": "NoTween" + }, + { + "label": "FadeIn", + "value": "FadeIn" + }, + { + "label": "FadeOut", + "value": "FadeOut" + } + ], + "hidden": true, + "name": "TweenState" + }, + { + "value": "0", + "type": "Number", + "label": "", + "hidden": true, + "name": "EffectValue" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ButtonFSM" + }, + { + "propertyName": "Effect" + }, + { + "folderName": "Effect", + "children": [ + { + "propertyName": "EffectName" + }, + { + "propertyName": "EffectProperty" + } + ] + }, + { + "folderName": "Value", + "children": [ + { + "propertyName": "IdleValue" + }, + { + "propertyName": "FocusedValue" + }, + { + "propertyName": "PressedValue" + } + ] + }, + { + "folderName": "Speed", + "children": [ + { + "propertyName": "FadeInEasing" + }, + { + "propertyName": "FadeOutEasing" + }, + { + "propertyName": "FadeInDuration" + }, + { + "propertyName": "FadeOutDuration" + } + ] + }, + { + "propertyName": "ShouldDisableEffectInIdle" + }, + { + "propertyName": "PreviousState" + }, + { + "propertyName": "TweenInitialValue" + }, + { + "propertyName": "TweenTargetedValue" + }, + { + "propertyName": "TweenTime" + }, + { + "propertyName": "TweenState" + }, + { + "propertyName": "EffectValue" + } + ] + } + }, + { + "description": "Smoothly resize buttons according to their state.", + "fullName": "Button scale tween", + "helpPath": "", + "iconUrl": "", + "name": "ButtonScaleTween", + "objectType": "", + "previewIconUrl": "", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ScalableCapability::ScalableBehavior::SetValue" + }, + "parameters": [ + "Object", + "Scale", + "=", + "IdleScale" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Change based on state", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsIdle" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "!=", + "\"Idle\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Idle\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonScaleTween::FadeOut" + }, + "parameters": [ + "Object", + "Behavior", + "IdleScale", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsFocused" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Idle\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Focused\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonScaleTween::FadeIn" + }, + "parameters": [ + "Object", + "Behavior", + "FocusedScale", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Pressed\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Focused\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonScaleTween::FadeOut" + }, + "parameters": [ + "Object", + "Behavior", + "FocusedScale", + "" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "!=", + "\"Pressed\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Pressed\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonScaleTween::FadeIn" + }, + "parameters": [ + "Object", + "Behavior", + "PressedScale", + "" + ] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Fade in", + "functionType": "Action", + "name": "FadeIn", + "private": true, + "sentence": "_PARAM0_ fade in to _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween::AddObjectScaleTween" + }, + "parameters": [ + "Object", + "Tween", + "\"__ButtonScaleTween.Fade\"", + "Value", + "Value", + "FadeInEasing", + "1000 * FadeInDuration", + "no", + "yes" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "fullName": "Fade out", + "functionType": "Action", + "name": "FadeOut", + "private": true, + "sentence": "_PARAM0_ fade out to _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween::AddObjectScaleTween" + }, + "parameters": [ + "Object", + "Tween", + "\"__ButtonScaleTween.Fade\"", + "Value", + "Value", + "FadeOutEasing", + "1000 * FadeOutDuration", + "no", + "yes" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the idle state size scale of the object.", + "fullName": "Idle state size scale", + "functionType": "ExpressionAndCondition", + "group": "Button scale tween configuration", + "name": "IdleScale", + "sentence": "the idle state size scale", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "IdleScale" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "IdleScale", + "group": "Button scale tween configuration", + "name": "SetIdleScale", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "IdleScale", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the focused state size scale of the object. The state is Focused when the button is hovered or held outside.", + "fullName": "Focused state size scale", + "functionType": "ExpressionAndCondition", + "group": "Button scale tween configuration", + "name": "FocusedScale", + "sentence": "the focused state size scale", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FocusedScale" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FocusedScale", + "group": "Button scale tween configuration", + "name": "SetFocusedScale", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FocusedScale", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the pressed state size scale of the object.", + "fullName": "Pressed state size scale", + "functionType": "ExpressionAndCondition", + "group": "Button scale tween configuration", + "name": "PressedScale", + "sentence": "the pressed state size scale", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "PressedScale" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "PressedScale", + "group": "Button scale tween configuration", + "name": "SetPressedScale", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "PressedScale", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-in duration of the object.", + "fullName": "Fade-in duration", + "functionType": "ExpressionAndCondition", + "group": "Button scale tween configuration", + "name": "FadeInDuration", + "sentence": "the fade-in duration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FadeInDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeInDuration", + "group": "Button scale tween configuration", + "name": "SetFadeInDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FadeInDuration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-out duration of the object.", + "fullName": "Fade-out duration", + "functionType": "ExpressionAndCondition", + "group": "Button scale tween configuration", + "name": "FadeOutDuration", + "sentence": "the fade-out duration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FadeOutDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeOutDuration", + "group": "Button scale tween configuration", + "name": "SetFadeOutDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FadeOutDuration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-in easing of the object.", + "fullName": "Fade-in easing", + "functionType": "ExpressionAndCondition", + "group": "Button scale tween configuration", + "name": "FadeInEasing", + "sentence": "the fade-in easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "FadeInEasing" + ] + } + ] + } + ], + "expressionType": { + "supplementaryInformation": "[\"linear\",\"easeInQuad\",\"easeOutQuad\",\"easeInOutQuad\",\"easeInCubic\",\"easeOutCubic\",\"easeInOutCubic\",\"easeInQuart\",\"easeOutQuart\",\"easeInOutQuart\",\"easeInQuint\",\"easeOutQuint\",\"easeInOutQuint\",\"easeInOutSine\",\"easeInExpo\",\"easeOutExpo\",\"easeInOutExpo\",\"easeInCirc\",\"easeOutCirc\",\"easeInOutCirc\",\"easeOutBounce\",\"easeInBack\",\"easeOutBack\",\"easeInOutBack\",\"elastic\",\"swingFromTo\",\"swingFrom\",\"swingTo\",\"bounce\",\"bouncePast\",\"easeFromTo\",\"easeFrom\",\"easeTo\"]", + "type": "stringWithSelector" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeInEasing", + "group": "Button scale tween configuration", + "name": "SetFadeInEasing", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "FadeInEasing", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-out easing of the object.", + "fullName": "Fade-out easing", + "functionType": "ExpressionAndCondition", + "group": "Button scale tween configuration", + "name": "FadeOutEasing", + "sentence": "the fade-out easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "FadeOutEasing" + ] + } + ] + } + ], + "expressionType": { + "supplementaryInformation": "[\"linear\",\"easeInQuad\",\"easeOutQuad\",\"easeInOutQuad\",\"easeInCubic\",\"easeOutCubic\",\"easeInOutCubic\",\"easeInQuart\",\"easeOutQuart\",\"easeInOutQuart\",\"easeInQuint\",\"easeOutQuint\",\"easeInOutQuint\",\"easeInOutSine\",\"easeInExpo\",\"easeOutExpo\",\"easeInOutExpo\",\"easeInCirc\",\"easeOutCirc\",\"easeInOutCirc\",\"easeOutBounce\",\"easeInBack\",\"easeOutBack\",\"easeInOutBack\",\"elastic\",\"swingFromTo\",\"swingFrom\",\"swingTo\",\"bounce\",\"bouncePast\",\"easeFromTo\",\"easeFrom\",\"easeTo\"]", + "type": "stringWithSelector" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeOutEasing", + "group": "Button scale tween configuration", + "name": "SetFadeOutEasing", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "FadeOutEasing", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "FadeIn" + }, + { + "functionName": "FadeOut" + }, + { + "folderName": "Button scale tween configuration", + "children": [ + { + "functionName": "IdleScale" + }, + { + "functionName": "SetIdleScale" + }, + { + "functionName": "FocusedScale" + }, + { + "functionName": "SetFocusedScale" + }, + { + "functionName": "PressedScale" + }, + { + "functionName": "SetPressedScale" + }, + { + "functionName": "FadeInDuration" + }, + { + "functionName": "SetFadeInDuration" + }, + { + "functionName": "FadeOutDuration" + }, + { + "functionName": "SetFadeOutDuration" + }, + { + "functionName": "FadeInEasing" + }, + { + "functionName": "SetFadeInEasing" + }, + { + "functionName": "FadeOutEasing" + }, + { + "functionName": "SetFadeOutEasing" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Scalable capability", + "extraInformation": [ + "ScalableCapability::ScalableBehavior" + ], + "choices": [], + "name": "Scale" + }, + { + "value": "", + "type": "Behavior", + "label": "Button states behavior (required)", + "extraInformation": [ + "ButtonStates::ButtonFSM" + ], + "choices": [], + "name": "ButtonFSM" + }, + { + "value": "", + "type": "Behavior", + "label": "Tween behavior (required)", + "extraInformation": [ + "Tween::TweenBehavior" + ], + "choices": [], + "name": "Tween" + }, + { + "value": "1", + "type": "Number", + "unit": "Dimensionless", + "label": "Idle state size scale", + "group": "Size", + "name": "IdleScale" + }, + { + "value": "1", + "type": "Number", + "unit": "Dimensionless", + "label": "Focused state size scale", + "description": "The state is Focused when the button is hovered or held outside.", + "group": "Size", + "name": "FocusedScale" + }, + { + "value": "0.1", + "type": "Number", + "unit": "Second", + "label": "Fade-in duration", + "group": "Speed", + "name": "FadeInDuration" + }, + { + "value": "0.2", + "type": "Number", + "unit": "Second", + "label": "Fade-out duration", + "group": "Speed", + "name": "FadeOutDuration" + }, + { + "value": "0.95", + "type": "Number", + "unit": "Dimensionless", + "label": "Pressed state size scale", + "group": "Size", + "name": "PressedScale" + }, + { + "value": "easeInOutQuad", + "type": "Choice", + "label": "Fade-in easing", + "group": "Speed", + "choices": [ + { + "label": "linear", + "value": "linear" + }, + { + "label": "easeInQuad", + "value": "easeInQuad" + }, + { + "label": "easeOutQuad", + "value": "easeOutQuad" + }, + { + "label": "easeInOutQuad", + "value": "easeInOutQuad" + }, + { + "label": "easeInCubic", + "value": "easeInCubic" + }, + { + "label": "easeOutCubic", + "value": "easeOutCubic" + }, + { + "label": "easeInOutCubic", + "value": "easeInOutCubic" + }, + { + "label": "easeInQuart", + "value": "easeInQuart" + }, + { + "label": "easeOutQuart", + "value": "easeOutQuart" + }, + { + "label": "easeInOutQuart", + "value": "easeInOutQuart" + }, + { + "label": "easeInQuint", + "value": "easeInQuint" + }, + { + "label": "easeOutQuint", + "value": "easeOutQuint" + }, + { + "label": "easeInOutQuint", + "value": "easeInOutQuint" + }, + { + "label": "easeInOutSine", + "value": "easeInOutSine" + }, + { + "label": "easeInExpo", + "value": "easeInExpo" + }, + { + "label": "easeOutExpo", + "value": "easeOutExpo" + }, + { + "label": "easeInOutExpo", + "value": "easeInOutExpo" + }, + { + "label": "easeInCirc", + "value": "easeInCirc" + }, + { + "label": "easeOutCirc", + "value": "easeOutCirc" + }, + { + "label": "easeInOutCirc", + "value": "easeInOutCirc" + }, + { + "label": "easeOutBounce", + "value": "easeOutBounce" + }, + { + "label": "easeInBack", + "value": "easeInBack" + }, + { + "label": "easeOutBack", + "value": "easeOutBack" + }, + { + "label": "easeInOutBack", + "value": "easeInOutBack" + }, + { + "label": "elastic", + "value": "elastic" + }, + { + "label": "swingFromTo", + "value": "swingFromTo" + }, + { + "label": "swingFrom", + "value": "swingFrom" + }, + { + "label": "swingTo", + "value": "swingTo" + }, + { + "label": "bounce", + "value": "bounce" + }, + { + "label": "bouncePast", + "value": "bouncePast" + }, + { + "label": "easeFromTo", + "value": "easeFromTo" + }, + { + "label": "easeFrom", + "value": "easeFrom" + }, + { + "label": "easeTo", + "value": "easeTo" + } + ], + "name": "FadeInEasing" + }, + { + "value": "easeInOutQuad", + "type": "Choice", + "label": "Fade-out easing", + "group": "Speed", + "choices": [ + { + "label": "linear", + "value": "linear" + }, + { + "label": "easeInQuad", + "value": "easeInQuad" + }, + { + "label": "easeOutQuad", + "value": "easeOutQuad" + }, + { + "label": "easeInOutQuad", + "value": "easeInOutQuad" + }, + { + "label": "easeInCubic", + "value": "easeInCubic" + }, + { + "label": "easeOutCubic", + "value": "easeOutCubic" + }, + { + "label": "easeInOutCubic", + "value": "easeInOutCubic" + }, + { + "label": "easeInQuart", + "value": "easeInQuart" + }, + { + "label": "easeOutQuart", + "value": "easeOutQuart" + }, + { + "label": "easeInOutQuart", + "value": "easeInOutQuart" + }, + { + "label": "easeInQuint", + "value": "easeInQuint" + }, + { + "label": "easeOutQuint", + "value": "easeOutQuint" + }, + { + "label": "easeInOutQuint", + "value": "easeInOutQuint" + }, + { + "label": "easeInOutSine", + "value": "easeInOutSine" + }, + { + "label": "easeInExpo", + "value": "easeInExpo" + }, + { + "label": "easeOutExpo", + "value": "easeOutExpo" + }, + { + "label": "easeInOutExpo", + "value": "easeInOutExpo" + }, + { + "label": "easeInCirc", + "value": "easeInCirc" + }, + { + "label": "easeOutCirc", + "value": "easeOutCirc" + }, + { + "label": "easeInOutCirc", + "value": "easeInOutCirc" + }, + { + "label": "easeOutBounce", + "value": "easeOutBounce" + }, + { + "label": "easeInBack", + "value": "easeInBack" + }, + { + "label": "easeOutBack", + "value": "easeOutBack" + }, + { + "label": "easeInOutBack", + "value": "easeInOutBack" + }, + { + "label": "elastic", + "value": "elastic" + }, + { + "label": "swingFromTo", + "value": "swingFromTo" + }, + { + "label": "swingFrom", + "value": "swingFrom" + }, + { + "label": "swingTo", + "value": "swingTo" + }, + { + "label": "bounce", + "value": "bounce" + }, + { + "label": "bouncePast", + "value": "bouncePast" + }, + { + "label": "easeFromTo", + "value": "easeFromTo" + }, + { + "label": "easeFrom", + "value": "easeFrom" + }, + { + "label": "easeTo", + "value": "easeTo" + } + ], + "name": "FadeOutEasing" + }, + { + "value": "Idle", + "type": "Choice", + "label": "", + "choices": [ + { + "label": "Idle", + "value": "Idle" + }, + { + "label": "Focused", + "value": "Focused" + }, + { + "label": "Pressed", + "value": "Pressed" + } + ], + "hidden": true, + "name": "PreviousState" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Scale" + }, + { + "propertyName": "ButtonFSM" + }, + { + "propertyName": "Tween" + }, + { + "folderName": "Size", + "children": [ + { + "propertyName": "IdleScale" + }, + { + "propertyName": "FocusedScale" + }, + { + "propertyName": "PressedScale" + } + ] + }, + { + "folderName": "Speed", + "children": [ + { + "propertyName": "FadeInDuration" + }, + { + "propertyName": "FadeOutDuration" + }, + { + "propertyName": "FadeInEasing" + }, + { + "propertyName": "FadeOutEasing" + } + ] + }, + { + "propertyName": "PreviousState" + } + ] + } + }, + { + "description": "Smoothly change the color tint of buttons according to their state.", + "fullName": "Button color tint tween", + "helpPath": "", + "iconUrl": "", + "name": "ButtonColorTintTween", + "objectType": "Sprite", + "previewIconUrl": "", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ChangeColor" + }, + "parameters": [ + "Object", + "IdleColorTint" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "States", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsIdle" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "!=", + "\"Idle\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Idle\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonColorTintTween::FadeOut" + }, + "parameters": [ + "Object", + "Behavior", + "IdleColorTint", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsFocused" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Idle\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Focused\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonColorTintTween::FadeIn" + }, + "parameters": [ + "Object", + "Behavior", + "FocusedColorTint", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Pressed\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Focused\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonColorTintTween::FadeOut" + }, + "parameters": [ + "Object", + "Behavior", + "FocusedColorTint", + "" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "!=", + "\"Pressed\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Pressed\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonColorTintTween::FadeIn" + }, + "parameters": [ + "Object", + "Behavior", + "PressedColorTint", + "" + ] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Fade in", + "functionType": "Action", + "name": "FadeIn", + "private": true, + "sentence": "_PARAM0_ fade in to _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween::AddObjectColorTween" + }, + "parameters": [ + "Object", + "Tween", + "\"__ButtonColorTintTween.Fade\"", + "Value", + "FadeInEasing", + "1000 * FadeInDuration", + "no", + "yes" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + }, + { + "description": "Value", + "name": "Value", + "type": "color" + } + ], + "objectGroups": [] + }, + { + "fullName": "Fade out", + "functionType": "Action", + "name": "FadeOut", + "private": true, + "sentence": "_PARAM0_ fade out to _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween::AddObjectColorTween" + }, + "parameters": [ + "Object", + "Tween", + "\"__ButtonColorTintTween.Fade\"", + "Value", + "FadeOutEasing", + "1000 * FadeOutDuration", + "no", + "yes" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + }, + { + "description": "Value", + "name": "Value", + "type": "color" + } + ], + "objectGroups": [] + }, + { + "description": "the idle state color tint of the object.", + "fullName": "Idle state color tint", + "functionType": "ExpressionAndCondition", + "group": "Button color tint tween", + "name": "IdleColorTint", + "sentence": "the idle state color tint", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "IdleColorTint" + ] + } + ] + } + ], + "expressionType": { + "type": "color" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "IdleColorTint", + "group": "Button color tint tween", + "name": "SetIdleColorTint", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "IdleColorTint", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the focused state color tint of the object. The state is Focused when the button is hovered or held outside.", + "fullName": "Focused state color tint", + "functionType": "ExpressionAndCondition", + "group": "Button color tint tween", + "name": "FocusedColorTint", + "sentence": "the focused state color tint", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "FocusedColorTint" + ] + } + ] + } + ], + "expressionType": { + "type": "color" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FocusedColorTint", + "group": "Button color tint tween", + "name": "SetFocusedColorTint", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "FocusedColorTint", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the pressed state color tint of the object.", + "fullName": "Pressed state color tint", + "functionType": "ExpressionAndCondition", + "group": "Button color tint tween", + "name": "PressedColorTint", + "sentence": "the pressed state color tint", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "PressedColorTint" + ] + } + ] + } + ], + "expressionType": { + "type": "color" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "PressedColorTint", + "group": "Button color tint tween", + "name": "SetPressedColorTint", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PressedColorTint", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-in duration of the object.", + "fullName": "Fade-in duration", + "functionType": "ExpressionAndCondition", + "group": "Button color tint tween", + "name": "FadeInDuration", + "sentence": "the fade-in duration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FadeInDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeInDuration", + "group": "Button color tint tween", + "name": "SetFadeInDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FadeInDuration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-out duration of the object.", + "fullName": "Fade-out duration", + "functionType": "ExpressionAndCondition", + "group": "Button color tint tween", + "name": "FadeOutDuration", + "sentence": "the fade-out duration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FadeOutDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeOutDuration", + "group": "Button color tint tween", + "name": "SetFadeOutDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FadeOutDuration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-in easing of the object.", + "fullName": "Fade-in easing", + "functionType": "ExpressionAndCondition", + "group": "Button color tint tween", + "name": "FadeInEasing", + "sentence": "the fade-in easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "FadeInEasing" + ] + } + ] + } + ], + "expressionType": { + "supplementaryInformation": "[\"linear\",\"easeInQuad\",\"easeOutQuad\",\"easeInOutQuad\",\"easeInCubic\",\"easeOutCubic\",\"easeInOutCubic\",\"easeInQuart\",\"easeOutQuart\",\"easeInOutQuart\",\"easeInQuint\",\"easeOutQuint\",\"easeInOutQuint\",\"easeInOutSine\",\"easeInExpo\",\"easeOutExpo\",\"easeInOutExpo\",\"easeInCirc\",\"easeOutCirc\",\"easeInOutCirc\",\"easeOutBounce\",\"easeInBack\",\"easeOutBack\",\"easeInOutBack\",\"elastic\",\"swingFromTo\",\"swingFrom\",\"swingTo\",\"bounce\",\"bouncePast\",\"easeFromTo\",\"easeFrom\",\"easeTo\"]", + "type": "stringWithSelector" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeInEasing", + "group": "Button color tint tween", + "name": "SetFadeInEasing", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "FadeInEasing", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-out easing of the object.", + "fullName": "Fade-out easing", + "functionType": "ExpressionAndCondition", + "group": "Button color tint tween", + "name": "FadeOutEasing", + "sentence": "the fade-out easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "FadeOutEasing" + ] + } + ] + } + ], + "expressionType": { + "supplementaryInformation": "[\"linear\",\"easeInQuad\",\"easeOutQuad\",\"easeInOutQuad\",\"easeInCubic\",\"easeOutCubic\",\"easeInOutCubic\",\"easeInQuart\",\"easeOutQuart\",\"easeInOutQuart\",\"easeInQuint\",\"easeOutQuint\",\"easeInOutQuint\",\"easeInOutSine\",\"easeInExpo\",\"easeOutExpo\",\"easeInOutExpo\",\"easeInCirc\",\"easeOutCirc\",\"easeInOutCirc\",\"easeOutBounce\",\"easeInBack\",\"easeOutBack\",\"easeInOutBack\",\"elastic\",\"swingFromTo\",\"swingFrom\",\"swingTo\",\"bounce\",\"bouncePast\",\"easeFromTo\",\"easeFrom\",\"easeTo\"]", + "type": "stringWithSelector" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeOutEasing", + "group": "Button color tint tween", + "name": "SetFadeOutEasing", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "FadeOutEasing", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "FadeIn" + }, + { + "functionName": "FadeOut" + }, + { + "folderName": "Button color tint tween", + "children": [ + { + "functionName": "IdleColorTint" + }, + { + "functionName": "SetIdleColorTint" + }, + { + "functionName": "FocusedColorTint" + }, + { + "functionName": "SetFocusedColorTint" + }, + { + "functionName": "PressedColorTint" + }, + { + "functionName": "SetPressedColorTint" + }, + { + "functionName": "FadeInDuration" + }, + { + "functionName": "SetFadeInDuration" + }, + { + "functionName": "FadeOutDuration" + }, + { + "functionName": "SetFadeOutDuration" + }, + { + "functionName": "FadeInEasing" + }, + { + "functionName": "SetFadeInEasing" + }, + { + "functionName": "FadeOutEasing" + }, + { + "functionName": "SetFadeOutEasing" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Button states", + "extraInformation": [ + "ButtonStates::ButtonFSM" + ], + "choices": [], + "name": "ButtonFSM" + }, + { + "value": "", + "type": "Behavior", + "label": "Tween", + "extraInformation": [ + "Tween::TweenBehavior" + ], + "choices": [], + "name": "Tween" + }, + { + "value": "255;255;255", + "type": "Color", + "label": "Idle state color tint", + "group": "Color", + "name": "IdleColorTint" + }, + { + "value": "192;192;192", + "type": "Color", + "label": "Focused state color tint", + "description": "The state is Focused when the button is hovered or held outside.", + "group": "Color", + "name": "FocusedColorTint" + }, + { + "value": "64;64;64", + "type": "Color", + "label": "Pressed state color tint", + "group": "Color", + "name": "PressedColorTint" + }, + { + "value": "0.1", + "type": "Number", + "unit": "Second", + "label": "Fade-in duration", + "group": "Speed", + "name": "FadeInDuration" + }, + { + "value": "0.2", + "type": "Number", + "unit": "Second", + "label": "Fade-out duration", + "group": "Speed", + "name": "FadeOutDuration" + }, + { + "value": "easeInOutQuad", + "type": "Choice", + "label": "Fade-in easing", + "group": "Speed", + "choices": [ + { + "label": "linear", + "value": "linear" + }, + { + "label": "easeInQuad", + "value": "easeInQuad" + }, + { + "label": "easeOutQuad", + "value": "easeOutQuad" + }, + { + "label": "easeInOutQuad", + "value": "easeInOutQuad" + }, + { + "label": "easeInCubic", + "value": "easeInCubic" + }, + { + "label": "easeOutCubic", + "value": "easeOutCubic" + }, + { + "label": "easeInOutCubic", + "value": "easeInOutCubic" + }, + { + "label": "easeInQuart", + "value": "easeInQuart" + }, + { + "label": "easeOutQuart", + "value": "easeOutQuart" + }, + { + "label": "easeInOutQuart", + "value": "easeInOutQuart" + }, + { + "label": "easeInQuint", + "value": "easeInQuint" + }, + { + "label": "easeOutQuint", + "value": "easeOutQuint" + }, + { + "label": "easeInOutQuint", + "value": "easeInOutQuint" + }, + { + "label": "easeInOutSine", + "value": "easeInOutSine" + }, + { + "label": "easeInExpo", + "value": "easeInExpo" + }, + { + "label": "easeOutExpo", + "value": "easeOutExpo" + }, + { + "label": "easeInOutExpo", + "value": "easeInOutExpo" + }, + { + "label": "easeInCirc", + "value": "easeInCirc" + }, + { + "label": "easeOutCirc", + "value": "easeOutCirc" + }, + { + "label": "easeInOutCirc", + "value": "easeInOutCirc" + }, + { + "label": "easeOutBounce", + "value": "easeOutBounce" + }, + { + "label": "easeInBack", + "value": "easeInBack" + }, + { + "label": "easeOutBack", + "value": "easeOutBack" + }, + { + "label": "easeInOutBack", + "value": "easeInOutBack" + }, + { + "label": "elastic", + "value": "elastic" + }, + { + "label": "swingFromTo", + "value": "swingFromTo" + }, + { + "label": "swingFrom", + "value": "swingFrom" + }, + { + "label": "swingTo", + "value": "swingTo" + }, + { + "label": "bounce", + "value": "bounce" + }, + { + "label": "bouncePast", + "value": "bouncePast" + }, + { + "label": "easeFromTo", + "value": "easeFromTo" + }, + { + "label": "easeFrom", + "value": "easeFrom" + }, + { + "label": "easeTo", + "value": "easeTo" + } + ], + "name": "FadeInEasing" + }, + { + "value": "easeInOutQuad", + "type": "Choice", + "label": "Fade-out easing", + "group": "Speed", + "choices": [ + { + "label": "linear", + "value": "linear" + }, + { + "label": "easeInQuad", + "value": "easeInQuad" + }, + { + "label": "easeOutQuad", + "value": "easeOutQuad" + }, + { + "label": "easeInOutQuad", + "value": "easeInOutQuad" + }, + { + "label": "easeInCubic", + "value": "easeInCubic" + }, + { + "label": "easeOutCubic", + "value": "easeOutCubic" + }, + { + "label": "easeInOutCubic", + "value": "easeInOutCubic" + }, + { + "label": "easeInQuart", + "value": "easeInQuart" + }, + { + "label": "easeOutQuart", + "value": "easeOutQuart" + }, + { + "label": "easeInOutQuart", + "value": "easeInOutQuart" + }, + { + "label": "easeInQuint", + "value": "easeInQuint" + }, + { + "label": "easeOutQuint", + "value": "easeOutQuint" + }, + { + "label": "easeInOutQuint", + "value": "easeInOutQuint" + }, + { + "label": "easeInOutSine", + "value": "easeInOutSine" + }, + { + "label": "easeInExpo", + "value": "easeInExpo" + }, + { + "label": "easeOutExpo", + "value": "easeOutExpo" + }, + { + "label": "easeInOutExpo", + "value": "easeInOutExpo" + }, + { + "label": "easeInCirc", + "value": "easeInCirc" + }, + { + "label": "easeOutCirc", + "value": "easeOutCirc" + }, + { + "label": "easeInOutCirc", + "value": "easeInOutCirc" + }, + { + "label": "easeOutBounce", + "value": "easeOutBounce" + }, + { + "label": "easeInBack", + "value": "easeInBack" + }, + { + "label": "easeOutBack", + "value": "easeOutBack" + }, + { + "label": "easeInOutBack", + "value": "easeInOutBack" + }, + { + "label": "elastic", + "value": "elastic" + }, + { + "label": "swingFromTo", + "value": "swingFromTo" + }, + { + "label": "swingFrom", + "value": "swingFrom" + }, + { + "label": "swingTo", + "value": "swingTo" + }, + { + "label": "bounce", + "value": "bounce" + }, + { + "label": "bouncePast", + "value": "bouncePast" + }, + { + "label": "easeFromTo", + "value": "easeFromTo" + }, + { + "label": "easeFrom", + "value": "easeFrom" + }, + { + "label": "easeTo", + "value": "easeTo" + } + ], + "name": "FadeOutEasing" + }, + { + "value": "Idle", + "type": "Choice", + "label": "", + "choices": [ + { + "label": "Idle", + "value": "Idle" + }, + { + "label": "Focused", + "value": "Focused" + }, + { + "label": "Pressed", + "value": "Pressed" + } + ], + "hidden": true, + "name": "PreviousState" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ButtonFSM" + }, + { + "propertyName": "Tween" + }, + { + "folderName": "Color", + "children": [ + { + "propertyName": "IdleColorTint" + }, + { + "propertyName": "FocusedColorTint" + }, + { + "propertyName": "PressedColorTint" + } + ] + }, + { + "folderName": "Speed", + "children": [ + { + "propertyName": "FadeInDuration" + }, + { + "propertyName": "FadeOutDuration" + }, + { + "propertyName": "FadeInEasing" + }, + { + "propertyName": "FadeOutEasing" + } + ] + }, + { + "propertyName": "PreviousState" + } + ] + } + } + ], + "eventsBasedObjects": [] + }, + { + "author": "", + "category": "General", + "dimension": "3D", + "extensionNamespace": "", + "fullName": "3D terrain", + "gdevelopVersion": ">=5.5.222", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXRlcnJhaW4iIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTQsNkwxMC4yNSwxMUwxMy4xLDE0LjhMMTEuNSwxNkM5LjgxLDEzLjc1IDcsMTAgNywxMEwxLDE4SDIzTDE0LDZaIiAvPjwvc3ZnPg==", + "name": "Terrain3D", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/10e0a26c0d500830dfe23b94138beb0ef61607ba62bca05a5aa5c849777f1b06_terrain.svg", + "shortDescription": "A ground with hills and valleys that can be sculpted and painted in the editor, with collisions.", + "version": "0.1.0", + "description": [ + "A 3D terrain object for open worlds, adventure or racing games: hills, mountains, islands or valleys, with painted layers of grass, dirt, rock or sand.", + "", + "- **Sculpt and paint in the scene editor**: select a terrain to show the tools (raise, lower, smooth, flatten and paint 4 layers).", + "- **Start from a relief** (hills, mountains, island) or **from a heightmap image**.", + "- **Steep slopes automatically show rock** (or any layer).", + "- **Natural borders between layers**: stones and tufts of a texture cover the gaps of the other where layers meet (height blending), and the repetition of textures is made less visible (texture variation).", + "- **Physics**: add the **3D physics** behavior to the terrain. Characters and objects collide with the ground exactly. The terrain is always static.", + "- **Pathfinding**: add the **floor/obstacle for pathfinding (navmesh based)** behavior to the terrain, so that characters with the navmesh pathfinding behavior walk on its hills and around its cliffs (in recent GDevelop versions).", + "- **Shape it with the Edits property**: a list of raise, flatten, smooth and paint strokes written in JSON, shown in the scene editor. It's readable and can be changed by hand or by an AI agent, like the relief, the heightmap and the other properties. Actions only change the terrain during the game.", + "- **Change it during the game** with actions (raise, lower, flatten, smooth, paint, along a line for paths and rivers) and read the ground height, slope or layer with expressions.", + "- **Grass**: add a **terrain grass** object over the terrain. Thousands of blades sway in the wind, grow where a layer (like grass) is painted and where the ground is not too steep, and are pushed aside by characters. Sculpt or paint the terrain and the grass follows.", + "- **Forests, rocks and bushes**: add a **scattered models** object over the terrain. It places hundreds or thousands of copies of a 3D model (trees, rocks, bushes, flowers...) on the ground and draws them all at once, which is much faster than an object for each. Copies grow where a layer is painted, away from steep slopes, and follow the ground when it's sculpted. Add or erase them with the brushes of the scene editor or with the Edits property, give trees trunk collisions, and cut them down with actions. Ready-made forests, rocks and bushes are in the asset store.", + "- **Tunnels and caves**: make holes in the ground (and fill them again) with the brushes, the Edits property or actions, and put a 3D model of the tunnel or the cave (with a mesh collision shape) under the ground. Nothing is drawn in holes, and characters, cars and rays go through them.", + "- **Made for large worlds**: the terrain is split in chunks that are hidden when off-screen and simplified when far away, and collisions are updated only where the ground changes." + ], + "origin": { + "identifier": "Terrain3D", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d", + "terrain", + "ground", + "heightmap", + "landscape", + "open world", + "grass", + "foliage", + "forest", + "trees", + "tunnel", + "cave" + ], + "authorIds": [], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [ + { + "fullName": "Define helper classes", + "functionType": "Action", + "name": "DefineHelperClasses", + "sentence": "Define helper classes JavaScript code", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (gdjs.__terrain3DExtension) {", + " return;", + "}", + "", + "// Tested by the gameplay tests of the \"3D island terrain\" example (GDevelop-examples repository).", + "", + "const groundsByScene = new WeakMap();", + "/**", + " * The grounds of a scene (the terrains), followed by the terrain grass.", + " *", + " * A ground has: `getVersion()` (changing when the ground changes),", + " * `containsPoint(x, y)`, `getHeightAt(x, y)` (Z position of the ground),", + " * `getSlopeAt(x, y)` (in degrees), `getLayerWeightAt(x, y, layer)` (how", + " * much a painted layer, from 1 to 4, is there, from 0 to 1) and optionally", + " * `getTintAt(x, y)` (the painted tint, as red, green and blue from 0 to 255).", + " * @param {gdjs.RuntimeScene} runtimeScene", + " * @returns {Set}", + " */", + "const getGrounds = (runtimeScene) => {", + " let grounds = groundsByScene.get(runtimeScene);", + " if (!grounds) {", + " grounds = new Set();", + " groundsByScene.set(runtimeScene, grounds);", + " }", + " return grounds;", + "};", + "", + "/**", + " * @param {number} seed", + " * @returns {() => number} Random numbers between 0 (included) and 1 (excluded), the same for the same seed.", + " */", + "const createRandom = (seed) => {", + " let state = seed >>> 0 || 1;", + " return () => {", + " state ^= state << 13;", + " state ^= state >>> 17;", + " state ^= state << 5;", + " return (state >>> 0) / 4294967296;", + " };", + "};", + "", + "/**", + " * Find the highest ground at a position: grounds can overlap (like an island over a sea floor).", + " * @param {Iterable} grounds", + " * @param {{ground: object | null, z: number}} result Set to the ground (null if there is none) and its Z position.", + " */", + "const findHighestGround = (grounds, x, y, result) => {", + " result.ground = null;", + " for (const ground of grounds) {", + " if (!ground.containsPoint(x, y)) continue;", + " const z = ground.getHeightAt(x, y);", + " if (!result.ground || z > result.z) {", + " result.ground = ground;", + " result.z = z;", + " }", + " }", + " return result;", + "};", + "", + "// The grounds or the objects placed on them can change every frame (while", + "// sculpting or moving them in the editor): things are placed again at most this often.", + "const PLACEMENT_DELAY = 0.3;", + "", + "/**", + " * Tells when things placed on the grounds of a scene (grass blades, scattered", + " * models) must be placed again: after the grounds or the given properties", + " * changed, at most every `PLACEMENT_DELAY` seconds.", + " */", + "class GroundPlacement {", + " /** @param {gdjs.RuntimeScene} runtimeScene */", + " constructor(runtimeScene) {", + " this.grounds = getGrounds(runtimeScene);", + " this.placedFrom = '';", + " this.timeSincePlacement = 0;", + " }", + "", + " /**", + " * @param {number} elapsedSeconds", + " * @param {string} propertiesKey Changes when the things must be placed again.", + " */", + " shouldPlaceAgain(elapsedSeconds, propertiesKey) {", + " this.timeSincePlacement += elapsedSeconds;", + " if (this.placedFrom && this.timeSincePlacement < PLACEMENT_DELAY) return false;", + " let placementKey = propertiesKey;", + " for (const ground of this.grounds) placementKey += '|' + ground.getVersion();", + " if (placementKey === this.placedFrom) return false;", + " this.placedFrom = placementKey;", + " this.timeSincePlacement = 0;", + " return true;", + " }", + "", + " placeAgain() {", + " this.placedFrom = '';", + " }", + "}", + "", + "/**", + " * Replace parts of the shader code of a Three.js material, with an error if", + " * a part is missing (for example after an update of Three.js).", + " * @param {string} shaderCode", + " * @param {Array<[string, string]>} replacements The searched code and its replacement.", + " * @param {string} materialName", + " */", + "const patchShaderCode = (shaderCode, replacements, materialName) => {", + " for (const [searchedCode, newCode] of replacements) {", + " if (!shaderCode.includes(searchedCode)) {", + " console.error(`${materialName}: \"${searchedCode}\" was not found in the shader, which won't look as expected.`);", + " }", + " shaderCode = shaderCode.replace(searchedCode, newCode);", + " }", + " return shaderCode;", + "};", + "", + "// Size of the object inner area. Instances are scaled from it, so these only", + "// define the default size of a new terrain.", + "const AREA_SIZE = 4096;", + "const AREA_DEPTH = 600;", + "", + "// Terrains have at most 8 x 8 chunks (so at most 64 draw calls), of at least", + "// 32 x 32 cells.", + "const MIN_CHUNK_CELLS = 32;", + "const MAX_CHUNKS_PER_SIDE = 8;", + "/** @param {number} resolution */", + "const getChunkCells = (resolution) => Math.max(MIN_CHUNK_CELLS, resolution / MAX_CHUNKS_PER_SIDE);", + "// Grid steps of each level of detail, and the distance (in chunk sizes)", + "// from which each one is used.", + "const LOD_STEPS = [1, 2, 4];", + "const LOD_DISTANCES = [0, 3, 6];", + "", + "const LAYER_COUNT = 4;", + "const RESOLUTIONS = [64, 128, 256, 512, 1024];", + "const DEFAULT_RESOLUTION = 256;", + "// Each undo step is a copy of the data: fewer are kept for big terrains.", + "const UNDO_STEPS_SAMPLES = 20 * 257 * 257;", + "", + "const clamp01 = (value) => (value < 0 ? 0 : value > 1 ? 1 : value);", + "const clampInteger = (value, min, max) =>", + " value < min ? min : value > max ? max : Math.floor(value);", + "", + "/**", + " * A rectangle of grid samples, inclusive. Empty when `minI > maxI`.", + " */", + "class SampleRectangle {", + " constructor() {", + " this.clear();", + " }", + "", + " clear() {", + " this.minI = Number.MAX_SAFE_INTEGER;", + " this.minJ = Number.MAX_SAFE_INTEGER;", + " this.maxI = -1;", + " this.maxJ = -1;", + " }", + "", + " isEmpty() {", + " return this.minI > this.maxI;", + " }", + "", + " /** @param {SampleRectangle} other */", + " add(other) {", + " if (other.isEmpty()) return;", + " this.minI = Math.min(this.minI, other.minI);", + " this.minJ = Math.min(this.minJ, other.minJ);", + " this.maxI = Math.max(this.maxI, other.maxI);", + " this.maxJ = Math.max(this.maxJ, other.maxJ);", + " }", + "", + " set(minI, minJ, maxI, maxJ) {", + " this.minI = minI;", + " this.minJ = minJ;", + " this.maxI = maxI;", + " this.maxJ = maxJ;", + " return this;", + " }", + "}", + "", + "/**", + " * The heights (between 0 and 1) and the painted layers weights of a square", + " * grid of `size * size` samples.", + " */", + "class TerrainData {", + " /** @param {number} resolution The number of cells on each side. */", + " constructor(resolution) {", + " this.resolution = resolution;", + " this.size = resolution + 1;", + " this.heights = new Float32Array(this.size * this.size);", + " /** 4 weights (0-255) per sample, one for each layer. */", + " this.splat = new Uint8Array(this.size * this.size * LAYER_COUNT);", + " this.fillLayer(0);", + " /**", + " * 4 bytes per sample: a tint multiplying the layers (red, green and", + " * blue, white for none) and the wetness (0 for dry).", + " */", + " this.colors = new Uint8Array(this.size * this.size * 4);", + " this.clearColors();", + " }", + "", + " clearColors() {", + " this.colors.fill(255);", + " for (let index = 3; index < this.colors.length; index += 4) {", + " this.colors[index] = 0;", + " }", + " }", + "", + " /** @param {number} layer */", + " fillLayer(layer) {", + " this.splat.fill(0);", + " for (let index = layer; index < this.splat.length; index += LAYER_COUNT) {", + " this.splat[index] = 255;", + " }", + " }", + "", + " getHeight(i, j) {", + " const last = this.resolution;", + " const clampedI = i < 0 ? 0 : i > last ? last : i;", + " const clampedJ = j < 0 ? 0 : j > last ? last : j;", + " return this.heights[clampedJ * this.size + clampedI];", + " }", + "", + " /**", + " * @param {number} gridX", + " * @param {number} gridY", + " * @returns {number} The height interpolated on the triangles that are drawn.", + " */", + " sampleHeight(gridX, gridY) {", + " const x = Math.min(Math.max(gridX, 0), this.resolution);", + " const y = Math.min(Math.max(gridY, 0), this.resolution);", + " const i = Math.min(Math.floor(x), this.resolution - 1);", + " const j = Math.min(Math.floor(y), this.resolution - 1);", + " const fx = x - i;", + " const fy = y - j;", + " const topRight = this.getHeight(i + 1, j);", + " const bottomLeft = this.getHeight(i, j + 1);", + " if (fx + fy <= 1) {", + " const topLeft = this.getHeight(i, j);", + " return topLeft + fx * (topRight - topLeft) + fy * (bottomLeft - topLeft);", + " }", + " const bottomRight = this.getHeight(i + 1, j + 1);", + " return (", + " bottomRight +", + " (1 - fx) * (bottomLeft - bottomRight) +", + " (1 - fy) * (topRight - bottomRight)", + " );", + " }", + "", + " /** @returns {number} The weight (0-1) of a layer, interpolated between samples. */", + " sampleLayerWeight(gridX, gridY, layer) {", + " const x = Math.min(Math.max(gridX, 0), this.resolution);", + " const y = Math.min(Math.max(gridY, 0), this.resolution);", + " const i = Math.min(Math.floor(x), this.resolution - 1);", + " const j = Math.min(Math.floor(y), this.resolution - 1);", + " const fx = x - i;", + " const fy = y - j;", + " const weight = (i, j) => this.splat[(j * this.size + i) * LAYER_COUNT + layer];", + " const top = weight(i, j) + fx * (weight(i + 1, j) - weight(i, j));", + " const bottom = weight(i, j + 1) + fx * (weight(i + 1, j + 1) - weight(i, j + 1));", + " return (top + fy * (bottom - top)) / 255;", + " }", + "", + " /**", + " * @param {number} channel 0, 1 and 2 for the tint, 3 for the wetness.", + " * @returns {number} The channel (0-1), interpolated between samples.", + " */", + " sampleColor(gridX, gridY, channel) {", + " const x = Math.min(Math.max(gridX, 0), this.resolution);", + " const y = Math.min(Math.max(gridY, 0), this.resolution);", + " const i = Math.min(Math.floor(x), this.resolution - 1);", + " const j = Math.min(Math.floor(y), this.resolution - 1);", + " const fx = x - i;", + " const fy = y - j;", + " const value = (i, j) => this.colors[(j * this.size + i) * 4 + channel];", + " const top = value(i, j) + fx * (value(i + 1, j) - value(i, j));", + " const bottom = value(i, j + 1) + fx * (value(i + 1, j + 1) - value(i, j + 1));", + " return (top + fy * (bottom - top)) / 255;", + " }", + "", + " /**", + " * @param {number} index The index of a sample.", + " * @returns {boolean} true if no layer is painted on the sample: it's a hole.", + " */", + " isHole(index) {", + " const offset = index * LAYER_COUNT;", + " return (this.splat[offset] | this.splat[offset + 1] | this.splat[offset + 2] | this.splat[offset + 3]) === 0;", + " }", + "", + " /**", + " * @returns {number} The sum of the layer weights (0-1), interpolated between samples: there is a", + " * hole where it's under 0.5, like what is drawn.", + " */", + " sampleSolidity(gridX, gridY) {", + " let solidity = 0;", + " for (let layer = 0; layer < LAYER_COUNT; layer++) solidity += this.sampleLayerWeight(gridX, gridY, layer);", + " return solidity;", + " }", + "", + " /** @returns {number} The index (from 0) of the layer painted the most. */", + " getDominantLayer(gridX, gridY) {", + " const i = clampInteger(Math.round(gridX), 0, this.resolution);", + " const j = clampInteger(Math.round(gridY), 0, this.resolution);", + " const offset = (j * this.size + i) * LAYER_COUNT;", + " let dominantLayer = 0;", + " for (let layer = 1; layer < LAYER_COUNT; layer++) {", + " if (this.splat[offset + layer] > this.splat[offset + dominantLayer]) {", + " dominantLayer = layer;", + " }", + " }", + " return dominantLayer;", + " }", + "", + " /** @param {TerrainData} other */", + " copyFrom(other) {", + " this.heights.set(other.heights);", + " this.splat.set(other.splat);", + " this.colors.set(other.colors);", + " }", + "", + " /**", + " * @param {number} resolution", + " * @returns {TerrainData} A copy of this data with another resolution.", + " */", + " resampled(resolution) {", + " const data = new TerrainData(resolution);", + " const ratio = this.resolution / resolution;", + " for (let j = 0; j < data.size; j++) {", + " for (let i = 0; i < data.size; i++) {", + " const index = j * data.size + i;", + " data.heights[index] = this.sampleHeight(i * ratio, j * ratio);", + " const sourceOffset =", + " (Math.round(j * ratio) * this.size + Math.round(i * ratio)) * LAYER_COUNT;", + " for (let layer = 0; layer < LAYER_COUNT; layer++) {", + " data.splat[index * LAYER_COUNT + layer] = this.splat[sourceOffset + layer];", + " }", + " for (let channel = 0; channel < 4; channel++) {", + " data.colors[index * 4 + channel] = this.colors[sourceOffset + channel];", + " }", + " }", + " }", + " return data;", + " }", + "}", + "", + "/**", + " * Brushes change the samples around a segment, with an effect going smoothly", + " * from full on the segment to none at `radius`. A point is a segment of", + " * length 0. Positions are in scene units relative to the terrain origin.", + " */", + "const brushes = {", + " /**", + " * @param {TerrainData} data", + " * @param {{cellWidth: number, cellHeight: number, ax: number, ay: number, bx: number, by: number, radius: number}} stroke", + " * @param {(sampleIndex: number, weight: number, t: number) => void} apply Called with the weight of", + " * the brush and the position (from 0 to 1) of the nearest point of the segment.", + " * @param {SampleRectangle} changedSamples", + " */", + " forEachSample(data, stroke, apply, changedSamples) {", + " const { cellWidth, cellHeight, ax, ay, bx, by, radius } = stroke;", + " const last = data.resolution;", + " const minI = clampInteger(Math.floor((Math.min(ax, bx) - radius) / cellWidth), 0, last);", + " const maxI = clampInteger(Math.ceil((Math.max(ax, bx) + radius) / cellWidth), 0, last);", + " const minJ = clampInteger(Math.floor((Math.min(ay, by) - radius) / cellHeight), 0, last);", + " const maxJ = clampInteger(Math.ceil((Math.max(ay, by) + radius) / cellHeight), 0, last);", + " changedSamples.set(minI, minJ, maxI, maxJ);", + " if (radius <= 0) return;", + "", + " const abX = bx - ax;", + " const abY = by - ay;", + " const abLengthSquared = abX * abX + abY * abY;", + " const radiusSquared = radius * radius;", + " for (let j = minJ; j <= maxJ; j++) {", + " const y = j * cellHeight;", + " for (let i = minI; i <= maxI; i++) {", + " const x = i * cellWidth;", + " const t =", + " abLengthSquared > 0", + " ? clamp01(((x - ax) * abX + (y - ay) * abY) / abLengthSquared)", + " : 0;", + " const dx = x - (ax + t * abX);", + " const dy = y - (ay + t * abY);", + " const distanceSquared = dx * dx + dy * dy;", + " if (distanceSquared >= radiusSquared) continue;", + " const falloff = 1 - distanceSquared / radiusSquared;", + " apply(j * data.size + i, falloff * falloff, t);", + " }", + " }", + " },", + "", + " raise(data, stroke, amount, changedSamples) {", + " const { heights } = data;", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index, weight) => {", + " heights[index] = clamp01(heights[index] + amount * weight);", + " },", + " changedSamples", + " );", + " },", + "", + " /**", + " * @param {number} targetHeight", + " * @param {number} [endTargetHeight] The target at the end of the segment, for a slope (like a road", + " * going up). The same target as at its start by default.", + " */", + " flatten(data, stroke, targetHeight, strength, changedSamples, endTargetHeight = targetHeight) {", + " const { heights } = data;", + " const startTarget = clamp01(targetHeight);", + " const endTarget = clamp01(endTargetHeight);", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index, weight, t) => {", + " const target = startTarget + (endTarget - startTarget) * t;", + " heights[index] += (target - heights[index]) * clamp01(weight * strength);", + " },", + " changedSamples", + " );", + " },", + "", + " smooth(data, stroke, strength, changedSamples) {", + " const { heights, size } = data;", + " const changes = [];", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index, weight) => {", + " const i = index % size;", + " const j = (index - i) / size;", + " let sum = 0;", + " for (let dj = -1; dj <= 1; dj++) {", + " for (let di = -1; di <= 1; di++) {", + " sum += data.getHeight(i + di, j + dj);", + " }", + " }", + " changes.push(index, heights[index] + (sum / 9 - heights[index]) * clamp01(weight * strength));", + " },", + " changedSamples", + " );", + " // Written after all averages are computed, so that the result", + " // doesn't depend on the order in which samples are visited.", + " for (let index = 0; index < changes.length; index += 2) {", + " heights[changes[index]] = changes[index + 1];", + " }", + " },", + "", + " paint(data, stroke, layer, strength, changedSamples) {", + " const { splat } = data;", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index, weight) => {", + " const amount = clamp01(weight * strength);", + " const offset = index * LAYER_COUNT;", + " for (let otherLayer = 0; otherLayer < LAYER_COUNT; otherLayer++) {", + " const target = otherLayer === layer ? 255 : 0;", + " const value = splat[offset + otherLayer];", + " // Rounded towards the target, so that light strokes still paint.", + " const change = (target - value) * amount;", + " splat[offset + otherLayer] = value + (change > 0 ? Math.ceil(change) : Math.floor(change));", + " }", + " },", + " changedSamples", + " );", + " },", + "", + " /** Remove all the layers, making a hole (`fill` or painting a layer fills it again). */", + " hole(data, stroke, changedSamples) {", + " const { splat } = data;", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index) => {", + " splat.fill(0, index * LAYER_COUNT, (index + 1) * LAYER_COUNT);", + " },", + " changedSamples", + " );", + " },", + "", + " /**", + " * Fill the holes in the radius with the layers of the ground around them:", + " * each pass fills the holes next to the ground with the average of their", + " * neighbors, from the edges of the holes inward.", + " */", + " fill(data, stroke, changedSamples) {", + " const { splat, size, resolution } = data;", + " /** @type {number[]} */", + " let holes = [];", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index) => {", + " if (data.isHole(index)) holes.push(index);", + " },", + " changedSamples", + " );", + " const filledWeights = new Float32Array(holes.length * LAYER_COUNT);", + " while (holes.length > 0) {", + " /** @type {number[]} */", + " const remainingHoles = [];", + " /** @type {number[]} */", + " const filledHoles = [];", + " for (const index of holes) {", + " const i = index % size;", + " const j = (index - i) / size;", + " const weights = filledWeights.subarray(filledHoles.length * LAYER_COUNT, (filledHoles.length + 1) * LAYER_COUNT);", + " weights.fill(0);", + " for (let neighborJ = Math.max(j - 1, 0); neighborJ <= Math.min(j + 1, resolution); neighborJ++) {", + " for (let neighborI = Math.max(i - 1, 0); neighborI <= Math.min(i + 1, resolution); neighborI++) {", + " const neighbor = neighborJ * size + neighborI;", + " if (data.isHole(neighbor)) continue;", + " for (let layer = 0; layer < LAYER_COUNT; layer++) weights[layer] += splat[neighbor * LAYER_COUNT + layer];", + " }", + " }", + " if (weights[0] + weights[1] + weights[2] + weights[3] > 0) filledHoles.push(index);", + " else remainingHoles.push(index);", + " }", + " if (filledHoles.length === 0) {", + " // No ground around (a hole bigger than the brush): the first layer is used.", + " for (const index of remainingHoles) splat[index * LAYER_COUNT] = 255;", + " break;", + " }", + " // Filled after the pass, so that the holes are filled from the edges inward.", + " filledHoles.forEach((index, filledIndex) => {", + " const weights = filledWeights.subarray(filledIndex * LAYER_COUNT, (filledIndex + 1) * LAYER_COUNT);", + " const total = weights[0] + weights[1] + weights[2] + weights[3];", + " for (let layer = 0; layer < LAYER_COUNT; layer++) {", + " splat[index * LAYER_COUNT + layer] = Math.round((weights[layer] / total) * 255);", + " }", + " });", + " holes = remainingHoles;", + " }", + " },", + "", + " /** @param {number[]} color The red, green and blue (0-255) of the tint: white removes it. */", + " tint(data, stroke, color, strength, changedSamples) {", + " brushes.paintColors(data, stroke, 0, color, strength, changedSamples);", + " },", + "", + " /** @param {number} wetness From 0 (dry) to 1. */", + " wet(data, stroke, wetness, strength, changedSamples) {", + " brushes.paintColors(data, stroke, 3, [Math.round(clamp01(wetness) * 255)], strength, changedSamples);", + " },", + "", + " /**", + " * Bring channels of the colors (see `TerrainData.colors`) towards targets.", + " * @param {number} firstChannel", + " * @param {number[]} targets From 0 to 255, for the channels from the first one.", + " */", + " paintColors(data, stroke, firstChannel, targets, strength, changedSamples) {", + " const { colors } = data;", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index, weight) => {", + " const amount = clamp01(weight * strength);", + " for (let channel = 0; channel < targets.length; channel++) {", + " const offset = index * 4 + firstChannel + channel;", + " // Rounded towards the target, so that light strokes still paint.", + " const change = (targets[channel] - colors[offset]) * amount;", + " colors[offset] += change > 0 ? Math.ceil(change) : Math.floor(change);", + " }", + " },", + " changedSamples", + " );", + " },", + "};", + "", + "const relief = (() => {", + " const hash = (x, y, seed) => {", + " let h = Math.imul(x, 374761393) + Math.imul(y, 668265263) + Math.imul(seed, 982451653);", + " h = Math.imul(h ^ (h >>> 13), 1274126177);", + " return ((h ^ (h >>> 16)) >>> 0) / 4294967296;", + " };", + " const valueNoise = (x, y, seed) => {", + " const x0 = Math.floor(x);", + " const y0 = Math.floor(y);", + " const fx = x - x0;", + " const fy = y - y0;", + " const sx = fx * fx * (3 - 2 * fx);", + " const sy = fy * fy * (3 - 2 * fy);", + " const top = hash(x0, y0, seed) + sx * (hash(x0 + 1, y0, seed) - hash(x0, y0, seed));", + " const bottom =", + " hash(x0, y0 + 1, seed) + sx * (hash(x0 + 1, y0 + 1, seed) - hash(x0, y0 + 1, seed));", + " return top + sy * (bottom - top);", + " };", + " /** Fractal noise between 0 and 1. */", + " const fractalNoise = (x, y, seed) => {", + " let value = 0;", + " let amplitude = 0.5;", + " let frequency = 1;", + " for (let octave = 0; octave < 5; octave++) {", + " value += amplitude * valueNoise(x * frequency, y * frequency, seed + octave);", + " amplitude *= 0.5;", + " frequency *= 2;", + " }", + " return value / 0.96875;", + " };", + " const smoothstep = (edge0, edge1, x) => {", + " const t = clamp01((x - edge0) / (edge1 - edge0));", + " return t * t * (3 - 2 * t);", + " };", + " const reliefHeights = {", + " Flat: () => 0,", + " Hills: (u, v, seed) => 0.05 + 0.5 * Math.pow(fractalNoise(u * 3, v * 3, seed), 2),", + " Mountains: (u, v, seed) => {", + " const ridge = 1 - Math.abs(2 * fractalNoise(u * 2.5, v * 2.5, seed) - 1);", + " return 0.9 * Math.pow(ridge, 2.5);", + " },", + " Island: (u, v, seed) => {", + " const distanceToCenter = 2 * Math.hypot(u - 0.5, v - 0.5);", + " const coast = 0.2 * (fractalNoise(u * 4, v * 4, seed + 7) - 0.5);", + " const land = smoothstep(0.95, 0.55, distanceToCenter + coast);", + " return land * (0.12 + 0.6 * Math.pow(fractalNoise(u * 3, v * 3, seed), 1.5));", + " },", + " };", + " const beachLayer = 3;", + "", + " return {", + " /**", + " * @param {TerrainData} data", + " * @param {string} reliefName Flat, Hills, Mountains or Island.", + " * @param {number} seed", + " */", + " generate(data, reliefName, seed) {", + " const getHeight = reliefHeights[reliefName] || reliefHeights.Flat;", + " const integerSeed = Math.floor(seed) | 0;", + " data.fillLayer(0);", + " for (let j = 0; j < data.size; j++) {", + " for (let i = 0; i < data.size; i++) {", + " const index = j * data.size + i;", + " const height = clamp01(", + " getHeight(i / data.resolution, j / data.resolution, integerSeed)", + " );", + " data.heights[index] = height;", + " if (reliefName === 'Island') {", + " const sand = Math.round(255 * smoothstep(0.16, 0.1, height));", + " const offset = index * LAYER_COUNT;", + " data.splat[offset] = 255 - sand;", + " data.splat[offset + beachLayer] = sand;", + " }", + " }", + " }", + " },", + " };", + "})();", + "", + "/**", + " * Edits written in the Edits property: a JSON list of brush strokes, like", + " * `[{\"tool\": \"raise\", \"x\": 0.5, \"y\": 0.5, \"radius\": 0.2, \"height\": 0.3}]`,", + " * applied on the relief or the heightmap. Positions and radius are fractions", + " * of the terrain size, heights and Z positions fractions of its depth: they", + " * are the same for all instances, whatever their size. They are made to be", + " * read and written by people and by AI agents.", + " */", + "const edits = {", + " /**", + " * @param {TerrainData} data", + " * @param {string} text", + " */", + " apply(data, text) {", + " if (!text.trim()) return;", + " let editList;", + " try {", + " editList = JSON.parse(text);", + " } catch (error) {", + " console.warn('The edits of a terrain are not valid JSON: ' + error.message);", + " return;", + " }", + " if (!Array.isArray(editList)) {", + " console.warn('The edits of a terrain must be a JSON list (like [{\"tool\": \"raise\", ...}]).');", + " return;", + " }", + " const numberOr = (value, defaultValue) => (typeof value === 'number' && isFinite(value) ? value : defaultValue);", + " const changedSamples = new SampleRectangle();", + " for (const edit of editList) {", + " if (!edit || typeof edit !== 'object') continue;", + " const x = numberOr(edit.x, 0.5);", + " const y = numberOr(edit.y, 0.5);", + " const stroke = {", + " cellWidth: 1 / data.resolution,", + " cellHeight: 1 / data.resolution,", + " ax: x,", + " ay: y,", + " bx: numberOr(edit.toX, x),", + " by: numberOr(edit.toY, y),", + " radius: numberOr(edit.radius, 0.1),", + " };", + " const strength = clamp01(numberOr(edit.strength, 1));", + " if (edit.tool === 'raise') {", + " brushes.raise(data, stroke, numberOr(edit.height, 0.1), changedSamples);", + " } else if (edit.tool === 'flatten') {", + " const z = numberOr(edit.z, 0);", + " brushes.flatten(data, stroke, z, strength, changedSamples, numberOr(edit.toZ, z));", + " } else if (edit.tool === 'smooth') {", + " // Smoothing the same fraction of the terrain at any resolution.", + " const passes = Math.max(Math.round(((1 + 9 * strength) * data.resolution) / 256), 1);", + " for (let pass = 0; pass < passes; pass++) brushes.smooth(data, stroke, 1, changedSamples);", + " } else if (edit.tool === 'paint') {", + " const layer = clampInteger(numberOr(edit.layer, 1) - 1, 0, LAYER_COUNT - 1);", + " brushes.paint(data, stroke, layer, strength, changedSamples);", + " } else if (edit.tool === 'tint') {", + " const color = gdjs.rgbOrHexToRGBColor(typeof edit.color === 'string' ? edit.color : '255;255;255');", + " brushes.tint(data, stroke, color, strength, changedSamples);", + " } else if (edit.tool === 'wetness') {", + " brushes.wet(data, stroke, numberOr(edit.amount, 1), strength, changedSamples);", + " } else if (edit.tool === 'hole') {", + " brushes.hole(data, stroke, changedSamples);", + " } else if (edit.tool === 'fill') {", + " brushes.fill(data, stroke, changedSamples);", + " } else {", + " console.warn('Unknown tool in the edits of a terrain: \"' + edit.tool + '\" (use raise, flatten, smooth, paint, tint, wetness, hole or fill).');", + " }", + " }", + " },", + "};", + "", + "/**", + " * @param {gdjs.RuntimeGame} game", + " * @param {string} imageResourceName", + " * @returns {CanvasImageSource | null} The image, or null if it's not loaded (yet).", + " */", + "const getLoadedImage = (game, imageResourceName) => {", + " const imageManager = game.getImageManager();", + " if (typeof imageManager.getImageSource === 'function') return imageManager.getImageSource(imageResourceName);", + " // Older GDevelop versions: a 192 x 192 placeholder is given for images that are not loaded.", + " const texture = imageManager.getPIXITexture(imageResourceName);", + " return texture && texture !== imageManager.getInvalidPIXITexture() ? texture.baseTexture.getDrawableSource() : null;", + "};", + "", + "/**", + " * Loads heights from an image: black is the lowest, white the highest.", + " * Heights have 16 bits of precision when the red channel holds the most", + " * significant byte and the green one the least significant (a gray image", + " * works as expected too).", + " * @param {TerrainData} data", + " * @param {gdjs.RuntimeGame} game", + " * @param {string} imageResourceName", + " * @returns {boolean} true if the image could be read.", + " */", + "const loadHeightsFromImage = (data, game, imageResourceName) => {", + " const source = getLoadedImage(game, imageResourceName);", + " if (!source) return false;", + "", + " const canvas = document.createElement('canvas');", + " canvas.width = source.width;", + " canvas.height = source.height;", + " const context = canvas.getContext('2d', { willReadFrequently: true });", + " if (!context) return false;", + " context.drawImage(source, 0, 0);", + " const pixels = context.getImageData(0, 0, source.width, source.height).data;", + " // Pixels are decoded before being interpolated, to keep the 16 bits.", + " const pixelHeight = (x, y) => {", + " const offset = (y * source.width + x) * 4;", + " return (256 * pixels[offset] + pixels[offset + 1]) / 65535;", + " };", + " for (let j = 0; j < data.size; j++) {", + " const y = (j / data.resolution) * (source.height - 1);", + " const y0 = Math.floor(y);", + " const y1 = Math.min(y0 + 1, source.height - 1);", + " for (let i = 0; i < data.size; i++) {", + " const x = (i / data.resolution) * (source.width - 1);", + " const x0 = Math.floor(x);", + " const x1 = Math.min(x0 + 1, source.width - 1);", + " const top = pixelHeight(x0, y0) + (x - x0) * (pixelHeight(x1, y0) - pixelHeight(x0, y0));", + " const bottom = pixelHeight(x0, y1) + (x - x0) * (pixelHeight(x1, y1) - pixelHeight(x0, y1));", + " data.heights[j * data.size + i] = top + (y - y0) * (bottom - top);", + " }", + " }", + " return true;", + "};", + "", + "/**", + " * Stores the data in a string. Only the differences with the base data (from", + " * the relief or the heightmap) are stored: heights on 16 bits, layer weights", + " * on 8 bits, then (since format 2) the tint and wetness on 8 bits. Each value is predicted from its neighbors and the", + " * prediction errors are written as variable length integers, with runs of", + " * zeros collapsed. Untouched or smoothly changed areas take almost no space.", + " */", + "const codec = (() => {", + " const formatVersion = '2';", + " // Format 1 has no tint and wetness.", + " const readableFormatVersions = ['1', '2'];", + "", + " class ByteWriter {", + " constructor() {", + " this.bytes = new Uint8Array(1024);", + " this.length = 0;", + " }", + " writeVarUint(value) {", + " if (this.length + 5 > this.bytes.length) {", + " const bytes = new Uint8Array(this.bytes.length * 2);", + " bytes.set(this.bytes);", + " this.bytes = bytes;", + " }", + " while (value >= 0x80) {", + " this.bytes[this.length++] = (value & 0x7f) | 0x80;", + " value >>>= 7;", + " }", + " this.bytes[this.length++] = value;", + " }", + " }", + "", + " class ByteReader {", + " /** @param {Uint8Array} bytes */", + " constructor(bytes) {", + " this.bytes = bytes;", + " this.position = 0;", + " }", + " readVarUint() {", + " let value = 0;", + " let shift = 0;", + " let byte;", + " do {", + " if (this.position >= this.bytes.length) throw new Error('Truncated terrain data.');", + " byte = this.bytes[this.position++];", + " value += (byte & 0x7f) * Math.pow(2, shift);", + " shift += 7;", + " } while (byte & 0x80);", + " return value;", + " }", + " }", + "", + " /** Predicts a value from the left, top and top-left ones. */", + " const predict = (values, size, index) => {", + " const i = index % size;", + " if (index < size) return i === 0 ? 0 : values[index - 1];", + " if (i === 0) return values[index - size];", + " return values[index - 1] + values[index - size] - values[index - size - 1];", + " };", + "", + " /**", + " * @param {ByteWriter} writer", + " * @param {Int32Array} values A grid of `size * size` values.", + " */", + " const writeGrid = (writer, values, size) => {", + " let zeroRun = 0;", + " for (let index = 0; index < values.length; index++) {", + " const error = values[index] - predict(values, size, index);", + " if (error === 0) {", + " zeroRun++;", + " continue;", + " }", + " if (zeroRun > 0) {", + " writer.writeVarUint(0);", + " writer.writeVarUint(zeroRun);", + " zeroRun = 0;", + " }", + " // Zigzag: small negative and positive numbers get small codes.", + " writer.writeVarUint(error > 0 ? error * 2 : -error * 2 - 1);", + " }", + " if (zeroRun > 0) {", + " writer.writeVarUint(0);", + " writer.writeVarUint(zeroRun);", + " }", + " };", + "", + " /** @param {ByteReader} reader */", + " const readGrid = (reader, size) => {", + " const values = new Int32Array(size * size);", + " let zeroRun = 0;", + " for (let index = 0; index < values.length; index++) {", + " let error = 0;", + " if (zeroRun > 0) {", + " zeroRun--;", + " } else {", + " const code = reader.readVarUint();", + " if (code === 0) {", + " zeroRun = reader.readVarUint() - 1;", + " } else {", + " error = code % 2 === 0 ? code / 2 : -(code + 1) / 2;", + " }", + " }", + " values[index] = predict(values, size, index) + error;", + " }", + " return values;", + " };", + "", + " const toBase64 = (bytes, length) => {", + " let binary = '';", + " for (let offset = 0; offset < length; offset += 0x8000) {", + " binary += String.fromCharCode.apply(null, bytes.subarray(offset, Math.min(offset + 0x8000, length)));", + " }", + " return btoa(binary);", + " };", + "", + " const fromBase64 = (base64) => {", + " const binary = atob(base64);", + " const bytes = new Uint8Array(binary.length);", + " for (let index = 0; index < binary.length; index++) {", + " bytes[index] = binary.charCodeAt(index);", + " }", + " return bytes;", + " };", + "", + " return {", + " /**", + " * @param {TerrainData} data", + " * @param {TerrainData} baseData The data without changes, with the same resolution.", + " */", + " encode(data, baseData) {", + " const writer = new ByteWriter();", + " const values = new Int32Array(data.heights.length);", + " for (let index = 0; index < values.length; index++) {", + " values[index] = Math.round((data.heights[index] - baseData.heights[index]) * 65535);", + " }", + " writeGrid(writer, values, data.size);", + " for (let layer = 0; layer < LAYER_COUNT; layer++) {", + " for (let index = 0; index < values.length; index++) {", + " const offset = index * LAYER_COUNT + layer;", + " values[index] = data.splat[offset] - baseData.splat[offset];", + " }", + " writeGrid(writer, values, data.size);", + " }", + " // Not painted, these grids are zeros: a few bytes each.", + " for (let channel = 0; channel < 4; channel++) {", + " for (let index = 0; index < values.length; index++) {", + " const offset = index * 4 + channel;", + " values[index] = data.colors[offset] - baseData.colors[offset];", + " }", + " writeGrid(writer, values, data.size);", + " }", + " return formatVersion + ';' + data.resolution + ';' + toBase64(writer.bytes, writer.length);", + " },", + "", + " /**", + " * Store grids of integers in a string, like the terrain data. Used by other objects (the scattered models).", + " * @param {Int32Array[]} grids Grids of `size * size` values.", + " * @param {number} size", + " */", + " encodeGrids(grids, size) {", + " const writer = new ByteWriter();", + " for (const grid of grids) writeGrid(writer, grid, size);", + " return toBase64(writer.bytes, writer.length);", + " },", + "", + " /**", + " * @param {string} base64 A string from `encodeGrids`.", + " * @param {number} size", + " * @param {number} count The number of grids.", + " * @returns {Int32Array[]} The grids (an error is thrown if the string is not valid).", + " */", + " decodeGrids(base64, size, count) {", + " const reader = new ByteReader(fromBase64(base64));", + " return Array.from({ length: count }, () => readGrid(reader, size));", + " },", + "", + " /**", + " * @param {string} text", + " * @param {(resolution: number) => TerrainData} createBaseData", + " * @returns {TerrainData | null} null if the text is empty or not valid.", + " */", + " decode(text, createBaseData) {", + " if (!text) return null;", + " const parts = text.split(';');", + " const resolution = parseInt(parts[1], 10);", + " if (readableFormatVersions.indexOf(parts[0]) === -1 || RESOLUTIONS.indexOf(resolution) === -1) {", + " return null;", + " }", + " try {", + " const reader = new ByteReader(fromBase64(parts[2]));", + " const data = createBaseData(resolution);", + " const heightChanges = readGrid(reader, data.size);", + " for (let index = 0; index < heightChanges.length; index++) {", + " data.heights[index] = clamp01(data.heights[index] + heightChanges[index] / 65535);", + " }", + " for (let layer = 0; layer < LAYER_COUNT; layer++) {", + " const weightChanges = readGrid(reader, data.size);", + " for (let index = 0; index < weightChanges.length; index++) {", + " const offset = index * LAYER_COUNT + layer;", + " // The base may have changed since the data was saved.", + " data.splat[offset] = Math.min(Math.max(data.splat[offset] + weightChanges[index], 0), 255);", + " }", + " }", + " if (parts[0] !== '1') {", + " for (let channel = 0; channel < 4; channel++) {", + " const colorChanges = readGrid(reader, data.size);", + " for (let index = 0; index < colorChanges.length; index++) {", + " const offset = index * 4 + channel;", + " data.colors[offset] = Math.min(Math.max(data.colors[offset] + colorChanges[index], 0), 255);", + " }", + " }", + " }", + " return data;", + " } catch (error) {", + " console.error('Terrain data could not be read:', error);", + " return null;", + " }", + " },", + " };", + "})();", + "", + "const lodIndices = new Map();", + "/**", + " * The triangles of a chunk, shared by all chunks of the same size. Chunk", + " * borders have a skirt going down, hiding the gaps between chunks drawn", + " * with different levels of detail.", + " * @param {number} chunkCells", + " * @param {number} step", + " */", + "const getLodIndex = (chunkCells, step) => {", + " const key = chunkCells + ';' + step;", + " const existingIndex = lodIndices.get(key);", + " if (existingIndex) return existingIndex;", + "", + " const sideVertices = chunkCells + 1;", + " const gridVertices = sideVertices * sideVertices;", + " const indices = [];", + " const grid = (i, j) => j * sideVertices + i;", + " for (let j = 0; j < chunkCells; j += step) {", + " for (let i = 0; i < chunkCells; i += step) {", + " const topLeft = grid(i, j);", + " const topRight = grid(i + step, j);", + " const bottomLeft = grid(i, j + step);", + " const bottomRight = grid(i + step, j + step);", + " indices.push(topLeft, topRight, bottomLeft, topRight, bottomRight, bottomLeft);", + " }", + " }", + " const borders = [", + " (k) => grid(k, 0),", + " (k) => grid(k, chunkCells),", + " (k) => grid(0, k),", + " (k) => grid(chunkCells, k),", + " ];", + " borders.forEach((borderVertex, border) => {", + " const skirtVertex = (k) => gridVertices + border * sideVertices + k;", + " for (let k = 0; k < chunkCells; k += step) {", + " const a = borderVertex(k);", + " const b = borderVertex(k + step);", + " const skirtA = skirtVertex(k);", + " const skirtB = skirtVertex(k + step);", + " // Both faces, as a skirt can be seen from either side.", + " indices.push(a, b, skirtB, a, skirtB, skirtA, a, skirtB, b, a, skirtA, skirtB);", + " }", + " });", + " const index = new THREE.BufferAttribute(new Uint16Array(indices), 1);", + " lodIndices.set(key, index);", + " return index;", + "};", + "", + "const vertexShaderDeclarations = `", + "varying vec2 vTerrainLocalXY;", + "varying float vTerrainLocalZ;", + "uniform float terrainWorldScale;", + "varying vec2 vTerrainWorldXY;", + "varying float vTerrainUp;", + "`;", + "const vertexShaderCode = `", + "vTerrainLocalXY = position.xy;", + "vTerrainLocalZ = position.z;", + "vTerrainWorldXY = (modelMatrix * vec4(transformed, 1.0)).xy * terrainWorldScale;", + "vTerrainUp = normalize(transpose(inverse(mat3(modelMatrix))) * objectNormal).z;", + "`;", + "const fragmentShaderDeclarations = `", + "uniform sampler2D terrainSplatMap;", + "uniform vec2 terrainSplatUvScaleAndOffset;", + "uniform vec3 terrainLayerColors[4];", + "uniform sampler2D terrainLayerMap0;", + "uniform sampler2D terrainLayerMap1;", + "uniform sampler2D terrainLayerMap2;", + "uniform sampler2D terrainLayerMap3;", + "uniform sampler2D terrainLayerNormalMap0;", + "uniform sampler2D terrainLayerNormalMap1;", + "uniform sampler2D terrainLayerNormalMap2;", + "uniform sampler2D terrainLayerNormalMap3;", + "uniform float terrainHasNormalMaps;", + "uniform float terrainNormalMapStrength;", + "uniform float terrainTextureSize;", + "uniform float terrainHeightBlending;", + "uniform float terrainTextureVariation;", + "uniform vec4 terrainCliffLayer;", + "uniform vec2 terrainCliffCosines;", + "uniform vec4 terrainBrush;", + "uniform sampler2D terrainColorMap;", + "uniform vec3 terrainWetShore;", + "varying vec2 vTerrainLocalXY;", + "varying float vTerrainLocalZ;", + "varying vec2 vTerrainWorldXY;", + "varying float vTerrainUp;", + "", + "float terrainHash(vec2 p) {", + " return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);", + "}", + "// The rotation of the copies of textures used to make repetition less visible.", + "const mat2 terrainCopyRotation = mat2(0.8, -0.6, 0.6, 0.8);", + "/** A bump of a normal map (from -1 to 1), mixed with its rotated copy in patches. */", + "vec3 terrainSampleNormal(sampler2D normalMap, vec2 uv, vec2 uv2, float patches) {", + " vec3 bump = texture2D(normalMap, uv).xyz * 2.0 - 1.0;", + " if (patches <= 0.0) return bump;", + " vec3 copyBump = texture2D(normalMap, uv2).xyz * 2.0 - 1.0;", + " // The bumps of the copy are rotated back with the inverse rotation.", + " copyBump.xy = transpose(terrainCopyRotation) * copyBump.xy;", + " return mix(bump, copyBump, patches);", + "}", + "float terrainNoise(vec2 p) {", + " vec2 i = floor(p);", + " vec2 f = fract(p);", + " vec2 u = f * f * (3.0 - 2.0 * f);", + " return mix(", + " mix(terrainHash(i), terrainHash(i + vec2(1.0, 0.0)), u.x),", + " mix(terrainHash(i + vec2(0.0, 1.0)), terrainHash(i + vec2(1.0, 1.0)), u.x),", + " u.y", + " );", + "}", + "`;", + "// Holes are samples without any painted layer: the interpolated sum of the", + "// weights gives smooth edges. Only compiled for terrains having holes, as", + "// discard makes drawing slower on some GPUs.", + "const holeShaderCode = `", + "#ifdef TERRAIN_HOLES", + "if (dot(texture2D(terrainSplatMap, vTerrainLocalXY * terrainSplatUvScaleAndOffset.x + terrainSplatUvScaleAndOffset.y), vec4(1.0)) < 0.5) discard;", + "#endif", + "`;", + "// For the shadows: the depth of holes is not drawn.", + "const holeDepthShaderDeclarations = `", + "uniform sampler2D terrainSplatMap;", + "uniform vec2 terrainSplatUvScaleAndOffset;", + "varying vec2 vTerrainLocalXY;", + "`;", + "", + "const fragmentShaderCode = holeShaderCode + `", + "// Borders between layers are made irregular and sharper, to look painted.", + "vec2 terrainNoisePosition = vTerrainWorldXY / 24.0;", + "vec4 terrainJitter = vec4(", + " terrainNoise(terrainNoisePosition),", + " terrainNoise(terrainNoisePosition + 19.1),", + " terrainNoise(terrainNoisePosition + 37.7),", + " terrainNoise(terrainNoisePosition + 53.3)", + ");", + "// Layer weights are in a texture, so that painted details stay the same", + "// whatever the level of detail of the ground.", + "vec2 terrainSplatUv = vTerrainLocalXY * terrainSplatUvScaleAndOffset.x + terrainSplatUvScaleAndOffset.y;", + "vec4 terrainWeights = texture2D(terrainSplatMap, terrainSplatUv) * (0.7 + 0.6 * terrainJitter);", + "terrainWeights = terrainWeights * terrainWeights;", + "terrainWeights /= max(dot(terrainWeights, vec4(1.0)), 0.00001);", + "float terrainCliff = 1.0 - smoothstep(terrainCliffCosines.x, terrainCliffCosines.y, vTerrainUp);", + "terrainWeights = mix(terrainWeights, terrainCliffLayer, terrainCliff * dot(terrainCliffLayer, vec4(1.0)));", + "", + "vec2 terrainUv = vTerrainWorldXY / terrainTextureSize;", + "vec3 terrainTexture0 = texture2D(terrainLayerMap0, terrainUv).rgb;", + "vec3 terrainTexture1 = texture2D(terrainLayerMap1, terrainUv).rgb;", + "vec3 terrainTexture2 = texture2D(terrainLayerMap2, terrainUv).rgb;", + "vec3 terrainTexture3 = texture2D(terrainLayerMap3, terrainUv).rgb;", + "", + "// Less visible repetition: in irregular patches, textures are replaced by a", + "// rotated and shifted copy of themselves, and colors slowly vary.", + "float terrainPatches = 0.0;", + "vec2 terrainUv2 = terrainUv;", + "float terrainColorVariation = 1.0;", + "if (terrainTextureVariation > 0.0) {", + " // Up to half of the ground is covered by the copies, each patch showing", + " // only one of them (mixing them would make them blurry).", + " float terrainPatchThreshold = 1.0 - 0.5 * terrainTextureVariation;", + " terrainPatches = smoothstep(terrainPatchThreshold - 0.05, terrainPatchThreshold + 0.05, terrainNoise(terrainUv * 0.23 + 7.3));", + " terrainUv2 = terrainCopyRotation * terrainUv + vec2(0.31, 0.73);", + " terrainTexture0 = mix(terrainTexture0, texture2D(terrainLayerMap0, terrainUv2).rgb, terrainPatches);", + " terrainTexture1 = mix(terrainTexture1, texture2D(terrainLayerMap1, terrainUv2).rgb, terrainPatches);", + " terrainTexture2 = mix(terrainTexture2, texture2D(terrainLayerMap2, terrainUv2).rgb, terrainPatches);", + " terrainTexture3 = mix(terrainTexture3, texture2D(terrainLayerMap3, terrainUv2).rgb, terrainPatches);", + " terrainColorVariation += terrainTextureVariation * 0.3 *", + " (terrainNoise(terrainUv * 0.11) + 0.5 * terrainNoise(terrainUv * 0.37 + 3.1) - 0.75);", + "}", + "", + "// Height-based blending: the brightness of a texture, compared to its", + "// average, is used as its height. Where layers meet, the high parts (stones,", + "// tufts) cover the low parts (sand, gaps) instead of fading into each other.", + "if (terrainHeightBlending > 0.0) {", + " const vec3 terrainLuminance = vec3(0.299, 0.587, 0.114);", + " vec4 terrainHeights = vec4(", + " dot(terrainTexture0 - textureLod(terrainLayerMap0, vec2(0.5), 16.0).rgb, terrainLuminance),", + " dot(terrainTexture1 - textureLod(terrainLayerMap1, vec2(0.5), 16.0).rgb, terrainLuminance),", + " dot(terrainTexture2 - textureLod(terrainLayerMap2, vec2(0.5), 16.0).rgb, terrainLuminance),", + " dot(terrainTexture3 - textureLod(terrainLayerMap3, vec2(0.5), 16.0).rgb, terrainLuminance)", + " );", + " vec4 terrainHeightWeights = terrainWeights + terrainHeights;", + " float terrainTop = max(max(terrainHeightWeights.x, terrainHeightWeights.y), max(terrainHeightWeights.z, terrainHeightWeights.w));", + " // Only painted layers (and the cliff layer) can show.", + " vec4 terrainHeightBlend = max(terrainHeightWeights - terrainTop + 0.1, 0.0) * step(0.004, terrainWeights);", + " terrainHeightBlend /= max(dot(terrainHeightBlend, vec4(1.0)), 0.00001);", + " terrainWeights = mix(terrainWeights, terrainHeightBlend, terrainHeightBlending);", + "}", + "", + "vec3 terrainColor = terrainColorVariation * (", + " terrainWeights.x * terrainLayerColors[0] * terrainTexture0 +", + " terrainWeights.y * terrainLayerColors[1] * terrainTexture1 +", + " terrainWeights.z * terrainLayerColors[2] * terrainTexture2 +", + " terrainWeights.w * terrainLayerColors[3] * terrainTexture3);", + "", + "// The painted tint multiplies all layers. Wet ground is darker (and", + "// shinier, see the roughness).", + "vec4 terrainTintAndWetness = texture2D(terrainColorMap, terrainSplatUv);", + "float terrainWetness = max(", + " terrainTintAndWetness.a,", + " terrainWetShore.z * (1.0 - smoothstep(terrainWetShore.x, terrainWetShore.x + terrainWetShore.y, vTerrainLocalZ))", + ");", + "terrainColor *= terrainTintAndWetness.rgb * (1.0 - 0.4 * terrainWetness);", + "diffuseColor.rgb *= terrainColor;", + "", + "if (terrainBrush.w > 0.5) {", + " float brushDistance = distance(vTerrainWorldXY, terrainBrush.xy);", + " float ringWidth = max(terrainBrush.z * 0.04, 1.5);", + " float ring = 1.0 - smoothstep(0.0, ringWidth, abs(brushDistance - terrainBrush.z));", + " float disc = brushDistance < terrainBrush.z ? 0.12 : 0.0;", + " diffuseColor.rgb = mix(diffuseColor.rgb, vec3(1.0), max(ring * 0.85, disc));", + "}", + "`;", + "", + "const fragmentShaderRoughnessCode = `", + "roughnessFactor = mix(roughnessFactor, 0.3, terrainWetness);", + "`;", + "", + "const fragmentShaderNormalCode = `", + "if (terrainHasNormalMaps > 0.5) {", + " vec3 terrainMapNormal =", + " terrainWeights.x * terrainSampleNormal(terrainLayerNormalMap0, terrainUv, terrainUv2, terrainPatches) +", + " terrainWeights.y * terrainSampleNormal(terrainLayerNormalMap1, terrainUv, terrainUv2, terrainPatches) +", + " terrainWeights.z * terrainSampleNormal(terrainLayerNormalMap2, terrainUv, terrainUv2, terrainPatches) +", + " terrainWeights.w * terrainSampleNormal(terrainLayerNormalMap3, terrainUv, terrainUv2, terrainPatches);", + " terrainMapNormal.xy *= terrainNormalMapStrength;", + " // The X and Y of textures follow the X and Y of the 3D world, projected on", + " // the ground (the camera space of the normal is used).", + " vec3 terrainTangent = (viewMatrix * vec4(1.0, 0.0, 0.0, 0.0)).xyz;", + " terrainTangent -= normal * dot(normal, terrainTangent);", + " // A ground facing the X axis (a rotated terrain) uses the Y axis instead.", + " if (dot(terrainTangent, terrainTangent) < 0.000001) {", + " terrainTangent = (viewMatrix * vec4(0.0, 1.0, 0.0, 0.0)).xyz;", + " terrainTangent -= normal * dot(normal, terrainTangent);", + " }", + " terrainTangent = normalize(terrainTangent);", + " vec3 terrainBitangent = cross(normal, terrainTangent);", + " normal = normalize(mat3(terrainTangent, terrainBitangent, normal) * terrainMapNormal);", + "}", + "`;", + "", + "let whiteTexture = null;", + "const getWhiteTexture = () => {", + " if (!whiteTexture) {", + " whiteTexture = new THREE.DataTexture(new Uint8Array([255, 255, 255, 255]), 1, 1);", + " whiteTexture.needsUpdate = true;", + " }", + " return whiteTexture;", + "};", + "", + "let flatNormalTexture = null;", + "const getFlatNormalTexture = () => {", + " if (!flatNormalTexture) {", + " flatNormalTexture = new THREE.DataTexture(new Uint8Array([128, 128, 255, 255]), 1, 1);", + " flatNormalTexture.needsUpdate = true;", + " }", + " return flatNormalTexture;", + "};", + "", + "const layerTextures = new WeakMap();", + "const layerNormalMaps = new WeakMap();", + "/**", + " * Textures of the image manager have no mipmaps: they would shimmer when", + " * repeated in the distance.", + " * @param {gdjs.RuntimeGame} game", + " * @param {string} resourceName", + " * @param {boolean} isNormalMap Normal maps are directions, not colors.", + " */", + "const getLayerTexture = (game, resourceName, isNormalMap) => {", + " if (!resourceName) return isNormalMap ? getFlatNormalTexture() : getWhiteTexture();", + " const imageTexture = game.getImageManager().getThreeTexture(resourceName);", + " const textures = isNormalMap ? layerNormalMaps : layerTextures;", + " let texture = textures.get(imageTexture);", + " if (!texture) {", + " texture = imageTexture.clone();", + " if (isNormalMap) texture.colorSpace = THREE.NoColorSpace;", + " texture.wrapS = THREE.RepeatWrapping;", + " texture.wrapT = THREE.RepeatWrapping;", + " texture.minFilter = THREE.LinearMipmapLinearFilter;", + " texture.generateMipmaps = true;", + " texture.anisotropy = 4;", + " texture.needsUpdate = true;", + " textures.set(imageTexture, texture);", + " }", + " return texture;", + "};", + "", + "/**", + " * A texture of 4 bytes per sample of a grid of `size * size` samples (layer", + " * weights, tint and wetness...), interpolated between samples.", + " * @param {Uint8Array} bytes", + " * @param {number} size", + " */", + "const createGridTexture = (bytes, size) => {", + " const texture = new THREE.DataTexture(bytes, size, size);", + " texture.magFilter = THREE.LinearFilter;", + " texture.minFilter = THREE.LinearFilter;", + " texture.userData.isUploaded = false;", + " texture.onUpdate = () => {", + " texture.userData.isUploaded = true;", + " };", + " return texture;", + "};", + "", + "/**", + " * Send the rows of the changed samples to the GPU, or the whole texture", + " * before its first upload (and in older GDevelop versions).", + " * @param {THREE.DataTexture} texture", + " * @param {SampleRectangle} samples", + " */", + "const uploadGridTextureRows = (texture, samples) => {", + " if (texture.userData.isUploaded && typeof texture.addUpdateRange === 'function') {", + " const size = texture.image.width;", + " for (let j = samples.minJ; j <= samples.maxJ; j++) {", + " texture.addUpdateRange((j * size + samples.minI) * 4, (samples.maxI - samples.minI + 1) * 4);", + " }", + " }", + " texture.needsUpdate = true;", + "};", + "", + "class TerrainRenderer {", + " /** @param {Terrain} terrain */", + " constructor(terrain) {", + " this.terrain = terrain;", + " this.group = new THREE.Group();", + " terrain.object.get3DRendererObject().add(this.group);", + " /** @type {Array<{mesh: THREE.Mesh, geometry: THREE.BufferGeometry, firstI: number, firstJ: number, center: THREE.Vector3, lod: number}>} */", + " this.chunks = [];", + " this.chunksPerSide = 0;", + " this.chunkCells = 0;", + " this.chunkSideVertices = 0;", + " this.chunkGridVertices = 0;", + " this.chunkVertices = 0;", + " /** @type {THREE.DataTexture | null} */", + " this.splatTexture = null;", + " /** @type {THREE.DataTexture | null} */", + " this.colorTexture = null;", + " this.uniforms = {", + " terrainSplatMap: { value: getWhiteTexture() },", + " terrainSplatUvScaleAndOffset: { value: new THREE.Vector2() },", + " terrainLayerColors: { value: [new THREE.Color(), new THREE.Color(), new THREE.Color(), new THREE.Color()] },", + " terrainLayerMap0: { value: getWhiteTexture() },", + " terrainLayerMap1: { value: getWhiteTexture() },", + " terrainLayerMap2: { value: getWhiteTexture() },", + " terrainLayerMap3: { value: getWhiteTexture() },", + " terrainLayerNormalMap0: { value: getFlatNormalTexture() },", + " terrainLayerNormalMap1: { value: getFlatNormalTexture() },", + " terrainLayerNormalMap2: { value: getFlatNormalTexture() },", + " terrainLayerNormalMap3: { value: getFlatNormalTexture() },", + " terrainHasNormalMaps: { value: 0 },", + " terrainNormalMapStrength: { value: 1 },", + " terrainTextureSize: { value: 256 },", + " terrainHeightBlending: { value: 0 },", + " terrainTextureVariation: { value: 0 },", + " terrainCliffLayer: { value: new THREE.Vector4() },", + " terrainCliffCosines: { value: new THREE.Vector2() },", + " terrainBrush: { value: new THREE.Vector4() },", + " terrainColorMap: { value: getWhiteTexture() },", + " // Z (in the terrain group) under which the ground is wet, fade height, and 1 if it's used.", + " terrainWetShore: { value: new THREE.Vector3() },", + " // Scene units by 3D world unit.", + " terrainWorldScale: { value: 1 },", + " };", + " this.material = new THREE.MeshStandardMaterial({ roughness: 1, metalness: 0 });", + " this.material.onBeforeCompile = (shader) => {", + " Object.assign(shader.uniforms, this.uniforms);", + " shader.vertexShader = patchShaderCode(shader.vertexShader, [", + " ['#include ', '#include \\n' + vertexShaderDeclarations],", + " ['#include ', '#include \\n' + vertexShaderCode],", + " ], 'Terrain3D');", + " shader.fragmentShader = patchShaderCode(shader.fragmentShader, [", + " ['#include ', '#include \\n' + fragmentShaderDeclarations],", + " ['#include ', fragmentShaderCode],", + " ['#include ', '#include \\n' + fragmentShaderRoughnessCode],", + " ['#include ', '#include \\n' + fragmentShaderNormalCode],", + " ], 'Terrain3D');", + " };", + " this.material.customProgramCacheKey = () => (this.hasHoles ? 'Terrain3DWithHoles' : 'Terrain3D');", + " this.hasHoles = false;", + " // Without them, holes would cast shadows.", + " this.depthMaterial = this._createHoleDepthMaterial(new THREE.MeshDepthMaterial());", + " this.distanceMaterial = this._createHoleDepthMaterial(new THREE.MeshDistanceMaterial());", + " this._cameraPosition = new THREE.Vector3();", + " this._chunkPosition = new THREE.Vector3();", + " this._scale = new THREE.Vector3();", + " }", + "", + " /**", + " * @param {THREE.MeshDepthMaterial | THREE.MeshDistanceMaterial} material", + " * @returns The material, drawing nothing in holes.", + " */", + " _createHoleDepthMaterial(material) {", + " material.defines = { TERRAIN_HOLES: '' };", + " material.onBeforeCompile = (shader) => {", + " Object.assign(shader.uniforms, this.uniforms);", + " shader.vertexShader = patchShaderCode(shader.vertexShader, [", + " ['#include ', '#include \\nvarying vec2 vTerrainLocalXY;'],", + " ['#include ', '#include \\nvTerrainLocalXY = position.xy;'],", + " ], 'Terrain3D shadows');", + " shader.fragmentShader = patchShaderCode(shader.fragmentShader, [", + " ['#include ', '#include \\n' + holeDepthShaderDeclarations],", + " ['#include ', holeShaderCode + '#include '],", + " ], 'Terrain3D shadows');", + " };", + " material.customProgramCacheKey = () => 'Terrain3DHoles';", + " return material;", + " }", + "", + " /**", + " * Use the shaders drawing holes, or the faster ones without them.", + " * @param {boolean} hasHoles", + " */", + " setHasHoles(hasHoles) {", + " if (hasHoles === this.hasHoles) return;", + " this.hasHoles = hasHoles;", + " // The material has its own defines (STANDARD).", + " if (hasHoles) this.material.defines.TERRAIN_HOLES = '';", + " else delete this.material.defines.TERRAIN_HOLES;", + " this.material.needsUpdate = true;", + " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", + " }", + "", + " /** @param {THREE.Mesh} mesh */", + " _setChunkShadowMaterials(mesh) {", + " mesh.customDepthMaterial = this.hasHoles ? this.depthMaterial : undefined;", + " mesh.customDistanceMaterial = this.hasHoles ? this.distanceMaterial : undefined;", + " }", + "", + " /** Recreate all the chunks, for example after the resolution changed. */", + " rebuild() {", + " this.disposeChunks();", + " const { data } = this.terrain;", + " this.chunkCells = getChunkCells(data.resolution);", + " this.chunkSideVertices = this.chunkCells + 1;", + " this.chunkGridVertices = this.chunkSideVertices * this.chunkSideVertices;", + " this.chunkVertices = this.chunkGridVertices + 4 * this.chunkSideVertices;", + " this.chunksPerSide = data.resolution / this.chunkCells;", + " const cellSize = AREA_SIZE / data.resolution;", + " // Sample centers are at the texel centers.", + " this.splatTexture = createGridTexture(data.splat, data.size);", + " this.uniforms.terrainSplatMap.value = this.splatTexture;", + " this.colorTexture = createGridTexture(data.colors, data.size);", + " // Tints are colors chosen like the layer colors.", + " this.colorTexture.colorSpace = THREE.SRGBColorSpace;", + " this.uniforms.terrainColorMap.value = this.colorTexture;", + " this.uniforms.terrainSplatUvScaleAndOffset.value.set(1 / (cellSize * data.size), 0.5 / data.size);", + " for (let chunkJ = 0; chunkJ < this.chunksPerSide; chunkJ++) {", + " for (let chunkI = 0; chunkI < this.chunksPerSide; chunkI++) {", + " const firstI = chunkI * this.chunkCells;", + " const firstJ = chunkJ * this.chunkCells;", + " const positions = new Float32Array(this.chunkVertices * 3);", + " for (let j = 0; j < this.chunkSideVertices; j++) {", + " for (let i = 0; i < this.chunkSideVertices; i++) {", + " const vertex = j * this.chunkSideVertices + i;", + " positions[vertex * 3] = (firstI + i) * cellSize;", + " positions[vertex * 3 + 1] = (firstJ + j) * cellSize;", + " }", + " }", + " // Skirt vertices are below border vertices.", + " this._forEachSkirtVertex((skirtVertex, borderVertex) => {", + " positions[skirtVertex * 3] = positions[borderVertex * 3];", + " positions[skirtVertex * 3 + 1] = positions[borderVertex * 3 + 1];", + " });", + " const geometry = new THREE.BufferGeometry();", + " geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));", + " geometry.setAttribute('normal', new THREE.BufferAttribute(new Float32Array(this.chunkVertices * 3), 3));", + " geometry.setIndex(getLodIndex(this.chunkCells, LOD_STEPS[0]));", + " geometry.boundingBox = new THREE.Box3();", + " geometry.boundingSphere = new THREE.Sphere();", + " const mesh = new THREE.Mesh(geometry, this.material);", + " mesh.matrixAutoUpdate = false;", + " // Rays hit the ground at its full resolution, whatever the level of", + " // detail drawn, and go through holes.", + " const chunkCells = this.chunkCells;", + " const terrainData = () => this.terrain.data;", + " const localPoint = new THREE.Vector3();", + " mesh.raycast = function (raycaster, intersects) {", + " const drawnIndex = geometry.index;", + " const firstIntersection = intersects.length;", + " geometry.setIndex(getLodIndex(chunkCells, LOD_STEPS[0]));", + " THREE.Mesh.prototype.raycast.call(this, raycaster, intersects);", + " geometry.setIndex(drawnIndex);", + " const data = terrainData();", + " const cellSize = AREA_SIZE / data.resolution;", + " for (let index = intersects.length - 1; index >= firstIntersection; index--) {", + " this.worldToLocal(localPoint.copy(intersects[index].point));", + " if (data.sampleSolidity(localPoint.x / cellSize, localPoint.y / cellSize) < 0.5) {", + " intersects.splice(index, 1);", + " }", + " }", + " };", + " this._setChunkShadowMaterials(mesh);", + " this.group.add(mesh);", + " this.chunks.push({", + " mesh,", + " geometry,", + " firstI,", + " firstJ,", + " center: new THREE.Vector3(", + " (firstI + this.chunkCells / 2) * cellSize,", + " (firstJ + this.chunkCells / 2) * cellSize,", + " 0", + " ),", + " lod: 0,", + " });", + " }", + " }", + " this.updateSamples(new SampleRectangle().set(0, 0, data.resolution, data.resolution), true);", + " this.updateMaterial();", + " }", + "", + " _forEachSkirtVertex(callback) {", + " for (let k = 0; k < this.chunkSideVertices; k++) {", + " callback(this.chunkGridVertices + k, k);", + " callback(this.chunkGridVertices + this.chunkSideVertices + k, this.chunkCells * this.chunkSideVertices + k);", + " callback(this.chunkGridVertices + 2 * this.chunkSideVertices + k, k * this.chunkSideVertices);", + " callback(", + " this.chunkGridVertices + 3 * this.chunkSideVertices + k,", + " k * this.chunkSideVertices + this.chunkCells", + " );", + " }", + " }", + "", + " /**", + " * Skirts go down only to the lowest point a coarser neighbor chunk can", + " * draw along the border, so that they can't be seen through holes.", + " * @param {Float32Array} positions", + " */", + " _updateSkirtHeights(positions) {", + " const coarsestStep = LOD_STEPS[LOD_STEPS.length - 1];", + " const borderVertices = [", + " (k) => k,", + " (k) => this.chunkCells * this.chunkSideVertices + k,", + " (k) => k * this.chunkSideVertices,", + " (k) => k * this.chunkSideVertices + this.chunkCells,", + " ];", + " borderVertices.forEach((borderVertex, border) => {", + " for (let k = 0; k < this.chunkSideVertices; k++) {", + " let minZ = Infinity;", + " const from = Math.max(k - coarsestStep, 0);", + " const to = Math.min(k + coarsestStep, this.chunkCells);", + " for (let other = from; other <= to; other++) {", + " minZ = Math.min(minZ, positions[borderVertex(other) * 3 + 2]);", + " }", + " const skirtVertex = this.chunkGridVertices + border * this.chunkSideVertices + k;", + " positions[skirtVertex * 3 + 2] = minZ;", + " }", + " });", + " }", + "", + " /**", + " * Update the vertices of the chunks sharing the given samples.", + " * @param {SampleRectangle} samples", + " * @param {boolean} hasPaintChanged", + " */", + " updateSamples(samples, hasPaintChanged) {", + " if (samples.isEmpty()) return;", + " const { data } = this.terrain;", + " const cellSize = AREA_SIZE / data.resolution;", + " const slopeFactor = AREA_DEPTH / cellSize;", + " // Normals of samples next to changed ones change too.", + " const minI = Math.max(samples.minI - 1, 0);", + " const minJ = Math.max(samples.minJ - 1, 0);", + " const maxI = Math.min(samples.maxI + 1, data.resolution);", + " const maxJ = Math.min(samples.maxJ + 1, data.resolution);", + " const firstChunkI = Math.max(Math.ceil(minI / this.chunkCells) - 1, 0);", + " const firstChunkJ = Math.max(Math.ceil(minJ / this.chunkCells) - 1, 0);", + " const lastChunkI = Math.min(Math.floor(maxI / this.chunkCells), this.chunksPerSide - 1);", + " const lastChunkJ = Math.min(Math.floor(maxJ / this.chunkCells), this.chunksPerSide - 1);", + "", + " for (let chunkJ = firstChunkJ; chunkJ <= lastChunkJ; chunkJ++) {", + " for (let chunkI = firstChunkI; chunkI <= lastChunkI; chunkI++) {", + " const chunk = this.chunks[chunkJ * this.chunksPerSide + chunkI];", + " const positions = chunk.geometry.attributes.position.array;", + " const normals = chunk.geometry.attributes.normal.array;", + " const fromI = Math.max(minI - chunk.firstI, 0);", + " const toI = Math.min(maxI - chunk.firstI, this.chunkCells);", + " const fromJ = Math.max(minJ - chunk.firstJ, 0);", + " const toJ = Math.min(maxJ - chunk.firstJ, this.chunkCells);", + " for (let j = fromJ; j <= toJ; j++) {", + " const gridJ = chunk.firstJ + j;", + " for (let i = fromI; i <= toI; i++) {", + " const gridI = chunk.firstI + i;", + " const vertex = j * this.chunkSideVertices + i;", + " const sample = gridJ * data.size + gridI;", + " positions[vertex * 3 + 2] = data.heights[sample] * AREA_DEPTH;", + "", + " const leftI = Math.max(gridI - 1, 0);", + " const rightI = Math.min(gridI + 1, data.resolution);", + " const upJ = Math.max(gridJ - 1, 0);", + " const downJ = Math.min(gridJ + 1, data.resolution);", + " const nx =", + " ((data.getHeight(leftI, gridJ) - data.getHeight(rightI, gridJ)) * slopeFactor) /", + " (rightI - leftI);", + " const ny =", + " ((data.getHeight(gridI, upJ) - data.getHeight(gridI, downJ)) * slopeFactor) /", + " (downJ - upJ);", + " const inverseLength = 1 / Math.sqrt(nx * nx + ny * ny + 1);", + " normals[vertex * 3] = nx * inverseLength;", + " normals[vertex * 3 + 1] = ny * inverseLength;", + " normals[vertex * 3 + 2] = inverseLength;", + " }", + " }", + " this._forEachSkirtVertex((skirtVertex, borderVertex) => {", + " for (let axis = 0; axis < 3; axis++) {", + " normals[skirtVertex * 3 + axis] = normals[borderVertex * 3 + axis];", + " }", + " });", + " this._updateSkirtHeights(positions);", + " let maxZ = 0;", + " for (let vertex = 0; vertex < this.chunkGridVertices; vertex++) {", + " maxZ = Math.max(maxZ, positions[vertex * 3 + 2]);", + " }", + " const chunkSize = this.chunkCells * cellSize;", + " chunk.geometry.boundingBox.min.set(chunk.firstI * cellSize, chunk.firstJ * cellSize, 0);", + " chunk.geometry.boundingBox.max.set(", + " chunk.firstI * cellSize + chunkSize,", + " chunk.firstJ * cellSize + chunkSize,", + " maxZ", + " );", + " chunk.geometry.boundingBox.getBoundingSphere(chunk.geometry.boundingSphere);", + " chunk.center.z = maxZ / 2;", + " chunk.geometry.attributes.position.needsUpdate = true;", + " chunk.geometry.attributes.normal.needsUpdate = true;", + " }", + " }", + " if (hasPaintChanged) {", + " // The data may have been replaced by one with the same resolution.", + " this.splatTexture.image.data = data.splat;", + " uploadGridTextureRows(this.splatTexture, samples);", + " this.colorTexture.image.data = data.colors;", + " uploadGridTextureRows(this.colorTexture, samples);", + " }", + " }", + "", + " updateMaterial() {", + " const { object } = this.terrain;", + " const game = object.getRuntimeScene().getGame();", + " const colors = [object._getLayer1Color(), object._getLayer2Color(), object._getLayer3Color(), object._getLayer4Color()];", + " const textures = [", + " object._getLayer1Texture(),", + " object._getLayer2Texture(),", + " object._getLayer3Texture(),", + " object._getLayer4Texture(),", + " ];", + " const normalMaps = [", + " object._getLayer1NormalMap(),", + " object._getLayer2NormalMap(),", + " object._getLayer3NormalMap(),", + " object._getLayer4NormalMap(),", + " ];", + " for (let layer = 0; layer < LAYER_COUNT; layer++) {", + " this.uniforms.terrainLayerColors.value[layer].set(gdjs.rgbOrHexStringToNumber(colors[layer]));", + " this.uniforms['terrainLayerMap' + layer].value = getLayerTexture(game, textures[layer], false);", + " this.uniforms['terrainLayerNormalMap' + layer].value = getLayerTexture(game, normalMaps[layer], true);", + " }", + " this.uniforms.terrainHasNormalMaps.value = normalMaps.some((normalMap) => !!normalMap) ? 1 : 0;", + " this.uniforms.terrainNormalMapStrength.value = Math.max(object._getNormalMapStrength(), 0);", + " this.uniforms.terrainTextureSize.value = Math.max(object._getTextureSize(), 1);", + " this.uniforms.terrainHeightBlending.value = clamp01(object._getHeightBlending());", + " this.uniforms.terrainTextureVariation.value = clamp01(object._getTextureVariation());", + "", + " const cliffLayer = parseInt(object._getCliffLayer(), 10);", + " const cliffAngle = gdjs.toRad(object._getCliffAngle());", + " const cliffBlendAngle = gdjs.toRad(4);", + " this.uniforms.terrainCliffLayer.value.set(", + " cliffLayer === 1 ? 1 : 0,", + " cliffLayer === 2 ? 1 : 0,", + " cliffLayer === 3 ? 1 : 0,", + " cliffLayer === 4 ? 1 : 0", + " );", + " this.uniforms.terrainCliffCosines.value.set(", + " Math.cos(cliffAngle + cliffBlendAngle),", + " Math.cos(cliffAngle - cliffBlendAngle)", + " );", + "", + " const castShadow = object._getCastShadow();", + " const receiveShadow = object._getReceiveShadow();", + " for (const chunk of this.chunks) {", + " chunk.mesh.castShadow = castShadow;", + " chunk.mesh.receiveShadow = receiveShadow;", + " }", + " }", + "", + " /**", + " * Show the brush circle of the editor.", + " * @param {THREE.Vector3 | null} localPosition", + " * @param {number} radius In scene units.", + " */", + " setBrush(localPosition, radius) {", + " const brush = this.uniforms.terrainBrush.value;", + " if (!localPosition) {", + " brush.w = 0;", + " return;", + " }", + " this.group.updateWorldMatrix(true, false);", + " const worldPosition = this._chunkPosition.copy(localPosition).applyMatrix4(this.group.matrixWorld);", + " const worldScale = this.uniforms.terrainWorldScale.value;", + " brush.set(worldPosition.x * worldScale, worldPosition.y * worldScale, radius, 1);", + " }", + "", + " /** @param {THREE.Camera | null} camera */", + " updateLevelsOfDetail(camera) {", + " if (!camera || !this.group.visible) return;", + " const matrixWorld = this.group.matrixWorld;", + " this._cameraPosition.setFromMatrixPosition(camera.matrixWorld);", + " this._scale.setFromMatrixScale(matrixWorld);", + " const cellSize = AREA_SIZE / this.terrain.data.resolution;", + " const chunkWorldSize = this.chunkCells * cellSize * Math.max(this._scale.x, this._scale.y);", + " for (const chunk of this.chunks) {", + " const distance =", + " this._chunkPosition.copy(chunk.center).applyMatrix4(matrixWorld).distanceTo(this._cameraPosition) /", + " chunkWorldSize;", + " let lod = LOD_DISTANCES.length - 1;", + " while (lod > 0 && distance < LOD_DISTANCES[lod]) lod--;", + " if (lod !== chunk.lod) {", + " chunk.lod = lod;", + " chunk.geometry.setIndex(getLodIndex(this.chunkCells, LOD_STEPS[lod]));", + " }", + " }", + " }", + "", + " disposeChunks() {", + " for (const chunk of this.chunks) {", + " chunk.mesh.removeFromParent();", + " // The index is shared by all chunks: it must stay on the GPU.", + " chunk.geometry.setIndex(null);", + " chunk.geometry.dispose();", + " }", + " this.chunks.length = 0;", + " if (this.splatTexture) this.splatTexture.dispose();", + " this.splatTexture = null;", + " if (this.colorTexture) this.colorTexture.dispose();", + " this.colorTexture = null;", + " }", + "", + " dispose() {", + " this.disposeChunks();", + " this.material.dispose();", + " this.depthMaterial.dispose();", + " this.distanceMaterial.dispose();", + " this.group.removeFromParent();", + " }", + "}", + "", + "/**", + " * Makes a Physics3D behavior use a height field shape following the terrain.", + " * Changes of heights are applied to the shape without recreating it.", + " * @implements {gdjs.Physics3DRuntimeBehavior.BodyUpdater}", + " */", + "class TerrainBodyUpdater {", + " /**", + " * @param {Terrain} terrain", + " * @param {gdjs.Physics3DRuntimeBehavior} physics", + " */", + " constructor(terrain, physics) {", + " this.terrain = terrain;", + " this.physics = physics;", + " this.defaultBodyUpdater = physics.bodyUpdater;", + " this.changedSamples = new SampleRectangle();", + " /** @type {Jolt.HeightFieldShape | null} */", + " this.heightField = null;", + " this.heightScale = 1;", + " this.heightsBufferPointer = 0;", + " this.heightsBufferLength = 0;", + " this.bodyX = 0;", + " this.bodyY = 0;", + " this.bodyZ = 0;", + " this.bodyHeight = 0;", + " physics.bodyType = 'Static';", + " physics.bodyUpdater = this;", + " }", + "", + " createShape() {", + " const { data, object } = this.terrain;", + " const worldInvScale = this.physics._sharedData.worldInvScale;", + " const { size } = data;", + " this.heightScale = object.getDepth() * worldInvScale;", + "", + " // Changes of heights (SetHeights) break collisions unless the sample", + " // count is a power of 2: extra samples have no collision.", + " const sampleCount = Math.pow(2, Math.ceil(Math.log2(size)));", + " const settings = new Jolt.HeightFieldShapeSettings();", + " settings.mScale.Set(", + " (object.getWidth() / data.resolution) * worldInvScale,", + " 1,", + " (object.getHeight() / data.resolution) * worldInvScale", + " );", + " settings.mSampleCount = sampleCount;", + " settings.mBlockSize = 4;", + " // Allows later changes of heights on the whole depth of the object.", + " settings.mMinHeightValue = 0;", + " settings.mMaxHeightValue = this.heightScale;", + " settings.mHeightSamples.resize(sampleCount * sampleCount);", + " const samples = new Float32Array(", + " Jolt.HEAPF32.buffer,", + " Jolt.getPointer(settings.mHeightSamples.data()),", + " sampleCount * sampleCount", + " );", + " const noCollision = Jolt.HeightFieldShapeConstantValues.prototype.cNoCollisionValue;", + " samples.fill(noCollision);", + " // Height fields go along X and Z: rows are flipped so that, once", + " // rotated to have heights along Z, rows go along Y.", + " for (let j = 0; j < size; j++) {", + " const row = (size - 1 - j) * sampleCount;", + " for (let i = 0; i < size; i++) {", + " samples[row + i] = this._getSampleHeight(j * size + i, noCollision);", + " }", + " }", + " const shape = settings.Create().Get();", + " Jolt.destroy(settings);", + " this.heightField = Jolt.castObject(shape, Jolt.HeightFieldShape);", + " this.changedSamples.clear();", + " return shape;", + " }", + "", + " /** @returns {number} The height of a sample in the shape, or no collision in a hole. */", + " _getSampleHeight(index, noCollision) {", + " const height = this.terrain.surfaceHeights[index];", + " return Number.isNaN(height) ? noCollision : height * this.heightScale;", + " }", + "", + " _getBodyPosition() {", + " const { object } = this.terrain;", + " const worldInvScale = this.physics._sharedData.worldInvScale;", + " this.bodyX = object.getX();", + " this.bodyY = object.getY();", + " this.bodyZ = object.getZ();", + " // The origin of the height field is at the bottom of the terrain (see createShape).", + " this.bodyHeight = object.getHeight();", + " return this.physics._sharedData.getRVec3(", + " object.getX() * worldInvScale,", + " (object.getY() + object.getHeight()) * worldInvScale,", + " object.getZ() * worldInvScale", + " );", + " }", + "", + " _getBodyRotation() {", + " // A quarter turn around X: heights go along Z.", + " return this.physics._sharedData.getQuat(Math.SQRT1_2, 0, 0, Math.SQRT1_2);", + " }", + "", + " createAndAddBody() {", + " const { physics } = this;", + " const { bodyInterface } = physics._sharedData;", + " const shape = this.createShape();", + " const settings = new Jolt.BodyCreationSettings(", + " shape,", + " this._getBodyPosition(),", + " this._getBodyRotation(),", + " Jolt.EMotionType_Static,", + " physics.getBodyLayer()", + " );", + " settings.mFriction = physics.friction;", + " settings.mRestitution = physics.restitution;", + " const body = bodyInterface.CreateBody(settings);", + " Jolt.destroy(settings);", + " bodyInterface.AddBody(body.GetID(), Jolt.EActivation_DontActivate);", + " return body;", + " }", + "", + " updateObjectFromBody() {", + " // The terrain is static: the body never moves it.", + " }", + "", + " updateBodyFromObject() {", + " const { physics } = this;", + " const body = physics._body;", + " if (!body) return;", + " const { object } = this.terrain;", + " const { bodyInterface } = physics._sharedData;", + " if (", + " object.getX() !== this.bodyX ||", + " object.getY() !== this.bodyY ||", + " object.getZ() !== this.bodyZ ||", + " object.getHeight() !== this.bodyHeight", + " ) {", + " bodyInterface.SetPositionAndRotation(", + " body.GetID(),", + " this._getBodyPosition(),", + " this._getBodyRotation(),", + " Jolt.EActivation_DontActivate", + " );", + " }", + " if (!this.changedSamples.isEmpty()) {", + " this._applyChangedHeights(body);", + " }", + " }", + "", + " _applyChangedHeights(body) {", + " const { data } = this.terrain;", + " const { size } = data;", + " const { minI, minJ, maxI, maxJ } = this.changedSamples;", + " // Jolt only changes blocks of samples. Rows are flipped and extra", + " // samples have no collision (see createShape).", + " const blockSize = this.heightField.GetBlockSize();", + " const sampleCount = this.heightField.GetSampleCount();", + " const alignDown = (value) => Math.floor(value / blockSize) * blockSize;", + " const alignUp = (value) => Math.min(Math.ceil(value / blockSize) * blockSize, sampleCount);", + " const firstX = alignDown(minI);", + " const firstRow = alignDown(size - 1 - maxJ);", + " const width = alignUp(maxI + 1) - firstX;", + " const height = alignUp(size - minJ) - firstRow;", + " if (width * height > this.heightsBufferLength) {", + " if (this.heightsBufferPointer) Jolt._webidl_free(this.heightsBufferPointer);", + " this.heightsBufferLength = width * height;", + " this.heightsBufferPointer = Jolt._webidl_malloc(this.heightsBufferLength * 4);", + " }", + " const heights = new Float32Array(Jolt.HEAPF32.buffer, this.heightsBufferPointer, width * height);", + " const noCollision = Jolt.HeightFieldShapeConstantValues.prototype.cNoCollisionValue;", + " for (let row = 0; row < height; row++) {", + " const j = size - 1 - (firstRow + row);", + " for (let column = 0; column < width; column++) {", + " const i = firstX + column;", + " heights[row * width + column] =", + " i < size && j >= 0 ? this._getSampleHeight(j * size + i, noCollision) : noCollision;", + " }", + " }", + " const sharedData = this.physics._sharedData;", + " this.heightField.SetHeights(", + " firstX,", + " firstRow,", + " width,", + " height,", + " this.heightsBufferPointer,", + " width,", + " sharedData.jolt.GetTempAllocator()", + " );", + " sharedData.bodyInterface.NotifyShapeChanged(", + " body.GetID(),", + " sharedData.getVec3(0, 0, 0),", + " false,", + " Jolt.EActivation_DontActivate", + " );", + " this._wakeUpBodiesOnChangedSamples();", + " this.changedSamples.clear();", + " }", + "", + " /** Bodies sleeping on the changed area would otherwise float or sink. */", + " _wakeUpBodiesOnChangedSamples() {", + " const { object, data } = this.terrain;", + " const worldInvScale = this.physics._sharedData.worldInvScale;", + " const cellWidth = object.getWidth() / data.resolution;", + " const cellHeight = object.getHeight() / data.resolution;", + " const { minI, minJ, maxI, maxJ } = this.changedSamples;", + " const min = new Jolt.Vec3(", + " (object.getX() + (minI - 1) * cellWidth) * worldInvScale,", + " (object.getY() + (minJ - 1) * cellHeight) * worldInvScale,", + " (object.getZ() - 1) * worldInvScale", + " );", + " const max = new Jolt.Vec3(", + " (object.getX() + (maxI + 1) * cellWidth) * worldInvScale,", + " (object.getY() + (maxJ + 1) * cellHeight) * worldInvScale,", + " (object.getZ() + object.getDepth() + 1) * worldInvScale", + " );", + " const box = new Jolt.AABox(min, max);", + " const broadPhaseLayerFilter = new Jolt.BroadPhaseLayerFilter();", + " const objectLayerFilter = new Jolt.ObjectLayerFilter();", + " this.physics._sharedData.bodyInterface.ActivateBodiesInAABox(box, broadPhaseLayerFilter, objectLayerFilter);", + " Jolt.destroy(min);", + " Jolt.destroy(max);", + " Jolt.destroy(box);", + " Jolt.destroy(broadPhaseLayerFilter);", + " Jolt.destroy(objectLayerFilter);", + " }", + "", + " recreateShape() {", + " const body = this.physics._body;", + " if (!body) return;", + " this.physics._sharedData.bodyInterface.SetShape(", + " body.GetID(),", + " this.createShape(),", + " false,", + " Jolt.EActivation_DontActivate", + " );", + " const { resolution } = this.terrain.data;", + " this.changedSamples.set(0, 0, resolution, resolution);", + " this._wakeUpBodiesOnChangedSamples();", + " this.changedSamples.clear();", + " }", + "", + " destroyBody() {", + " this.defaultBodyUpdater.destroyBody();", + " this.heightField = null;", + " }", + "", + " dispose() {", + " if (this.heightsBufferPointer) Jolt._webidl_free(this.heightsBufferPointer);", + " this.heightsBufferPointer = 0;", + " this.heightsBufferLength = 0;", + " }", + "}", + "", + "/** @param {TerrainData} data */", + "const copyTerrainData = (data) => {", + " const copy = new TerrainData(data.resolution);", + " copy.copyFrom(data);", + " return copy;", + "};", + "", + "// Generating a relief or reading an image is long for big terrains, and done", + "// for every instance and every hot-reload: the results are kept.", + "const baseDataCache = new Map();", + "const MAX_CACHED_BASE_DATA = 8;", + "// Changes of the shape of a terrain kept for consumers of its surface that", + "// missed some versions: older changes update the whole surface.", + "const MAX_SURFACE_CHANGES = 64;", + "// Unique among all terrains: a terrain replaced by another one never has its version.", + "let lastGroundVersion = 0;", + "", + "/**", + " * The terrain of a Terrain3D object: its data, how it's drawn and its", + " * surface (for collisions and navigation meshes). Positions are in the scene.", + " */", + "class Terrain {", + " /** @param {gdjs.CustomRuntimeObject3D} object */", + " constructor(object) {", + " this.object = object;", + " /** @type {TerrainData} */", + " this.data = new TerrainData(DEFAULT_RESOLUTION);", + " /** The data before any sculpting or painting, from the relief or heightmap. */", + " this.baseData = this.data;", + " this.renderer = new TerrainRenderer(this);", + " /** @type {TerrainBodyUpdater[]} */", + " this.bodyUpdaters = [];", + " this.changedSamples = new SampleRectangle();", + " this.hasPaintChanged = false;", + " /** Increased each time the ground changes, for objects following it (like grass). */", + " this.version = 0;", + " /** Increased each time the shape of the ground changes (not its paint or position). */", + " this.surfaceVersion = 0;", + " /**", + " * The last changes of the shape of the ground, for `getChangedArea`.", + " * @type {Array<{version: number, samples: SampleRectangle}>}", + " */", + " this.surfaceChanges = [];", + " /** The changes since this version are all in `surfaceChanges`. */", + " this.surfaceChangesStartVersion = 0;", + " this._stroke = { cellWidth: 1, cellHeight: 1, ax: 0, ay: 0, bx: 0, by: 0, radius: 0 };", + " this._strokeChangedSamples = new SampleRectangle();", + " /** The properties the data was loaded from, to reload it only if they change. */", + " this.loadedFrom = '';", + " /** The heightmap image being loaded: the relief is used until it is. */", + " this.loadingHeightmapImage = '';", + " this.isDisposed = false;", + " /** The last value of the sculpt data property read or saved. */", + " this.sculptData = '';", + " /**", + " * Values saved by the editor tools: the editor sends them back with a", + " * hot-reload, possibly after newer ones were saved.", + " * @type {string[]}", + " */", + " this.savedSculptData = [];", + " /** The heights of the surface: the heights of the data, and NaN in holes. */", + " this.surfaceHeights = new Float32Array(0);", + " this.loadFromProperties();", + " this._transform = this._getTransform();", + " getGrounds(object.getRuntimeScene()).add(this);", + " /**", + " * The shape of the ground, for the physics engine and navigation meshes.", + " * @type {gdjs.Surface}", + " */", + " this.surface = {", + " getVersion: () => this.surfaceVersion,", + " getChangedArea: (sinceVersion) => this.getChangedArea(sinceVersion),", + " getTriangles: () => null,", + " getHeightField: () => ({ columns: this.data.size, rows: this.data.size, heights: this.surfaceHeights }),", + " };", + " if (typeof object.setSurface === 'function') {", + " object.setSurface(this.surface);", + " } else if (gdjs.Physics3DRuntimeBehavior) {", + " // Older GDevelop versions don't use surfaces.", + " for (const behavior of object._behaviors) {", + " if (behavior instanceof gdjs.Physics3DRuntimeBehavior) {", + " this.bodyUpdaters.push(new TerrainBodyUpdater(this, behavior));", + " }", + " }", + " }", + " }", + "", + " _getSourceProperties() {", + " const { object } = this;", + " return [", + " object._getResolution(),", + " object._getRelief(),", + " object._getSeed(),", + " object._getHeightmapImage(),", + " object._getEdits(),", + " ].join('|');", + " }", + "", + " loadFromProperties() {", + " const { object } = this;", + " const resolution = parseInt(object._getResolution(), 10);", + " const validResolution = RESOLUTIONS.indexOf(resolution) !== -1 ? resolution : DEFAULT_RESOLUTION;", + " this.sculptData = object._getSculptData();", + " this.loadedFrom = this._getSourceProperties();", + "", + " this.baseData = this._getBaseData(validResolution);", + " let data = codec.decode(this.sculptData, (resolution) => copyTerrainData(this._getBaseData(resolution)));", + " if (data && data.resolution !== validResolution) {", + " data = data.resampled(validResolution);", + " }", + " this.setData(data || copyTerrainData(this.baseData));", + " }", + "", + " /**", + " * @param {number} resolution", + " * @returns {TerrainData} The data from the relief or the heightmap, shared: it must not be changed.", + " */", + " _getBaseData(resolution) {", + " const { object } = this;", + " const key = [resolution, object._getRelief(), object._getSeed(), object._getHeightmapImage(), object._getEdits()].join('|');", + " const cachedData = baseDataCache.get(key);", + " if (cachedData) return cachedData;", + "", + " const data = new TerrainData(resolution);", + " const heightmapImage = object._getHeightmapImage();", + " const isHeightmapLoaded =", + " !!heightmapImage && loadHeightsFromImage(data, object.getRuntimeScene().getGame(), heightmapImage);", + " if (!isHeightmapLoaded) {", + " relief.generate(data, object._getRelief(), object._getSeed());", + " if (heightmapImage) this._reloadOnceLoaded(heightmapImage);", + " }", + " edits.apply(data, object._getEdits());", + " // Not kept if the image is missing: it may be loaded later.", + " if (isHeightmapLoaded || !heightmapImage) baseDataCache.set(key, data);", + " if (baseDataCache.size > MAX_CACHED_BASE_DATA) {", + " baseDataCache.delete(baseDataCache.keys().next().value);", + " }", + " return data;", + " }", + "", + " /** @param {TerrainData} data */", + " setData(data) {", + " this.version = ++lastGroundVersion;", + " this.surfaceVersion = this.version;", + " this.surfaceChanges.length = 0;", + " this.surfaceChangesStartVersion = this.version;", + " const hasSameResolution = data.resolution === this.data.resolution && this.renderer.chunks.length > 0;", + " this.data = data;", + " const allSamples = new SampleRectangle().set(0, 0, data.resolution, data.resolution);", + " this.surfaceHeights = new Float32Array(data.heights.length);", + " this.renderer.setHasHoles(false);", + " this._updateSurfaceHeights(allSamples);", + " if (hasSameResolution) {", + " this.renderer.updateSamples(allSamples, true);", + " } else {", + " this.renderer.rebuild();", + " }", + " for (const bodyUpdater of this.bodyUpdaters) {", + " bodyUpdater.recreateShape();", + " }", + " }", + "", + " onHotReloading() {", + " const sculptData = this.object._getSculptData();", + " const savedIndex = this.savedSculptData.indexOf(sculptData);", + " if (savedIndex !== -1 && this._getSourceProperties() === this.loadedFrom) {", + " // The terrain already has this data, or newer changes.", + " this.savedSculptData.splice(0, savedIndex + 1);", + " } else if (sculptData !== this.sculptData || this._getSourceProperties() !== this.loadedFrom) {", + " this.savedSculptData.length = 0;", + " this.loadFromProperties();", + " }", + " this.renderer.updateMaterial();", + " }", + "", + " /** @returns {string} The data to save in the sculpt data property. */", + " save() {", + " this.sculptData = codec.encode(this.data, this.baseData);", + " this.savedSculptData.push(this.sculptData);", + " return this.sculptData;", + " }", + "", + " getCellWidth() {", + " return this.object.getWidth() / this.data.resolution;", + " }", + "", + " getCellHeight() {", + " return this.object.getHeight() / this.data.resolution;", + " }", + "", + " _toGridX(x) {", + " return (x - this.object.getX()) / this.getCellWidth();", + " }", + "", + " _toGridY(y) {", + " return (y - this.object.getY()) / this.getCellHeight();", + " }", + "", + " /** @returns {number} The Z position of the ground. */", + " getHeightAt(x, y) {", + " return this.object.getZ() + this.data.sampleHeight(this._toGridX(x), this._toGridY(y)) * this.object.getDepth();", + " }", + "", + " /** @returns {number} The angle of the ground with the horizontal, in degrees. */", + " getSlopeAt(x, y) {", + " const gridX = this._toGridX(x);", + " const gridY = this._toGridY(y);", + " const { data } = this;", + " const depth = this.object.getDepth();", + " const slopeX = ((data.sampleHeight(gridX + 0.5, gridY) - data.sampleHeight(gridX - 0.5, gridY)) * depth) / this.getCellWidth();", + " const slopeY = ((data.sampleHeight(gridX, gridY + 0.5) - data.sampleHeight(gridX, gridY - 0.5)) * depth) / this.getCellHeight();", + " return gdjs.toDegrees(Math.atan(Math.hypot(slopeX, slopeY)));", + " }", + "", + " /** @returns {number} The layer painted the most, from 1 to 4. */", + " getLayerAt(x, y) {", + " return this.data.getDominantLayer(this._toGridX(x), this._toGridY(y)) + 1;", + " }", + "", + " /**", + " * @param {number} layer From 1 to 4.", + " * @returns {number} How much the layer is painted, from 0 to 1.", + " */", + " getLayerWeightAt(x, y, layer) {", + " const layerIndex = clampInteger(layer - 1, 0, LAYER_COUNT - 1);", + " return this.data.sampleLayerWeight(this._toGridX(x), this._toGridY(y), layerIndex);", + " }", + "", + " /** @returns {number[]} The painted tint: red, green and blue, from 0 to 255 (white when there is none). */", + " getTintAt(x, y) {", + " const gridX = this._toGridX(x);", + " const gridY = this._toGridY(y);", + " return [0, 1, 2].map((channel) => Math.round(this.data.sampleColor(gridX, gridY, channel) * 255));", + " }", + "", + " /** @returns {number} How wet the ground is, from 0 (dry) to 1, painted or near the water (see the wet shore). */", + " getWetnessAt(x, y) {", + " const paintedWetness = this.data.sampleColor(this._toGridX(x), this._toGridY(y), 3);", + " const { object } = this;", + " if (!object._getWetShore()) return paintedWetness;", + " const fade = Math.max(object._getWetShoreFade(), 0.001);", + " const shoreWetness = clamp01((object._getWetShoreZ() + fade - this.getHeightAt(x, y)) / fade);", + " // Like the smoothstep of the shader.", + " return Math.max(paintedWetness, shoreWetness * shoreWetness * (3 - 2 * shoreWetness));", + " }", + "", + " _isInArea(x, y) {", + " const { object } = this;", + " return (", + " x >= object.getX() &&", + " x <= object.getX() + object.getWidth() &&", + " y >= object.getY() &&", + " y <= object.getY() + object.getHeight()", + " );", + " }", + "", + " /** @returns {boolean} true if the position is above or under the terrain, and not in a hole. */", + " containsPoint(x, y) {", + " return this._isInArea(x, y) && this.data.sampleSolidity(this._toGridX(x), this._toGridY(y)) >= 0.5;", + " }", + "", + " /** @returns {boolean} true if there is a hole in the terrain at the position, like what is drawn. */", + " isHoleAt(x, y) {", + " return this._isInArea(x, y) && this.data.sampleSolidity(this._toGridX(x), this._toGridY(y)) < 0.5;", + " }", + "", + " _getStroke(x1, y1, x2, y2, radius) {", + " const stroke = this._stroke;", + " stroke.cellWidth = this.getCellWidth();", + " stroke.cellHeight = this.getCellHeight();", + " stroke.ax = x1 - this.object.getX();", + " stroke.ay = y1 - this.object.getY();", + " stroke.bx = x2 - this.object.getX();", + " stroke.by = y2 - this.object.getY();", + " stroke.radius = Math.max(radius, 0);", + " return stroke;", + " }", + "", + " /**", + " * @param {SampleRectangle} samples", + " * @param {boolean} haveHeightsChanged false when only the paint changed.", + " */", + " _onSamplesChanged(samples, haveHeightsChanged) {", + " if (samples.isEmpty()) return;", + " this.version = ++lastGroundVersion;", + " this.changedSamples.add(samples);", + " // Painting changes the surface when it makes holes or fills them.", + " const hasSurfaceChanged = this._updateSurfaceHeights(samples);", + " if (!haveHeightsChanged && !hasSurfaceChanged) return;", + " this.surfaceVersion = this.version;", + " this.surfaceChanges.push({ version: this.version, samples: new SampleRectangle().set(samples.minI, samples.minJ, samples.maxI, samples.maxJ) });", + " if (this.surfaceChanges.length > MAX_SURFACE_CHANGES) {", + " this.surfaceChangesStartVersion = this.surfaceChanges.shift().version;", + " }", + " for (const bodyUpdater of this.bodyUpdaters) {", + " bodyUpdater.changedSamples.add(samples);", + " }", + " }", + "", + " /**", + " * @param {SampleRectangle} samples", + " * @returns {boolean} true if heights of the surface changed, or holes were made or filled.", + " */", + " _updateSurfaceHeights(samples) {", + " const { data, surfaceHeights } = this;", + " let hasChanged = false;", + " let hasHoles = false;", + " for (let j = samples.minJ; j <= samples.maxJ; j++) {", + " for (let i = samples.minI; i <= samples.maxI; i++) {", + " const index = j * data.size + i;", + " const isHole = data.isHole(index);", + " const height = isHole ? NaN : data.heights[index];", + " const previousHeight = surfaceHeights[index];", + " if (isHole) {", + " hasHoles = true;", + " if (Number.isNaN(previousHeight)) continue;", + " } else if (height === previousHeight) {", + " continue;", + " }", + " surfaceHeights[index] = height;", + " hasChanged = true;", + " }", + " }", + " // Filled holes keep the shaders drawing holes until the data is loaded again.", + " if (hasHoles) this.renderer.setHasHoles(true);", + " return hasChanged;", + " }", + "", + " /** @param {number} height In scene units, negative to lower the ground. */", + " raise(x1, y1, x2, y2, radius, height) {", + " const samples = this._strokeChangedSamples;", + " brushes.raise(this.data, this._getStroke(x1, y1, x2, y2, radius), height / this.object.getDepth(), samples);", + " this._onSamplesChanged(samples, true);", + " }", + "", + " /** @param {number} z The Z position to flatten the ground to. */", + " /** @param {number} [toZ] The Z position at the end of the segment, for a slope. The same as z by default. */", + " flatten(x1, y1, x2, y2, radius, z, strength, toZ = z) {", + " const samples = this._strokeChangedSamples;", + " const toHeight = (value) => (value - this.object.getZ()) / this.object.getDepth();", + " brushes.flatten(this.data, this._getStroke(x1, y1, x2, y2, radius), toHeight(z), strength, samples, toHeight(toZ));", + " this._onSamplesChanged(samples, true);", + " }", + "", + " smooth(x1, y1, x2, y2, radius, strength) {", + " const samples = this._strokeChangedSamples;", + " brushes.smooth(this.data, this._getStroke(x1, y1, x2, y2, radius), strength, samples);", + " this._onSamplesChanged(samples, true);", + " }", + "", + " /** @param {number} layer From 1 to 4. */", + " paint(x1, y1, x2, y2, radius, layer, strength) {", + " const layerIndex = clampInteger(layer - 1, 0, LAYER_COUNT - 1);", + " const samples = this._strokeChangedSamples;", + " brushes.paint(this.data, this._getStroke(x1, y1, x2, y2, radius), layerIndex, strength, samples);", + " this.hasPaintChanged = true;", + " this._onSamplesChanged(samples, false);", + " }", + "", + " /** @param {number[]} color The red, green and blue (0-255) of the tint: white removes it. */", + " tint(x1, y1, x2, y2, radius, color, strength) {", + " const samples = this._strokeChangedSamples;", + " brushes.tint(this.data, this._getStroke(x1, y1, x2, y2, radius), color, strength, samples);", + " this.hasPaintChanged = true;", + " this._onSamplesChanged(samples, false);", + " }", + "", + " /** @param {number} wetness From 0 (dry) to 1. */", + " wet(x1, y1, x2, y2, radius, wetness, strength) {", + " const samples = this._strokeChangedSamples;", + " brushes.wet(this.data, this._getStroke(x1, y1, x2, y2, radius), wetness, strength, samples);", + " this.hasPaintChanged = true;", + " this._onSamplesChanged(samples, false);", + " }", + "", + " /** Make a hole around a segment (`fillHoles` fills it again). */", + " makeHole(x1, y1, x2, y2, radius) {", + " const samples = this._strokeChangedSamples;", + " brushes.hole(this.data, this._getStroke(x1, y1, x2, y2, radius), samples);", + " this.hasPaintChanged = true;", + " this._onSamplesChanged(samples, false);", + " }", + "", + " /** Fill the holes around a segment with the layers of the ground around them. */", + " fillHoles(x1, y1, x2, y2, radius) {", + " const samples = this._strokeChangedSamples;", + " brushes.fill(this.data, this._getStroke(x1, y1, x2, y2, radius), samples);", + " this.hasPaintChanged = true;", + " this._onSamplesChanged(samples, false);", + " }", + "", + " /**", + " * Make the entrance of a tunnel or a cave: a hole, with the ground in", + " * front of it flattened to the floor of the tunnel.", + " * @param {number} angle The direction going into the ground, in degrees.", + " * @param {number} floorZ", + " * @param {number} length The length of the flat ground in front of the entrance.", + " */", + " carveEntrance(x, y, radius, angle, floorZ, length) {", + " const directionX = Math.cos(gdjs.toRad(angle));", + " const directionY = Math.sin(gdjs.toRad(angle));", + " this.flatten(x - directionX * length, y - directionY * length, x, y, radius * 1.5, floorZ, 1);", + " this.makeHole(x, y, x + directionX * radius, y + directionY * radius, radius);", + " }", + "", + " generate(reliefName, seed) {", + " this.baseData = new TerrainData(this.data.resolution);", + " relief.generate(this.baseData, reliefName, seed);", + " this.setData(copyTerrainData(this.baseData));", + " }", + "", + " loadHeightmap(imageResourceName) {", + " const baseData = new TerrainData(this.data.resolution);", + " if (loadHeightsFromImage(baseData, this.object.getRuntimeScene().getGame(), imageResourceName)) {", + " baseData.splat.set(this.data.splat);", + " baseData.colors.set(this.data.colors);", + " this.baseData = baseData;", + " this.setData(copyTerrainData(baseData));", + " }", + " }", + "", + " /** @param {gdjs.RuntimeObject[]} objects */", + " placeOnGround(objects) {", + " for (const object of objects) {", + " if (object === this.object || !gdjs.Base3DHandler.is3D(object)) continue;", + " const groundZ = this.getHeightAt(object.getCenterXInScene(), object.getCenterYInScene());", + " // The origin of the object is not always at its bottom.", + " object.setZ(groundZ + object.getZ() - object.getUnrotatedAABBMinZ());", + " }", + " }", + "", + " /**", + " * @returns {THREE.Vector3 | null} The point of the ground under the cursor,", + " * in the terrain group coordinates. Holes are hit too, so that brushes can fill them.", + " */", + " raycastFromCursor(game, raycaster, ndc) {", + " const layer = this.object.getInstanceContainer().getLayer(this.object.getLayer());", + " const camera = layer.getRenderer().getThreeCamera();", + " if (!camera) return null;", + " const inputManager = game.getInputManager();", + " ndc.set(", + " (inputManager.getCursorX() / game.getGameResolutionWidth()) * 2 - 1,", + " -(inputManager.getCursorY() / game.getGameResolutionHeight()) * 2 + 1", + " );", + " raycaster.setFromCamera(ndc, camera);", + " this.renderer.group.updateWorldMatrix(true, false);", + " const ray = raycaster.ray.clone().applyMatrix4(this.renderer.group.matrixWorld.clone().invert());", + " ray.direction.normalize();", + "", + " const box = new THREE.Box3(new THREE.Vector3(0, 0, 0), new THREE.Vector3(AREA_SIZE, AREA_SIZE, AREA_DEPTH));", + " const entry = box.containsPoint(ray.origin) ? ray.origin.clone() : ray.intersectBox(box, new THREE.Vector3());", + " if (!entry) return null;", + " const cellSize = AREA_SIZE / this.data.resolution;", + " const groundZAt = (point) => this.data.sampleHeight(point.x / cellSize, point.y / cellSize) * AREA_DEPTH;", + " const step = cellSize / 2;", + " const point = entry.clone();", + " const previousPoint = entry.clone();", + " for (let distance = 0; distance < AREA_SIZE * 2; distance += step) {", + " if (point.z <= groundZAt(point)) {", + " // Refine between the last point above the ground and this one.", + " for (let iteration = 0; iteration < 8; iteration++) {", + " const middle = previousPoint.clone().lerp(point, 0.5);", + " if (middle.z <= groundZAt(middle)) point.copy(middle);", + " else previousPoint.copy(middle);", + " }", + " return point;", + " }", + " if (!box.containsPoint(point)) return null;", + " previousPoint.copy(point);", + " point.addScaledVector(ray.direction, step);", + " }", + " return null;", + " }", + "", + " /**", + " * @param {THREE.Vector3} localPosition In the terrain group coordinates (like from `raycastFromCursor`).", + " * @returns {{x: number, y: number, z: number}} The position in the scene.", + " */", + " toScenePosition(localPosition) {", + " const { object } = this;", + " return {", + " x: object.getX() + (localPosition.x / AREA_SIZE) * object.getWidth(),", + " y: object.getY() + (localPosition.y / AREA_SIZE) * object.getHeight(),", + " z: object.getZ() + (localPosition.z / AREA_DEPTH) * object.getDepth(),", + " };", + " }", + "", + " /** Draw the changes of the ground (collisions are updated by the physics engine). */", + " applyChanges() {", + " this.renderer.updateSamples(this.changedSamples, this.hasPaintChanged);", + " this.changedSamples.clear();", + " this.hasPaintChanged = false;", + " }", + "", + " /** @returns {number} A number changing each time the ground changes (see `getGrounds`). */", + " getVersion() {", + " return this.version;", + " }", + "", + " /**", + " * @param {number} sinceVersion", + " * @returns {gdjs.SurfaceArea | null} The part of the ground whose shape changed since the version (see `gdjs.Surface`).", + " */", + " getChangedArea(sinceVersion) {", + " if (sinceVersion < this.surfaceChangesStartVersion) return null;", + " const samples = new SampleRectangle();", + " for (const change of this.surfaceChanges) {", + " if (change.version > sinceVersion) samples.add(change.samples);", + " }", + " if (samples.isEmpty()) return { minX: 0, minY: 0, maxX: 0, maxY: 0 };", + " const { resolution } = this.data;", + " return {", + " minX: samples.minI / resolution,", + " minY: samples.minJ / resolution,", + " maxX: samples.maxI / resolution,", + " maxY: samples.maxJ / resolution,", + " };", + " }", + "", + " _getTransform() {", + " const { object } = this;", + " return [object.getX(), object.getY(), object.getZ(), object.getWidth(), object.getHeight(), object.getDepth()].join(',');", + " }", + "", + " /** @param {string} imageResourceName A heightmap image that is not loaded yet. */", + " _reloadOnceLoaded(imageResourceName) {", + " if (this.loadingHeightmapImage === imageResourceName) return;", + " this.loadingHeightmapImage = imageResourceName;", + " const game = this.object.getRuntimeScene().getGame();", + " game.getImageManager()", + " .loadResource(imageResourceName)", + " .then(() => {", + " if (this.isDisposed || this.loadingHeightmapImage !== imageResourceName) return;", + " this.loadingHeightmapImage = '';", + " if (getLoadedImage(game, imageResourceName)) this.loadFromProperties();", + " })", + " .catch(() => {});", + " }", + "", + " update() {", + " const transform = this._getTransform();", + " if (transform !== this._transform) {", + " this._transform = transform;", + " this.version = ++lastGroundVersion;", + " }", + " this.applyChanges();", + " this.renderer.uniforms.terrainWorldScale.value = getWorldScale(this.object);", + " this._updateWetShore();", + " const layer = this.object.getInstanceContainer().getLayer(this.object.getLayer());", + " this.renderer.updateLevelsOfDetail(layer.getRenderer().getThreeCamera());", + " }", + "", + " _updateWetShore() {", + " const { object } = this;", + " // In the coordinates of the terrain group, like the vertices.", + " const depthScale = AREA_DEPTH / Math.max(object.getDepth(), 0.0001);", + " this.renderer.uniforms.terrainWetShore.value.set(", + " (object._getWetShoreZ() - object.getZ()) * depthScale,", + " Math.max(object._getWetShoreFade(), 0.001) * depthScale,", + " object._getWetShore() ? 1 : 0", + " );", + " }", + "", + " dispose() {", + " this.isDisposed = true;", + " getGrounds(this.object.getRuntimeScene()).delete(this);", + " if (typeof this.object.setSurface === 'function' && this.object.getSurface() === this.surface) {", + " this.object.setSurface(null);", + " }", + " this.renderer.dispose();", + " for (const bodyUpdater of this.bodyUpdaters) {", + " bodyUpdater.dispose();", + " }", + " }", + "}", + "", + "/**", + " * @param {gdjs.RuntimeObject} object", + " * @returns {number} The number of scene units by 3D world unit (1 in GDevelop versions before the world scale).", + " */", + "const getWorldScale = (object) => {", + " const scene = object.getRuntimeScene();", + " return typeof scene.getRenderer3DWorldScale === 'function' ? scene.getRenderer3DWorldScale() : 1;", + "};", + "", + "const averageColors = new WeakMap();", + "let white = null;", + "/**", + " * @param {THREE.Texture} texture", + " * @returns {THREE.Color} The average color of the texture (white when it's not loaded).", + " */", + "const getAverageColor = (texture) => {", + " const cachedColor = averageColors.get(texture);", + " if (cachedColor) return cachedColor;", + " const image = texture.image;", + " const isDrawable =", + " typeof document !== 'undefined' &&", + " ((image instanceof HTMLImageElement && image.complete && image.naturalWidth > 0) ||", + " image instanceof HTMLCanvasElement ||", + " (typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap));", + " if (!isDrawable) return white || (white = new THREE.Color(1, 1, 1));", + " const size = 8;", + " const canvas = document.createElement('canvas');", + " canvas.width = size;", + " canvas.height = size;", + " const context = canvas.getContext('2d');", + " context.drawImage(image, 0, 0, size, size);", + " const pixels = context.getImageData(0, 0, size, size).data;", + " let red = 0;", + " let green = 0;", + " let blue = 0;", + " for (let index = 0; index < pixels.length; index += 4) {", + " red += pixels[index];", + " green += pixels[index + 1];", + " blue += pixels[index + 2];", + " }", + " const count = size * size * 255;", + " const color = new THREE.Color().setRGB(red / count, green / count, blue / count, THREE.SRGBColorSpace);", + " averageColors.set(texture, color);", + " return color;", + "};", + "", + "const svgIcon = (content) =>", + " 'data:image/svg+xml,' +", + " encodeURIComponent('' + content + '');", + "const strokes = 'fill=\"none\" stroke=\"#000\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"';", + "const toolIcons = {", + " Raise: svgIcon(''),", + " Lower: svgIcon(''),", + " Smooth: svgIcon(''),", + " Flatten: svgIcon(''),", + " Size: svgIcon(''),", + " Strength: svgIcon(''),", + " Undo: svgIcon(''),", + " Hole: svgIcon(''),", + " FillHoles: svgIcon(''),", + " Wetness: svgIcon(''),", + "};", + "// Tints for the color brush when the editor has no color picker (older GDevelop versions).", + "const tintPresets = ['#e2c879', '#a5c45a', '#8a6a48', '#c98a62', '#9a9a92', '#ffffff'];", + "", + "/**", + " * The sculpt and paint tools shown in the scene editor when a terrain is", + " * selected. Without the needed editor functions (older GDevelop versions),", + " * nothing is shown: the terrain can still be changed with events.", + " */", + "class TerrainEditorTools {", + " constructor() {", + " /** '' when no brush is used. */", + " this.toolName = '';", + " this.sizePercent = 30;", + " this.strengthPercent = 50;", + " /** @type {Terrain | null} */", + " this.terrain = null;", + " /** @type {gdjs.InGameEditor | null} */", + " this.editor = null;", + " this.isStroking = false;", + " this.flattenHeight = 0;", + " /** The color of the \"Tint\" brush, as \"#rrggbb\". */", + " this.tintColor = tintPresets[0];", + " this.wetnessPercent = 100;", + " this.lastStrokeTime = 0;", + " /** @type {{data: TerrainData, baseData: TerrainData}[]} */", + " this.undoSteps = [];", + " this.raycaster = new THREE.Raycaster();", + " this.ndc = new THREE.Vector2();", + " this._swatchColor = new THREE.Color();", + " }", + "", + " /** @param {gdjs.InGameEditor} editor */", + " update(editor) {", + " if (typeof editor.showToolbar !== 'function') {", + " // Older GDevelop versions: the terrain can only be changed with events.", + " return;", + " }", + " this.editor = editor;", + " const selectedObjects = editor.getSelectedObjects();", + " const terrain =", + " selectedObjects.length === 1 && selectedObjects[0].__terrain3D ? selectedObjects[0].__terrain3D : null;", + " if (terrain !== this.terrain) {", + " this._endStroke();", + " if (this.terrain) this.terrain.renderer.setBrush(null, 0);", + " this.terrain = terrain;", + " this.undoSteps.length = 0;", + " }", + " if (terrain) editor.showToolbar('Terrain3D', this._getToolbarItems(terrain));", + " const game = editor.getRuntimeGame();", + " if (!terrain || !this.toolName) {", + " if (terrain) terrain.renderer.setBrush(null, 0);", + " return;", + " }", + "", + " const inputManager = game.getInputManager();", + " if (inputManager.wasKeyJustPressed(gdjs.evtTools.input.keysNameToCode.Escape)) {", + " this._endStroke();", + " this.toolName = '';", + " terrain.renderer.setBrush(null, 0);", + " return;", + " }", + " editor.captureLeftMouseButton();", + " const hit = terrain.raycastFromCursor(game, this.raycaster, this.ndc);", + " const radius = this._getRadius();", + " terrain.renderer.setBrush(hit, radius);", + "", + " // Older GDevelop versions don't have it.", + " const isPressedForTool =", + " typeof editor.isCapturedLeftMouseButtonPressed === 'function'", + " ? editor.isCapturedLeftMouseButtonPressed()", + " : inputManager.isMouseButtonPressed(0);", + " // The button is released outside the canvas when the mouse is on a toolbar.", + " const isPressed = isPressedForTool && inputManager.isMouseInsideCanvas();", + " if (isPressed && hit && !this.isStroking) {", + " this._startStroke(terrain, hit);", + " }", + " if (this.isStroking && isPressed && hit) {", + " this._applyBrush(terrain, hit, radius);", + " }", + " if (!isPressed) {", + " this._endStroke();", + " }", + " }", + "", + " _getRadius() {", + " const { object } = this.terrain;", + " const maxRadius = Math.max(object.getWidth(), object.getHeight()) / 4;", + " return Math.max(maxRadius * Math.pow(this.sizePercent / 100, 2), this.terrain.getCellWidth());", + " }", + "", + " _startStroke(terrain, hit) {", + " this.isStroking = true;", + " this.lastStrokeTime = performance.now();", + " this.flattenHeight = terrain.toScenePosition(hit).z;", + " this.undoSteps.push({ data: copyTerrainData(terrain.data), baseData: terrain.baseData });", + " const maxUndoSteps = Math.max(Math.floor(UNDO_STEPS_SAMPLES / terrain.data.heights.length), 2);", + " while (this.undoSteps.length > maxUndoSteps) this.undoSteps.shift();", + " }", + "", + " _endStroke() {", + " if (!this.isStroking) return;", + " this.isStroking = false;", + " this._save();", + " }", + "", + " _applyBrush(terrain, hit, radius) {", + " const now = performance.now();", + " const elapsedSeconds = Math.min((now - this.lastStrokeTime) / 1000, 0.1);", + " this.lastStrokeTime = now;", + " const { x, y } = terrain.toScenePosition(hit);", + " const amount = (this.strengthPercent / 100) * elapsedSeconds;", + " const depth = terrain.object.getDepth();", + " if (this.toolName === 'Raise') terrain.raise(x, y, x, y, radius, amount * depth * 0.5);", + " else if (this.toolName === 'Lower') terrain.raise(x, y, x, y, radius, -amount * depth * 0.5);", + " else if (this.toolName === 'Smooth') terrain.smooth(x, y, x, y, radius, amount * 10);", + " else if (this.toolName === 'Flatten') terrain.flatten(x, y, x, y, radius, this.flattenHeight, amount * 10);", + " else if (this.toolName.startsWith('Paint')) {", + " terrain.paint(x, y, x, y, radius, parseInt(this.toolName.slice(5), 10), amount * 8);", + " } else if (this.toolName === 'Hole') {", + " terrain.makeHole(x, y, x, y, radius);", + " } else if (this.toolName === 'FillHoles') {", + " terrain.fillHoles(x, y, x, y, radius);", + " } else if (this.toolName === 'Tint') {", + " terrain.tint(x, y, x, y, radius, gdjs.rgbOrHexToRGBColor(this.tintColor), amount * 8);", + " } else if (this.toolName === 'Wetness') {", + " terrain.wet(x, y, x, y, radius, this.wetnessPercent / 100, amount * 8);", + " }", + " // Drawn now, as the terrain was updated earlier in this frame.", + " terrain.applyChanges();", + " }", + "", + " _save() {", + " const { terrain, editor } = this;", + " if (!terrain || !editor) return;", + " editor.updateObjectProperties(terrain.object.getName(), { SculptData: terrain.save() });", + " }", + "", + " _undo() {", + " const undoStep = this.undoSteps.pop();", + " // The relief, the edits or the resolution may have changed since the stroke.", + " if (!this.terrain || !undoStep || undoStep.baseData !== this.terrain.baseData) {", + " this.undoSteps.length = 0;", + " return;", + " }", + " this.terrain.setData(undoStep.data);", + " this._save();", + " }", + "", + " /** @returns {Array} The settings of the tint or wetness brush, when it's used. */", + " _getColorToolItems() {", + " const { editor } = this;", + " if (this.toolName === 'Tint') {", + " if (editor && typeof editor.isToolbarItemTypeSupported === 'function' && editor.isToolbarItemTypeSupported('color')) {", + " return [", + " {", + " type: 'color',", + " id: 'TintColor',", + " tooltip: 'Color of the tint',", + " value: this.tintColor,", + " onChange: (color) => (this.tintColor = color),", + " },", + " ];", + " }", + " return tintPresets.map((color) => ({", + " type: 'button',", + " id: 'TintPreset' + color,", + " tooltip: color === '#ffffff' ? 'No tint' : 'Tint with this color',", + " color,", + " isActive: this.tintColor === color,", + " onClick: () => (this.tintColor = color),", + " }));", + " }", + " if (this.toolName === 'Wetness') {", + " return [", + " {", + " type: 'slider',", + " id: 'WetnessAmount',", + " tooltip: 'Wetness',", + " iconUrl: toolIcons.Wetness,", + " min: 0,", + " max: 100,", + " value: this.wetnessPercent,", + " onChange: (value) => (this.wetnessPercent = value),", + " },", + " ];", + " }", + " return [];", + " }", + "", + " /**", + " * @param {Terrain} terrain", + " * @returns {Array} The toolbar for the current state (the editor only", + " * updates what changed).", + " */", + " _getToolbarItems(terrain) {", + " const { object } = terrain;", + " const toolButton = (toolName, tooltip, color) => ({", + " type: 'button',", + " id: toolName,", + " tooltip,", + " iconUrl: toolIcons[toolName],", + " color,", + " isActive: this.toolName === toolName,", + " onClick: () => (this.toolName = this.toolName === toolName ? '' : toolName),", + " });", + " const layerColors = [object._getLayer1Color(), object._getLayer2Color(), object._getLayer3Color(), object._getLayer4Color()];", + " // Layers are shown with their color, tinting their texture.", + " const swatchColors = layerColors.map((color, index) =>", + " this._swatchColor", + " .set(gdjs.rgbOrHexStringToNumber(color))", + " .multiply(getAverageColor(terrain.renderer.uniforms['terrainLayerMap' + index].value))", + " .getHexString()", + " );", + " return [", + " toolButton('Raise', 'Raise the ground (drag on the terrain)'),", + " toolButton('Lower', 'Lower the ground'),", + " toolButton('Smooth', 'Smooth the ground'),", + " toolButton('Flatten', 'Flatten the ground to the height where the drag starts'),", + " { type: 'divider', id: 'PaintDivider' },", + " ...swatchColors.map((swatchColor, index) =>", + " toolButton('Paint' + (index + 1), 'Paint layer ' + (index + 1), '#' + swatchColor)", + " ),", + " toolButton('Hole', 'Make a hole, for the entrance of a tunnel or a cave'),", + " toolButton('FillHoles', 'Fill holes, with the layers of the ground around them'),", + " { type: 'divider', id: 'ColorDivider' },", + " toolButton('Tint', 'Tint the ground (white removes the tint)', this.tintColor),", + " toolButton('Wetness', 'Make the ground wet, or dry with a wetness of 0'),", + " ...this._getColorToolItems(),", + " { type: 'divider', id: 'BrushDivider' },", + " {", + " type: 'slider',", + " id: 'Size',", + " tooltip: 'Brush size',", + " iconUrl: toolIcons.Size,", + " min: 1,", + " max: 100,", + " value: this.sizePercent,", + " onChange: (value) => (this.sizePercent = value),", + " },", + " {", + " type: 'slider',", + " id: 'Strength',", + " tooltip: 'Brush strength',", + " iconUrl: toolIcons.Strength,", + " min: 5,", + " max: 100,", + " value: this.strengthPercent,", + " onChange: (value) => (this.strengthPercent = value),", + " },", + " { type: 'button', id: 'Undo', tooltip: 'Undo the last stroke on this terrain', iconUrl: toolIcons.Undo, onClick: () => this._undo() },", + " ];", + " }", + "}", + "", + "const editorTools = new TerrainEditorTools();", + "if (gdjs.registerInGameEditorPostStepCallback) {", + " gdjs.registerInGameEditorPostStepCallback((editor) => editorTools.update(editor));", + "}", + "", + "gdjs.__terrain3DExtension = {", + " Terrain,", + " // Used by the terrain grass and the scattered models.", + " getWorldScale,", + " getGrounds,", + " patchShaderCode,", + " createRandom,", + " findHighestGround,", + " GroundPlacement,", + " svgIcon,", + " toolIcons,", + " // Exposed for tests.", + " TerrainBodyUpdater,", + " TerrainData,", + " SampleRectangle,", + " brushes,", + " edits,", + " codec,", + " relief,", + "};" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [], + "description": "Define helper classes JavaScript code.", + "private": true + }, + { + "fullName": "Define grass helper classes", + "functionType": "Action", + "name": "DefineGrassHelperClasses", + "sentence": "Define grass helper classes JavaScript code", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (gdjs.__terrainGrassExtension) {", + " return;", + "}", + "// Blades grow on the grounds of the scene: the 3D terrains (see `getGrounds`", + "// in the terrain helper).", + "", + "// Size of the object inner area: instances are scaled from it.", + "const AREA_SIZE = 1000;", + "const AREA_DEPTH = 40;", + "const CHUNK_SIZE = 500;", + "const MAX_BENDING_OBJECTS = 8;", + "", + "/**", + " * A blade: a thin, tapered strip. Its width and height are 1: blades are", + " * scaled by the shader.", + " */", + "const createBladeGeometry = () => {", + " const halfWidths = [0.5, 0.42, 0.28, 0];", + " const heights = [0, 0.35, 0.7, 1];", + " const positions = [];", + " halfWidths.forEach((halfWidth, level) => {", + " positions.push(-halfWidth, 0, heights[level]);", + " if (halfWidth > 0) positions.push(halfWidth, 0, heights[level]);", + " });", + " const indices = [0, 1, 2, 1, 3, 2, 2, 3, 4, 3, 5, 4, 4, 5, 6];", + " const geometry = new THREE.InstancedBufferGeometry();", + " geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));", + " geometry.setAttribute('normal', new THREE.Float32BufferAttribute(new Array(positions.length).fill(0), 3));", + " geometry.setIndex(indices);", + " return geometry;", + "};", + "", + "const vertexShaderDeclarations = `", + "attribute vec4 grassRoot;", + "attribute vec2 grassShape;", + "// The tint of the ground at the root (linear, from 0 to 1).", + "attribute vec3 grassGroundTint;", + "uniform mat3 grassWorldToLocal;", + "uniform float grassWorldScale;", + "uniform float grassBladeWidth;", + "uniform float grassTime;", + "uniform float grassWindStrength;", + "uniform float grassWindSpeed;", + "uniform vec4 grassBenders[${MAX_BENDING_OBJECTS}];", + "uniform vec3 grassBaseColor;", + "uniform vec3 grassTipColor;", + "varying vec3 vGrassColor;", + "`;", + "const vertexShaderPosition = `", + "// Blades are shaped in scene units (with Y flipped like the 3D world), then", + "// moved back in the (scaled) object space.", + "float bladeHeight = position.z;", + "float grassAngle = grassRoot.w;", + "float bladeWidth = position.x * grassBladeWidth;", + "vec3 blade = vec3(", + " bladeWidth * cos(grassAngle),", + " bladeWidth * sin(grassAngle),", + " bladeHeight * grassShape.x", + ");", + "vec3 rootWorld = (modelMatrix * vec4(grassRoot.xyz, 1.0)).xyz * grassWorldScale;", + "// Tips bend more than the base.", + "float bendFactor = bladeHeight * bladeHeight * grassShape.x;", + "float gust = sin(grassTime * grassWindSpeed + rootWorld.x * 0.013 + rootWorld.y * 0.011);", + "float flutter = sin(grassTime * grassWindSpeed * 2.7 + grassShape.y * 40.0) * 0.3;", + "blade.xy += vec2(0.8, 0.5) * (gust + flutter + 0.6) * grassWindStrength * 0.35 * bendFactor;", + "for (int i = 0; i < ${MAX_BENDING_OBJECTS}; i++) {", + " vec4 bender = grassBenders[i];", + " if (bender.z <= 0.0) continue;", + " vec2 away = rootWorld.xy - bender.xy;", + " float distanceToBender = length(away);", + " float push = 1.0 - smoothstep(bender.z * 0.3, bender.z, distanceToBender);", + " if (push > 0.0) {", + " blade.xy += normalize(away + vec2(0.001)) * push * bendFactor * 0.9;", + " blade.z *= 1.0 - push * 0.5;", + " }", + "}", + "vec3 transformed = grassRoot.xyz + grassWorldToLocal * (blade / grassWorldScale);", + "vGrassColor = mix(grassBaseColor, grassTipColor, bladeHeight) * (0.85 + 0.3 * grassShape.y) * grassGroundTint;", + "`;", + "const vertexShaderNormal = `", + "// Blades are lit like the ground below them.", + "transformedNormal = normalize((viewMatrix * vec4(0.0, 0.0, 1.0, 0.0)).xyz);", + "`;", + "const fragmentShaderDeclarations = `", + "varying vec3 vGrassColor;", + "`;", + "const fragmentShaderColor = `", + "diffuseColor.rgb *= vGrassColor;", + "`;", + "// Both faces of blades are lit like the ground (not flipped for back faces).", + "const fragmentShaderNormal = `", + "#include ", + "normal = normalize(vNormal);", + "nonPerturbedNormal = normal;", + "`;", + "", + "const { getWorldScale, patchShaderCode, createRandom, findHighestGround, GroundPlacement } = gdjs.__terrain3DExtension;", + "", + "/**", + " * The grass of a terrain grass object: blades drawn with GPU instancing, in chunks", + " * that are hidden when off-screen and thinned out with the distance. They grow", + " * on the grounds below, like 3D terrains (only where a given layer is painted).", + " */", + "class Grass {", + " /** @param {gdjs.CustomRuntimeObject3D} object */", + " constructor(object) {", + " this.object = object;", + " this.group = new THREE.Group();", + " object.get3DRendererObject().add(this.group);", + " this.bladeGeometry = createBladeGeometry();", + " /** @type {Array<{mesh: THREE.Mesh, blades: number, center: THREE.Vector3}>} */", + " this.chunks = [];", + " this.placement = new GroundPlacement(object.getRuntimeScene());", + " this._groundAtBlade = { ground: null, z: 0 };", + " this.uniforms = {", + " grassWorldToLocal: { value: new THREE.Matrix3() },", + " // Scene units by 3D world unit.", + " grassWorldScale: { value: 1 },", + " grassBladeWidth: { value: 1 },", + " grassTime: { value: 0 },", + " grassWindStrength: { value: 1 },", + " grassWindSpeed: { value: 1 },", + " grassBenders: { value: Array.from({ length: MAX_BENDING_OBJECTS }, () => new THREE.Vector4()) },", + " grassBaseColor: { value: new THREE.Color() },", + " grassTipColor: { value: new THREE.Color() },", + " };", + " this.material = new THREE.MeshStandardMaterial({ roughness: 1, metalness: 0, side: THREE.DoubleSide });", + " this.material.onBeforeCompile = (shader) => {", + " Object.assign(shader.uniforms, this.uniforms);", + " shader.vertexShader = patchShaderCode(shader.vertexShader, [", + " ['#include ', '#include \\n' + vertexShaderDeclarations],", + " ['#include ', vertexShaderPosition],", + " ['#include ', '#include \\n' + vertexShaderNormal],", + " ], 'TerrainGrass');", + " shader.fragmentShader = patchShaderCode(shader.fragmentShader, [", + " ['#include ', '#include \\n' + fragmentShaderDeclarations],", + " ['#include ', '#include \\n' + fragmentShaderColor],", + " ['#include ', fragmentShaderNormal],", + " ], 'TerrainGrass');", + " };", + " this.material.customProgramCacheKey = () => 'TerrainGrass';", + "", + " this._matrix = new THREE.Matrix4();", + " this._cameraPosition = new THREE.Vector3();", + " this._chunkPosition = new THREE.Vector3();", + " this._scale = new THREE.Vector3();", + " this.updateFromProperties();", + " }", + "", + " updateFromProperties() {", + " const { object } = this;", + " this.uniforms.grassBaseColor.value.set(gdjs.rgbOrHexStringToNumber(object._getBaseColor()));", + " this.uniforms.grassTipColor.value.set(gdjs.rgbOrHexStringToNumber(object._getTipColor()));", + " this.uniforms.grassWindStrength.value = object._getWindStrength();", + " this.uniforms.grassWindSpeed.value = object._getWindSpeed();", + " this.uniforms.grassBladeWidth.value = Math.max(object._getBladeWidth(), 0);", + " this.material.needsUpdate = true;", + " this.placement.placeAgain();", + " }", + "", + " _getPlacementKey() {", + " const { object } = this;", + " return [", + " object.getX(), object.getY(), object.getZ(), object.getWidth(), object.getHeight(), object.getDepth(),", + " object._getDensity(), object._getBladeHeight(), object._getGroundLayer(), object._getMaxSlope(),", + " object._getSeed(),", + " ].join('|');", + " }", + "", + " /** Place the blades again, for example after the ground or the object changed. */", + " rebuild() {", + " const grounds = Array.from(this.placement.grounds);", + " for (const chunk of this.chunks) {", + " chunk.mesh.removeFromParent();", + " chunk.mesh.geometry.dispose();", + " }", + " this.chunks.length = 0;", + "", + " const { object } = this;", + " const width = object.getWidth();", + " const height = object.getHeight();", + " const scaleX = width / AREA_SIZE;", + " const scaleY = height / AREA_SIZE;", + " const scaleZ = Math.max(object.getDepth() / AREA_DEPTH, 0.0001);", + " const bladesPerChunk = Math.round((Math.max(object._getDensity(), 0) * CHUNK_SIZE * CHUNK_SIZE) / 10000);", + " const groundLayer = parseInt(object._getGroundLayer(), 10) || 0;", + " const maxSlope = object._getMaxSlope();", + " const chunksX = Math.max(Math.ceil(width / CHUNK_SIZE), 1);", + " const chunksY = Math.max(Math.ceil(height / CHUNK_SIZE), 1);", + " const nextRandom = createRandom(object._getSeed() * 7919 + 1);", + " const bladeHeight = Math.max(object._getBladeHeight(), 0);", + "", + " for (let chunkY = 0; chunkY < chunksY; chunkY++) {", + " for (let chunkX = 0; chunkX < chunksX; chunkX++) {", + " const roots = new Float32Array(bladesPerChunk * 4);", + " const shapes = new Float32Array(bladesPerChunk * 2);", + " const groundTints = new Uint8Array(bladesPerChunk * 3).fill(255);", + " let blades = 0;", + " let minZ = Infinity;", + " let maxZ = -Infinity;", + " for (let attempt = 0; attempt < bladesPerChunk; attempt++) {", + " // In scene units, relative to the object.", + " const x = (chunkX + nextRandom()) * CHUNK_SIZE;", + " const y = (chunkY + nextRandom()) * CHUNK_SIZE;", + " const angle = nextRandom() * Math.PI * 2;", + " const heightFactor = 0.6 + 0.8 * nextRandom();", + " const tint = nextRandom();", + " const growth = nextRandom();", + " if (x > width || y > height) continue;", + " const sceneX = object.getX() + x;", + " const sceneY = object.getY() + y;", + " const { ground, z } = findHighestGround(grounds, sceneX, sceneY, this._groundAtBlade);", + " const groundZ = ground ? z : object.getZ();", + " if (ground) {", + " if (groundLayer > 0 && growth > ground.getLayerWeightAt(sceneX, sceneY, groundLayer)) continue;", + " if (ground.getSlopeAt(sceneX, sceneY) > maxSlope) continue;", + " if (typeof ground.getTintAt === 'function') {", + " const tint = ground.getTintAt(sceneX, sceneY);", + " for (let channel = 0; channel < 3; channel++) {", + " groundTints[blades * 3 + channel] = Math.round(255 * Math.pow(tint[channel] / 255, 2.2));", + " }", + " }", + " }", + " const localZ = (groundZ - object.getZ()) / scaleZ;", + " roots[blades * 4] = x / scaleX;", + " roots[blades * 4 + 1] = y / scaleY;", + " roots[blades * 4 + 2] = localZ;", + " roots[blades * 4 + 3] = angle;", + " shapes[blades * 2] = heightFactor * bladeHeight;", + " shapes[blades * 2 + 1] = tint;", + " minZ = Math.min(minZ, localZ);", + " maxZ = Math.max(maxZ, localZ);", + " blades++;", + " }", + " if (blades === 0) continue;", + "", + " const geometry = new THREE.InstancedBufferGeometry();", + " geometry.index = this.bladeGeometry.index;", + " geometry.setAttribute('position', this.bladeGeometry.attributes.position);", + " geometry.setAttribute('normal', this.bladeGeometry.attributes.normal);", + " geometry.setAttribute('grassRoot', new THREE.InstancedBufferAttribute(roots.subarray(0, blades * 4), 4));", + " geometry.setAttribute('grassShape', new THREE.InstancedBufferAttribute(shapes.subarray(0, blades * 2), 2));", + " geometry.setAttribute('grassGroundTint', new THREE.InstancedBufferAttribute(groundTints.subarray(0, blades * 3), 3, true));", + " geometry.instanceCount = blades;", + " // The furthest tips can go with the wind and the bending objects (see the shader).", + " const maxBladeHeight = bladeHeight * 1.4;", + " const lean =", + " (0.35 * 1.9 * 0.943 * Math.max(object._getWindStrength(), 0) + 0.9) * maxBladeHeight +", + " Math.max(object._getBladeWidth(), 0) / 2;", + " const leanX = lean / scaleX;", + " const leanY = lean / scaleY;", + " geometry.boundingBox = new THREE.Box3(", + " new THREE.Vector3((chunkX * CHUNK_SIZE) / scaleX - leanX, (chunkY * CHUNK_SIZE) / scaleY - leanY, minZ),", + " new THREE.Vector3(", + " Math.min((chunkX + 1) * CHUNK_SIZE, width) / scaleX + leanX,", + " Math.min((chunkY + 1) * CHUNK_SIZE, height) / scaleY + leanY,", + " maxZ + (bladeHeight * 1.4) / scaleZ", + " )", + " );", + " geometry.boundingSphere = geometry.boundingBox.getBoundingSphere(new THREE.Sphere());", + " const mesh = new THREE.Mesh(geometry, this.material);", + " mesh.receiveShadow = true;", + " // Raycasts would only test one blade: the selection plane is used instead.", + " mesh.raycast = () => {};", + " this.group.add(mesh);", + " this.chunks.push({ mesh, blades, center: geometry.boundingSphere.center.clone() });", + " }", + " }", + " }", + "", + " _updateBendingObjects() {", + " const benders = this.uniforms.grassBenders.value;", + " const objectName = this.object._getBendingObject();", + " const bendingObjects = objectName ? this.object.getInstanceContainer().getObjects(objectName) || [] : [];", + " for (let index = 0; index < MAX_BENDING_OBJECTS; index++) {", + " const bendingObject = bendingObjects[index];", + " if (!bendingObject) {", + " benders[index].set(0, 0, 0, 0);", + " continue;", + " }", + " // Blades are pushed in the 3D scene, which is flipped on Y.", + " const radius = Math.max(bendingObject.getWidth(), bendingObject.getHeight()) * 1.2;", + " benders[index].set(bendingObject.getCenterXInScene(), -bendingObject.getCenterYInScene(), radius, 0);", + " }", + " }", + "", + " /**", + " * @param {number} elapsedSeconds", + " * @param {number} time In seconds, the same for all grass objects.", + " */", + " update(elapsedSeconds, time, camera) {", + " if (this.placement.shouldPlaceAgain(elapsedSeconds, this._getPlacementKey())) {", + " this.rebuild();", + " }", + "", + " this.uniforms.grassTime.value = time;", + " this.uniforms.grassWorldScale.value = getWorldScale(this.object);", + " // The object transformation is applied just before rendering, it's needed now.", + " this.object.getRenderer().ensureUpToDate();", + " this.group.updateWorldMatrix(true, false);", + " this.uniforms.grassWorldToLocal.value.setFromMatrix4(this._matrix.copy(this.group.matrixWorld).invert());", + " this._updateBendingObjects();", + " if (!camera) return;", + "", + " // Far chunks are hidden, and thinned out before (in 3D world units).", + " const fadeDistance = Math.max(this.object._getFadeDistance(), 1) / getWorldScale(this.object);", + " this._cameraPosition.setFromMatrixPosition(camera.matrixWorld);", + " for (const chunk of this.chunks) {", + " const distance = this._chunkPosition", + " .copy(chunk.center)", + " .applyMatrix4(this.group.matrixWorld)", + " .distanceTo(this._cameraPosition);", + " const density = gdjs.evtTools.common.clamp((fadeDistance - distance) / (fadeDistance * 0.5), 0, 1);", + " chunk.mesh.visible = density > 0;", + " chunk.mesh.geometry.instanceCount = Math.ceil(chunk.blades * density);", + " }", + " }", + "", + " dispose() {", + " for (const chunk of this.chunks) {", + " chunk.mesh.geometry.dispose();", + " }", + " this.bladeGeometry.dispose();", + " this.material.dispose();", + " this.group.removeFromParent();", + " }", + "}", + "", + "gdjs.__terrainGrassExtension = { Grass };" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [], + "description": "Define grass helper classes JavaScript code.", + "private": true + }, + { + "fullName": "Define scattered models helper classes", + "functionType": "Action", + "name": "DefineScatterHelperClasses", + "sentence": "Define scattered models helper classes JavaScript code", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (gdjs.__terrainScatterExtension) {", + " return;", + "}", + "// Copies grow on the grounds of the scene, like the terrain grass (see", + "// `getGrounds` in the terrain helper).", + "", + "const {", + " getWorldScale,", + " getGrounds,", + " patchShaderCode,", + " createRandom,", + " findHighestGround,", + " GroundPlacement,", + " SampleRectangle,", + " brushes,", + " codec,", + " svgIcon,", + " toolIcons,", + "} = gdjs.__terrain3DExtension;", + "", + "// Size of the object inner area: instances are scaled from it.", + "const AREA_SIZE = 2000;", + "const AREA_DEPTH = 200;", + "// The painted density is stored in a grid of this many cells on each side,", + "// whatever the size of the object.", + "const MASK_RESOLUTION = 256;", + "const MASK_FORMAT_VERSION = '1';", + "// Copies are hidden in chunks: big chunks mean fewer draw calls, small ones", + "// hide more copies out of the screen.", + "const MIN_CHUNK_SIZE = 500;", + "const MAX_CHUNK_SIZE = 2000;", + "// Copies shrink to nothing over this part of the draw distance, instead of popping out.", + "const FADE_PART = 0.15;", + "const MAX_COPIES = 200000;", + "// Prisms of the trunk collisions.", + "const TRUNK_SIDES = 6;", + "", + "const clamp01 = (value) => (value < 0 ? 0 : value > 1 ? 1 : value);", + "", + "/**", + " * @returns {number} A number for a cell of the copies grid, changing with the seed.", + " */", + "const hashCell = (seed, i, j) => {", + " let h = Math.imul(i, 374761393) + Math.imul(j, 668265263) + Math.imul(seed, 982451653);", + " h = Math.imul(h ^ (h >>> 13), 1274126177);", + " return (h ^ (h >>> 16)) >>> 0;", + "};", + "", + "/**", + " * How dense copies are, from 0 to 1, in a grid covering the object: painted", + " * with the editor brushes, the edits or events.", + " */", + "class DensityMask {", + " /** @param {number} fillValue From 0 to 255. */", + " constructor(fillValue) {", + " // `resolution` and `size` let terrain brushes paint it.", + " this.resolution = MASK_RESOLUTION;", + " this.size = MASK_RESOLUTION + 1;", + " this.values = new Uint8Array(this.size * this.size).fill(fillValue);", + " }", + "", + " /**", + " * @param {number} u From 0 to 1, along the object width.", + " * @param {number} v From 0 to 1, along the object height.", + " * @returns {number} The density, from 0 to 1, interpolated between samples.", + " */", + " sample(u, v) {", + " const x = clamp01(u) * this.resolution;", + " const y = clamp01(v) * this.resolution;", + " const i = Math.min(Math.floor(x), this.resolution - 1);", + " const j = Math.min(Math.floor(y), this.resolution - 1);", + " const fx = x - i;", + " const fy = y - j;", + " const value = (i, j) => this.values[j * this.size + i];", + " const top = value(i, j) + fx * (value(i + 1, j) - value(i, j));", + " const bottom = value(i, j + 1) + fx * (value(i + 1, j + 1) - value(i, j + 1));", + " return (top + fy * (bottom - top)) / 255;", + " }", + "", + " /**", + " * Bring the density towards a target around a segment: fully up to 70%", + " * of the radius, then fading out (forests have dense centers and soft edges).", + " * @param {{cellWidth: number, cellHeight: number, ax: number, ay: number, bx: number, by: number, radius: number}} stroke", + " * @param {number} density The target, from 0 to 1.", + " * @param {number} strength From 0 to 1.", + " * @param {SampleRectangle} changedSamples", + " */", + " paint(stroke, density, strength, changedSamples) {", + " const { values } = this;", + " const target = Math.round(clamp01(density) * 255);", + " brushes.forEachSample(", + " this,", + " stroke,", + " (index, falloff) => {", + " // The falloff of terrain brushes is (1 - distance² / radius²)².", + " const weight = clamp01(2 * Math.sqrt(falloff));", + " // Rounded towards the target, so that light strokes still paint.", + " const change = (target - values[index]) * clamp01(weight * strength);", + " values[index] += change > 0 ? Math.ceil(change) : Math.floor(change);", + " },", + " changedSamples", + " );", + " }", + "", + " /** @returns {DensityMask} */", + " clone() {", + " const mask = new DensityMask(0);", + " mask.values.set(this.values);", + " return mask;", + " }", + "}", + "", + "/**", + " * Edits written in the Edits property: a JSON list of brush strokes, like", + " * `[{\"tool\": \"add\", \"x\": 0.5, \"y\": 0.5, \"radius\": 0.2}]`. Positions and", + " * radius are fractions of the object size, like the terrain edits.", + " */", + "const applyEdits = (mask, text) => {", + " if (!text.trim()) return;", + " let editList;", + " try {", + " editList = JSON.parse(text);", + " } catch (error) {", + " console.warn('The edits of scattered models are not valid JSON: ' + error.message);", + " return;", + " }", + " if (!Array.isArray(editList)) {", + " console.warn('The edits of scattered models must be a JSON list (like [{\"tool\": \"add\", ...}]).');", + " return;", + " }", + " const numberOr = (value, defaultValue) => (typeof value === 'number' && isFinite(value) ? value : defaultValue);", + " const changedSamples = new SampleRectangle();", + " for (const edit of editList) {", + " if (!edit || typeof edit !== 'object') continue;", + " const x = numberOr(edit.x, 0.5);", + " const y = numberOr(edit.y, 0.5);", + " const stroke = {", + " cellWidth: 1 / mask.resolution,", + " cellHeight: 1 / mask.resolution,", + " ax: x,", + " ay: y,", + " bx: numberOr(edit.toX, x),", + " by: numberOr(edit.toY, y),", + " radius: numberOr(edit.radius, 0.1),", + " };", + " const strength = clamp01(numberOr(edit.strength, 1));", + " if (edit.tool === 'add') {", + " mask.paint(stroke, numberOr(edit.density, 1), strength, changedSamples);", + " } else if (edit.tool === 'erase') {", + " mask.paint(stroke, 0, strength, changedSamples);", + " } else {", + " console.warn('Unknown tool in the edits of scattered models: \"' + edit.tool + '\" (use add or erase).');", + " }", + " }", + "};", + "", + "/**", + " * The density painted in the editor, stored as its difference with the", + " * density from the properties (the coverage and the edits), like the terrain", + " * sculpt data.", + " */", + "const maskCodec = {", + " /**", + " * @param {DensityMask} mask", + " * @param {DensityMask} baseMask", + " */", + " encode(mask, baseMask) {", + " const changes = new Int32Array(mask.values.length);", + " let hasChanges = false;", + " for (let index = 0; index < changes.length; index++) {", + " changes[index] = mask.values[index] - baseMask.values[index];", + " if (changes[index] !== 0) hasChanges = true;", + " }", + " if (!hasChanges) return '';", + " return MASK_FORMAT_VERSION + ';' + MASK_RESOLUTION + ';' + codec.encodeGrids([changes], mask.size);", + " },", + "", + " /**", + " * @param {string} text", + " * @param {DensityMask} baseMask", + " * @returns {DensityMask | null} null if the text is empty or not valid.", + " */", + " decode(text, baseMask) {", + " if (!text) return null;", + " const parts = text.split(';');", + " if (parts[0] !== MASK_FORMAT_VERSION || parseInt(parts[1], 10) !== MASK_RESOLUTION) return null;", + " try {", + " const mask = baseMask.clone();", + " const [changes] = codec.decodeGrids(parts[2], mask.size, 1);", + " for (let index = 0; index < changes.length; index++) {", + " // The base may have changed since the data was saved.", + " mask.values[index] = Math.min(Math.max(mask.values[index] + changes[index], 0), 255);", + " }", + " return mask;", + " } catch (error) {", + " console.error('The painted data of scattered models could not be read:', error);", + " return null;", + " }", + " },", + "};", + "", + "const fadeShaderDeclarations = `", + "uniform float scatterDrawDistance;", + "`;", + "// Instance matrices are applied after this: copies shrink towards their base.", + "const fadeShaderCode = `", + "#ifdef USE_INSTANCING", + "vec3 scatterCopyPosition = (modelMatrix * instanceMatrix * vec4(0.0, 0.0, 0.0, 1.0)).xyz;", + "transformed *= clamp(", + " (scatterDrawDistance - distance(scatterCopyPosition, cameraPosition)) / (scatterDrawDistance * ${FADE_PART}),", + " 0.0,", + " 1.0", + ");", + "#endif", + "`;", + "", + "const flipTriangles = (geometry) => {", + " const index = geometry.index;", + " for (let offset = 0; offset + 2 < index.count; offset += 3) {", + " const second = index.getX(offset + 1);", + " index.setX(offset + 1, index.getX(offset + 2));", + " index.setX(offset + 2, second);", + " }", + "};", + "", + "/**", + " * The parts of a model, ready to be drawn with instancing: each glTF", + " * primitive with its material. Geometries are moved so that the model base", + " * is at the origin, its height is 1 and it stands along Z (like 3D model", + " * objects with a 90° rotation around X).", + " * @param {THREE.Object3D} modelScene", + " * @param {object} uniforms Uniforms added to the materials.", + " * @returns {{parts: Array<{geometry: THREE.BufferGeometry, material: THREE.Material | THREE.Material[]}>, width: number, depth: number}}", + " */", + "const createModelParts = (modelScene, uniforms) => {", + " modelScene.updateMatrixWorld(true);", + " const rootInverse = modelScene.matrixWorld.clone().invert();", + " const standUp = new THREE.Matrix4().makeRotationX(Math.PI / 2);", + " /** @type {Array<{mesh: THREE.Mesh, matrix: THREE.Matrix4}>} */", + " const meshes = [];", + " const box = new THREE.Box3();", + " modelScene.traverse((node) => {", + " if (!node.isMesh || !node.geometry || !node.geometry.attributes.position) return;", + " const matrix = standUp.clone().multiply(rootInverse).multiply(node.matrixWorld);", + " if (!node.geometry.boundingBox) node.geometry.computeBoundingBox();", + " box.union(node.geometry.boundingBox.clone().applyMatrix4(matrix));", + " meshes.push({ mesh: node, matrix });", + " });", + " if (box.isEmpty()) return { parts: [], width: 0, depth: 0 };", + "", + " const height = Math.max(box.max.z - box.min.z, 0.000001);", + " // The Y axis of the scene goes down: the model is flipped, like 3D model objects.", + " const normalization = new THREE.Matrix4()", + " .makeScale(1 / height, -1 / height, 1 / height)", + " .multiply(", + " new THREE.Matrix4().makeTranslation(", + " -(box.min.x + box.max.x) / 2,", + " -(box.min.y + box.max.y) / 2,", + " -box.min.z", + " )", + " );", + " const parts = meshes.map(({ mesh, matrix }) => {", + " let geometry = mesh.geometry.clone();", + " if (!geometry.index) {", + " const indices = Array.from({ length: geometry.attributes.position.count }, (_, index) => index);", + " geometry.setIndex(indices);", + " }", + " geometry.applyMatrix4(normalization.clone().multiply(matrix));", + " // The flip turned the faces inside out.", + " flipTriangles(geometry);", + " geometry.computeBoundingSphere();", + " geometry.computeBoundingBox();", + " const patchMaterial = (originalMaterial) => {", + " const material = originalMaterial.clone();", + " // Like 3D model objects (\"Standard without metalness\"), which look better without an environment map.", + " if (material.metalness) material.metalness = 0;", + " material.onBeforeCompile = (shader) => {", + " Object.assign(shader.uniforms, uniforms);", + " shader.vertexShader = patchShaderCode(shader.vertexShader, [", + " ['#include ', '#include \\n' + fadeShaderDeclarations],", + " ['#include ', '#include \\n' + fadeShaderCode],", + " ], 'ScatteredModels');", + " };", + " material.customProgramCacheKey = () => 'ScatteredModels';", + " return material;", + " };", + " const material = Array.isArray(mesh.material) ? mesh.material.map(patchMaterial) : patchMaterial(mesh.material);", + " return { geometry, material };", + " });", + " return {", + " parts,", + " width: (box.max.x - box.min.x) / height,", + " depth: (box.max.y - box.min.y) / height,", + " };", + "};", + "", + "/**", + " * A copy of the model: its base position in the scene, its scale (the", + " * height of the copy) and the key of its cell in the grid of copies.", + " * @typedef {{x: number, y: number, z: number, angle: number, height: number, key: number}} Copy", + " */", + "", + "/**", + " * The scattered models of an object: copies of a 3D model placed on the", + " * grounds of the scene, drawn with GPU instancing in chunks that are hidden", + " * when far or off-screen.", + " */", + "class Scatter {", + " /** @param {gdjs.CustomRuntimeObject3D} object */", + " constructor(object) {", + " this.object = object;", + " this.group = new THREE.Group();", + " object.get3DRendererObject().add(this.group);", + " /** @type {Array<{firstCellI: number, firstCellJ: number, meshes: THREE.InstancedMesh[], copies: Copy[], center: THREE.Vector3, radius: number}>} */", + " this.chunks = [];", + " /**", + " * The grid of copies: one copy at most in each cell, cells grouped in chunks.", + " * @type {{spacing: number, cellsPerChunk: number, cellsX: number, cellsY: number} | null}", + " */", + " this.grid = null;", + " this.placement = new GroundPlacement(object.getRuntimeScene());", + " this.uniforms = {", + " // In 3D world units.", + " scatterDrawDistance: { value: 1 },", + " };", + " /** @type {{parts: Array<{geometry: THREE.BufferGeometry, material: THREE.Material | THREE.Material[]}>, width: number, depth: number}} */", + " this.model = { parts: [], width: 0, depth: 0 };", + " this.modelResourceName = '';", + " this.loadingModelResourceName = '';", + " /**", + " * Copies removed by events (for example, cut trees), by key.", + " * @type {Map}", + " */", + " this.removedCopies = new Map();", + " /** Increased each time copies change, for the collisions. */", + " this.version = 0;", + " this.isDisposed = false;", + " /** The last value of the painted data property read or saved. */", + " this.paintData = '';", + " /**", + " * Values saved by the editor tools: the editor sends them back with a", + " * hot-reload, possibly after newer ones were saved.", + " * @type {string[]}", + " */", + " this.savedPaintData = [];", + " this.maskSource = '';", + " /** @type {DensityMask} */", + " this.baseMask = new DensityMask(255);", + " /** @type {DensityMask} */", + " this.mask = this.baseMask;", + " this.changedSamples = new SampleRectangle();", + " this._stroke = { cellWidth: 1, cellHeight: 1, ax: 0, ay: 0, bx: 0, by: 0, radius: 0 };", + " this._groundAtCopy = { ground: null, z: 0 };", + " this._matrix = new THREE.Matrix4();", + " this._raycastSphere = new THREE.Sphere();", + " this._raycastRay = new THREE.Ray();", + " this._raycastBox = new THREE.Box3();", + " this._raycastInverseMatrix = new THREE.Matrix4();", + " this._raycastScale = new THREE.Vector3();", + " /** Placed on a copy to test the triangles of the model. */", + " this._raycastMesh = new THREE.Mesh();", + " /** @type {THREE.Intersection[]} */", + " this._raycastHits = [];", + " this._color = new THREE.Color();", + " this._tint = new THREE.Color();", + " this._cameraPosition = new THREE.Vector3();", + " this._chunkPosition = new THREE.Vector3();", + " /**", + " * Trunks, for the 3D physics behavior and navigation meshes. Always given,", + " * empty without trunk collisions: otherwise these behaviors would use the", + " * object box, covering the whole area.", + " * @type {gdjs.Surface}", + " */", + " this.surface = {", + " getVersion: () => this.version,", + " getChangedArea: () => null,", + " getTriangles: () => this._getTrunkTriangles(),", + " getHeightField: () => null,", + " };", + " /** The trunk collision properties, changing the surface. */", + " this.trunkKey = '';", + " this.loadMaskFromProperties();", + " this.updateFromProperties();", + " // Before the behaviors create their shapes.", + " this._updateSurface();", + " }", + "", + " _getMaskSource() {", + " return this.object._getCoverage() + '|' + this.object._getEdits();", + " }", + "", + " loadMaskFromProperties() {", + " const { object } = this;", + " this.maskSource = this._getMaskSource();", + " this.paintData = object._getPaintData();", + " this.baseMask = new DensityMask(object._getCoverage() === 'Painted' ? 0 : 255);", + " applyEdits(this.baseMask, object._getEdits());", + " this.mask = maskCodec.decode(this.paintData, this.baseMask) || this.baseMask.clone();", + " this.placement.placeAgain();", + " }", + "", + " onHotReloading() {", + " const paintData = this.object._getPaintData();", + " const savedIndex = this.savedPaintData.indexOf(paintData);", + " if (savedIndex !== -1 && this._getMaskSource() === this.maskSource) {", + " // The object already has this data, or newer changes.", + " this.savedPaintData.splice(0, savedIndex + 1);", + " } else if (paintData !== this.paintData || this._getMaskSource() !== this.maskSource) {", + " this.savedPaintData.length = 0;", + " this.loadMaskFromProperties();", + " }", + " this.updateFromProperties();", + " }", + "", + " /** @returns {string} The data to save in the painted data property. */", + " save() {", + " this.paintData = maskCodec.encode(this.mask, this.baseMask);", + " this.savedPaintData.push(this.paintData);", + " return this.paintData;", + " }", + "", + " updateFromProperties() {", + " const { object } = this;", + " const modelResourceName = object._getModel();", + " if (modelResourceName !== this.modelResourceName) {", + " this.modelResourceName = modelResourceName;", + " this._loadModel();", + " }", + " this.placement.placeAgain();", + " }", + "", + " _disposeModel() {", + " for (const part of this.model.parts) {", + " part.geometry.dispose();", + " for (const material of Array.isArray(part.material) ? part.material : [part.material]) {", + " material.dispose();", + " }", + " }", + " this.model = { parts: [], width: 0, depth: 0 };", + " }", + "", + " _loadModel() {", + " this._disposeChunks(this.chunks);", + " this._disposeModel();", + " const resourceName = this.modelResourceName;", + " if (!resourceName) return;", + " const game = this.object.getRuntimeScene().getGame();", + " const modelManager = game.getModel3DManager();", + " const gltf = modelManager.getModel(resourceName);", + " if (gltf === modelManager._invalidModel) {", + " // Not loaded yet, for example a model just chosen in the editor.", + " if (this.loadingModelResourceName === resourceName) return;", + " this.loadingModelResourceName = resourceName;", + " game.getResourceLoader()", + " .loadResource(resourceName)", + " .then(() => {", + " if (this.isDisposed || this.modelResourceName !== resourceName) return;", + " this.loadingModelResourceName = '';", + " if (modelManager.getModel(resourceName) !== modelManager._invalidModel) {", + " this._loadModel();", + " this.placement.placeAgain();", + " }", + " })", + " .catch(() => {});", + " return;", + " }", + " this.model = createModelParts(gltf.scene, this.uniforms);", + " }", + "", + " _getPlacementKey() {", + " const { object } = this;", + " return [", + " object.getX(), object.getY(), object.getZ(), object.getWidth(), object.getHeight(), object.getDepth(),", + " object._getDensity(), object._getSize(), object._getMinScale(), object._getMaxScale(),", + " object._getRandomRotation(), object._getRandomTint(), object._getGroundLayer(), object._getMaxSlope(),", + " object._getMinGroundZ(), object._getMaxGroundZ(), object._getSeed(), object._getDrawDistance(),", + " object._getCastShadow(), object._getReceiveShadow(), this.model.parts.length,", + " ].join('|');", + " }", + "", + " _getChunkSize() {", + " return Math.min(Math.max(this.object._getDrawDistance() / 4, MIN_CHUNK_SIZE), MAX_CHUNK_SIZE);", + " }", + "", + " /** @returns {number} The distance between copies (one copy per cell of this size at most). */", + " _getSpacing() {", + " const { object } = this;", + " const density = Math.max(object._getDensity(), 0);", + " if (density <= 0) return 0;", + " const spacing = 100 / Math.sqrt(density);", + " // Too many cells would freeze the game: the density is lowered.", + " const cellCount = (object.getWidth() / spacing) * (object.getHeight() / spacing);", + " return cellCount > MAX_COPIES ? spacing * Math.sqrt(cellCount / MAX_COPIES) : spacing;", + " }", + "", + " /**", + " * Find the copy of a cell of the grid, if there is one.", + " * @returns {Copy | null}", + " */", + " _placeCopy(cellI, cellJ, spacing, grounds) {", + " const { object } = this;", + " const width = object.getWidth();", + " const height = object.getHeight();", + " const nextRandom = createRandom(hashCell(Math.floor(object._getSeed()), cellI, cellJ));", + " // Always drawn in the same order, so that a copy stays the same when properties change.", + " const x = (cellI + nextRandom()) * spacing;", + " const y = (cellJ + nextRandom()) * spacing;", + " const presence = nextRandom();", + " const growth = nextRandom();", + " const scaleFactor = nextRandom();", + " const angle = nextRandom() * Math.PI * 2;", + " if (x > width || y > height) return null;", + " if (presence >= this.mask.sample(x / width, y / height)) return null;", + " const key = cellJ * 65536 + cellI;", + " if (this.removedCopies.has(key)) return null;", + "", + " const sceneX = object.getX() + x;", + " const sceneY = object.getY() + y;", + " const { ground, z } = findHighestGround(grounds, sceneX, sceneY, this._groundAtCopy);", + " const groundZ = ground ? z : object.getZ();", + " if (groundZ < object._getMinGroundZ() || groundZ > object._getMaxGroundZ()) return null;", + " const copyHeight =", + " Math.max(object._getSize(), 0) *", + " (object._getMinScale() + (object._getMaxScale() - object._getMinScale()) * scaleFactor);", + " if (ground) {", + " if (ground.getSlopeAt(sceneX, sceneY) > object._getMaxSlope()) return null;", + " const groundLayer = parseInt(object._getGroundLayer(), 10) || 0;", + " if (groundLayer > 0) {", + " if (growth > ground.getLayerWeightAt(sceneX, sceneY, groundLayer)) return null;", + " // Copies don't stand at the border of their layer, so that trees don't cover roads.", + " const margin = (copyHeight * Math.max(this.model.width, this.model.depth)) / 4;", + " for (const [offsetX, offsetY] of [[margin, 0], [-margin, 0], [0, margin], [0, -margin]]) {", + " if (ground.getLayerWeightAt(sceneX + offsetX, sceneY + offsetY, groundLayer) < 0.5) return null;", + " }", + " }", + " }", + " return {", + " x: sceneX,", + " y: sceneY,", + " z: groundZ,", + " angle: object._getRandomRotation() ? angle : 0,", + " height: copyHeight,", + " key,", + " };", + " }", + "", + " _disposeChunks(chunks) {", + " for (const chunk of chunks) {", + " for (const mesh of chunk.meshes) {", + " mesh.removeFromParent();", + " // Geometries and materials are the ones of the model.", + " mesh.dispose();", + " }", + " }", + " if (chunks === this.chunks) this.chunks = [];", + " }", + "", + " /** Place the copies again, for example after the ground or the object changed. */", + " rebuild() {", + " this._disposeChunks(this.chunks);", + " this.version++;", + " const { object } = this;", + " const spacing = this._getSpacing();", + " if (spacing <= 0 || this.model.parts.length === 0) {", + " this.grid = null;", + " return;", + " }", + " const cellsPerChunk = Math.max(Math.round(this._getChunkSize() / spacing), 1);", + " const grid = {", + " spacing,", + " cellsPerChunk,", + " cellsX: Math.ceil(object.getWidth() / spacing),", + " cellsY: Math.ceil(object.getHeight() / spacing),", + " };", + " this.grid = grid;", + " for (let firstCellJ = 0; firstCellJ < grid.cellsY; firstCellJ += cellsPerChunk) {", + " for (let firstCellI = 0; firstCellI < grid.cellsX; firstCellI += cellsPerChunk) {", + " this._createChunk(firstCellI, firstCellJ);", + " }", + " }", + " }", + "", + " /**", + " * Place again the copies of the chunks touching a rectangle of the scene,", + " * for example after painting.", + " */", + " _rebuildArea(minX, minY, maxX, maxY) {", + " const { grid, object } = this;", + " if (!grid) return;", + " const chunkSize = grid.spacing * grid.cellsPerChunk;", + " const firstChunkI = Math.max(Math.floor((minX - object.getX()) / chunkSize), 0);", + " const firstChunkJ = Math.max(Math.floor((minY - object.getY()) / chunkSize), 0);", + " const lastChunkI = Math.min(Math.floor((maxX - object.getX()) / chunkSize), Math.ceil(grid.cellsX / grid.cellsPerChunk) - 1);", + " const lastChunkJ = Math.min(Math.floor((maxY - object.getY()) / chunkSize), Math.ceil(grid.cellsY / grid.cellsPerChunk) - 1);", + " if (firstChunkI > lastChunkI || firstChunkJ > lastChunkJ) return;", + " const isInArea = (chunk) => {", + " const chunkI = chunk.firstCellI / grid.cellsPerChunk;", + " const chunkJ = chunk.firstCellJ / grid.cellsPerChunk;", + " return chunkI >= firstChunkI && chunkI <= lastChunkI && chunkJ >= firstChunkJ && chunkJ <= lastChunkJ;", + " };", + " this._disposeChunks(this.chunks.filter(isInArea));", + " this.chunks = this.chunks.filter((chunk) => !isInArea(chunk));", + " for (let chunkJ = firstChunkJ; chunkJ <= lastChunkJ; chunkJ++) {", + " for (let chunkI = firstChunkI; chunkI <= lastChunkI; chunkI++) {", + " this._createChunk(chunkI * grid.cellsPerChunk, chunkJ * grid.cellsPerChunk);", + " }", + " }", + " this.version++;", + " }", + "", + " _createChunk(firstCellI, firstCellJ) {", + " const { grid } = this;", + " const grounds = Array.from(this.placement.grounds);", + " /** @type {Copy[]} */", + " const copies = [];", + " for (let cellJ = firstCellJ; cellJ < Math.min(firstCellJ + grid.cellsPerChunk, grid.cellsY); cellJ++) {", + " for (let cellI = firstCellI; cellI < Math.min(firstCellI + grid.cellsPerChunk, grid.cellsX); cellI++) {", + " const copy = this._placeCopy(cellI, cellJ, grid.spacing, grounds);", + " if (copy) copies.push(copy);", + " }", + " }", + " if (copies.length === 0) return;", + "", + " const { object } = this;", + " // Copies are placed in scene units, and the object group is scaled", + " // from its inner area: they are scaled back to keep their proportions.", + " const scaleX = Math.max(object.getWidth() / AREA_SIZE, 0.0001);", + " const scaleY = Math.max(object.getHeight() / AREA_SIZE, 0.0001);", + " const scaleZ = Math.max(object.getDepth() / AREA_DEPTH, 0.0001);", + " const matrices = new Float32Array(copies.length * 16);", + " const tintColor = gdjs.rgbOrHexStringToNumber(object._getRandomTint());", + " const hasTint = tintColor !== 0xffffff;", + " const colors = hasTint ? new Float32Array(copies.length * 3) : null;", + " this._tint.setHex(tintColor);", + " const min = new THREE.Vector3(Infinity, Infinity, Infinity);", + " const max = new THREE.Vector3(-Infinity, -Infinity, -Infinity);", + " copies.forEach((copy, index) => {", + " const cos = Math.cos(copy.angle) * copy.height;", + " const sin = Math.sin(copy.angle) * copy.height;", + " const localX = (copy.x - object.getX()) / scaleX;", + " const localY = (copy.y - object.getY()) / scaleY;", + " const localZ = (copy.z - object.getZ()) / scaleZ;", + " this._matrix.set(", + " cos / scaleX, -sin / scaleX, 0, localX,", + " sin / scaleY, cos / scaleY, 0, localY,", + " 0, 0, copy.height / scaleZ, localZ,", + " 0, 0, 0, 1", + " );", + " this._matrix.toArray(matrices, index * 16);", + " if (colors) {", + " // The tint of each copy is between white and the tint color.", + " this._color.setRGB(1, 1, 1).lerp(this._tint, createRandom(copy.key + 1)());", + " this._color.toArray(colors, index * 3);", + " }", + " const reach = (copy.height * Math.max(this.model.width, this.model.depth)) / 2;", + " min.min(this._chunkPosition.set(copy.x - reach, copy.y - reach, copy.z));", + " max.max(this._chunkPosition.set(copy.x + reach, copy.y + reach, copy.z + copy.height));", + " });", + " const instanceMatrix = new THREE.InstancedBufferAttribute(matrices, 16);", + " const instanceColor = colors ? new THREE.InstancedBufferAttribute(colors, 3) : null;", + " const castShadow = object._getCastShadow();", + " const receiveShadow = object._getReceiveShadow();", + " const meshes = this.model.parts.map((part) => {", + " const mesh = new THREE.InstancedMesh(part.geometry, part.material, 0);", + " // All the parts of the model share the positions of the copies.", + " mesh.instanceMatrix = instanceMatrix;", + " mesh.instanceColor = instanceColor;", + " mesh.count = copies.length;", + " mesh.computeBoundingSphere();", + " mesh.castShadow = castShadow;", + " mesh.receiveShadow = receiveShadow;", + " // The first mesh tests all the parts of the copies.", + " mesh.raycast = () => {};", + " this.group.add(mesh);", + " return mesh;", + " });", + " const center = new THREE.Vector3(", + " ((min.x + max.x) / 2 - object.getX()) / scaleX,", + " ((min.y + max.y) / 2 - object.getY()) / scaleY,", + " ((min.z + max.z) / 2 - object.getZ()) / scaleZ", + " );", + " const chunk = { firstCellI, firstCellJ, meshes, copies, center, radius: min.distanceTo(max) / 2 };", + " // Used by the scene editor to select the object when a copy is clicked.", + " meshes[0].raycast = (raycaster, intersects) => this._raycastChunk(chunk, raycaster, intersects);", + " this.chunks.push(chunk);", + " }", + "", + " /**", + " * Add the hits of a ray on the copies of a chunk, like they are drawn. There", + " * can be thousands of copies: only the ones whose box is crossed by the ray", + " * are tested with the triangles of the model.", + " * @param {{meshes: THREE.InstancedMesh[], copies: Copy[], center: THREE.Vector3, radius: number}} chunk", + " * @param {THREE.Raycaster} raycaster", + " * @param {THREE.Intersection[]} intersects", + " */", + " _raycastChunk(chunk, raycaster, intersects) {", + " const { object, group } = this;", + " this._raycastSphere.center.copy(chunk.center).applyMatrix4(group.matrixWorld);", + " this._raycastSphere.radius = chunk.radius / getWorldScale(object);", + " if (!raycaster.ray.intersectsSphere(this._raycastSphere)) return;", + "", + " // The ray in the scene coordinates, like the copies.", + " const scale = this._raycastScale.set(", + " Math.max(object.getWidth() / AREA_SIZE, 0.0001),", + " Math.max(object.getHeight() / AREA_SIZE, 0.0001),", + " Math.max(object.getDepth() / AREA_DEPTH, 0.0001)", + " );", + " const ray = this._raycastRay.copy(raycaster.ray).applyMatrix4(this._raycastInverseMatrix.copy(group.matrixWorld).invert());", + " ray.origin.multiply(scale).add(this._chunkPosition.set(object.getX(), object.getY(), object.getZ()));", + " ray.direction.multiply(scale);", + "", + " const reachPerHeight = Math.max(this.model.width, this.model.depth) / 2;", + " const instanceMatrices = chunk.meshes[0].instanceMatrix.array;", + " chunk.copies.forEach((copy, index) => {", + " const reach = copy.height * reachPerHeight;", + " this._raycastBox.min.set(copy.x - reach, copy.y - reach, copy.z);", + " this._raycastBox.max.set(copy.x + reach, copy.y + reach, copy.z + copy.height);", + " if (!ray.intersectsBox(this._raycastBox)) return;", + " this._raycastMesh.matrixWorld.fromArray(instanceMatrices, index * 16).premultiply(group.matrixWorld);", + " for (const mesh of chunk.meshes) {", + " this._raycastMesh.geometry = mesh.geometry;", + " this._raycastMesh.material = mesh.material;", + " this._raycastHits.length = 0;", + " this._raycastMesh.raycast(raycaster, this._raycastHits);", + " for (const hit of this._raycastHits) {", + " hit.object = mesh;", + " hit.instanceId = index;", + " intersects.push(hit);", + " }", + " }", + " });", + " }", + "", + " /** @param {THREE.Camera | null} camera */", + " _hideFarChunks(camera) {", + " if (!camera) return;", + " const worldScale = getWorldScale(this.object);", + " const drawDistance = Math.max(this.object._getDrawDistance(), 1) / worldScale;", + " this.uniforms.scatterDrawDistance.value = drawDistance;", + " this._cameraPosition.setFromMatrixPosition(camera.matrixWorld);", + " for (const chunk of this.chunks) {", + " const distance = this._chunkPosition", + " .copy(chunk.center)", + " .applyMatrix4(this.group.matrixWorld)", + " .distanceTo(this._cameraPosition);", + " const isVisible = distance - chunk.radius / worldScale < drawDistance;", + " for (const mesh of chunk.meshes) mesh.visible = isVisible;", + " }", + " }", + "", + " /**", + " * @param {number} elapsedSeconds", + " * @param {THREE.Camera | null} camera", + " */", + " update(elapsedSeconds, camera) {", + " if (this.placement.shouldPlaceAgain(elapsedSeconds, this._getPlacementKey())) {", + " this.rebuild();", + " }", + " if (!this.changedSamples.isEmpty()) {", + " const { object } = this;", + " const sampleWidth = object.getWidth() / MASK_RESOLUTION;", + " const sampleHeight = object.getHeight() / MASK_RESOLUTION;", + " const { minI, minJ, maxI, maxJ } = this.changedSamples;", + " // Copies between samples use the density of the samples around them.", + " this._rebuildArea(", + " object.getX() + (minI - 1) * sampleWidth,", + " object.getY() + (minJ - 1) * sampleHeight,", + " object.getX() + (maxI + 1) * sampleWidth,", + " object.getY() + (maxJ + 1) * sampleHeight", + " );", + " this.changedSamples.clear();", + " }", + " // The object transformation is applied just before rendering, it's needed now.", + " this.object.getRenderer().ensureUpToDate();", + " this.group.updateWorldMatrix(true, false);", + " this._hideFarChunks(camera);", + " this._updateSurface();", + " }", + "", + " _updateSurface() {", + " const { object } = this;", + " if (typeof object.setSurface !== 'function') return;", + " const trunkKey = object._getTrunkCollisions() + '|' + object._getTrunkDiameter();", + " if (trunkKey !== this.trunkKey) {", + " this.trunkKey = trunkKey;", + " this.version++;", + " }", + " if (object.getSurface() !== this.surface) object.setSurface(this.surface);", + " }", + "", + " /** @returns {Copy[]} */", + " getCopies() {", + " const copies = [];", + " for (const chunk of this.chunks) copies.push(...chunk.copies);", + " return copies;", + " }", + "", + " /**", + " * @returns {Copy | null} The copy whose base is the nearest to a position (on X and Y), if any.", + " */", + " findNearestCopy(x, y) {", + " let nearestCopy = null;", + " let nearestDistanceSquared = Infinity;", + " for (const chunk of this.chunks) {", + " for (const copy of chunk.copies) {", + " const distanceSquared = (copy.x - x) * (copy.x - x) + (copy.y - y) * (copy.y - y);", + " if (distanceSquared < nearestDistanceSquared) {", + " nearestDistanceSquared = distanceSquared;", + " nearestCopy = copy;", + " }", + " }", + " }", + " return nearestCopy;", + " }", + "", + " getCopyCount() {", + " let count = 0;", + " for (const chunk of this.chunks) count += chunk.copies.length;", + " return count;", + " }", + "", + " /** Remove the copies whose base is in a circle (they come back when the scene is restarted). */", + " removeCopies(x, y, radius) {", + " let hasRemovedCopies = false;", + " for (const chunk of this.chunks) {", + " for (const copy of chunk.copies) {", + " if ((copy.x - x) * (copy.x - x) + (copy.y - y) * (copy.y - y) <= radius * radius) {", + " this.removedCopies.set(copy.key, copy);", + " hasRemovedCopies = true;", + " }", + " }", + " }", + " if (hasRemovedCopies) this._rebuildArea(x - radius, y - radius, x + radius, y + radius);", + " }", + "", + " /** Paint the full density around a position, and bring back the copies removed there. */", + " addCopies(x, y, radius) {", + " for (const [key, copy] of this.removedCopies) {", + " if ((copy.x - x) * (copy.x - x) + (copy.y - y) * (copy.y - y) <= radius * radius) {", + " this.removedCopies.delete(key);", + " }", + " }", + " this.paint(x, y, x, y, radius, 1, 1);", + " }", + "", + " _getStroke(x1, y1, x2, y2, radius) {", + " const { object } = this;", + " const stroke = this._stroke;", + " stroke.cellWidth = object.getWidth() / MASK_RESOLUTION;", + " stroke.cellHeight = object.getHeight() / MASK_RESOLUTION;", + " stroke.ax = x1 - object.getX();", + " stroke.ay = y1 - object.getY();", + " stroke.bx = x2 - object.getX();", + " stroke.by = y2 - object.getY();", + " stroke.radius = Math.max(radius, 0);", + " return stroke;", + " }", + "", + " /**", + " * Paint the density around a segment (copies are placed again at the next frame).", + " * @param {number} density From 0 (erase) to 1 (as dense as the Density property).", + " * @param {number} strength From 0 to 1.", + " */", + " paint(x1, y1, x2, y2, radius, density, strength) {", + " const changedSamples = new SampleRectangle();", + " this.mask.paint(this._getStroke(x1, y1, x2, y2, radius), density, strength, changedSamples);", + " this.changedSamples.add(changedSamples);", + " }", + "", + " /** @param {DensityMask} mask */", + " setMask(mask) {", + " this.mask = mask;", + " this.rebuild();", + " }", + "", + " /**", + " * Prisms around the trunks, from 0 to 1 in the object box (see `gdjs.Surface`).", + " * @returns {gdjs.SurfaceTriangles}", + " */", + " _getTrunkTriangles() {", + " const { object } = this;", + " const copies = object._getTrunkCollisions() ? this.getCopies() : [];", + " const verticesPerCopy = TRUNK_SIDES * 2 + 1;", + " const positions = new Float32Array(copies.length * verticesPerCopy * 3);", + " const indices = new Uint32Array(copies.length * TRUNK_SIDES * 3 * 3);", + " const width = object.getWidth() || 1;", + " const height = object.getHeight() || 1;", + " const depth = object.getDepth() || 1;", + " let indexOffset = 0;", + " copies.forEach((copy, copyIndex) => {", + " const radius = (Math.max(object._getTrunkDiameter(), 0) / 2) * (copy.height / Math.max(object._getSize(), 1));", + " const top = copy.z + copy.height * 0.8;", + " const firstVertex = copyIndex * verticesPerCopy;", + " const setVertex = (vertex, x, y, z) => {", + " positions[vertex * 3] = (x - object.getX()) / width;", + " positions[vertex * 3 + 1] = (y - object.getY()) / height;", + " positions[vertex * 3 + 2] = (z - object.getZ()) / depth;", + " };", + " for (let side = 0; side < TRUNK_SIDES; side++) {", + " const angle = ((side + 0.5) / TRUNK_SIDES) * Math.PI * 2;", + " const x = copy.x + Math.cos(angle) * radius;", + " const y = copy.y + Math.sin(angle) * radius;", + " // Starting a bit under the ground, which can be sloped.", + " setVertex(firstVertex + side * 2, x, y, copy.z - radius);", + " setVertex(firstVertex + side * 2 + 1, x, y, top);", + " }", + " const center = firstVertex + TRUNK_SIDES * 2;", + " setVertex(center, copy.x, copy.y, top);", + " for (let side = 0; side < TRUNK_SIDES; side++) {", + " const bottom = firstVertex + side * 2;", + " const nextBottom = firstVertex + ((side + 1) % TRUNK_SIDES) * 2;", + " // Triangles face out of the prism (vertices at odd indices are at the top).", + " indices.set([bottom, nextBottom, bottom + 1, nextBottom, nextBottom + 1, bottom + 1], indexOffset);", + " indices.set([center, bottom + 1, nextBottom + 1], indexOffset + 6);", + " indexOffset += 9;", + " }", + " });", + " if (copies.length === 0) {", + " // Without triangles, the physics engine and navigation meshes would use", + " // the object box: a tiny triangle under the object is used instead.", + " return { positions: new Float32Array([0, 0, -1, 0.0001, 0, -1, 0, 0.0001, -1]), indices: new Uint32Array([0, 2, 1]) };", + " }", + " return { positions, indices };", + " }", + "", + " dispose() {", + " this.isDisposed = true;", + " if (typeof this.object.setSurface === 'function' && this.object.getSurface() === this.surface) {", + " this.object.setSurface(null);", + " }", + " this._disposeChunks(this.chunks);", + " this._disposeModel();", + " this.group.removeFromParent();", + " }", + "}", + "", + "const scatterToolIcons = {", + " Add: svgIcon(", + " ''", + " ),", + " Erase: svgIcon(", + " ''", + " ),", + "};", + "", + "/**", + " * The brushes shown in the scene editor when scattered models are selected,", + " * to add or erase copies. Without the needed editor functions (older GDevelop", + " * versions), nothing is shown: copies can still be painted with events.", + " */", + "class ScatterEditorTools {", + " constructor() {", + " /** '' when no brush is used. */", + " this.toolName = '';", + " this.sizePercent = 30;", + " this.strengthPercent = 50;", + " /** @type {Scatter | null} */", + " this.scatter = null;", + " /** @type {gdjs.InGameEditor | null} */", + " this.editor = null;", + " this.isStroking = false;", + " this.lastStrokeTime = 0;", + " /** @type {Array<{mask: DensityMask, baseMask: DensityMask}>} */", + " this.undoSteps = [];", + " this.raycaster = new THREE.Raycaster();", + " this.ndc = new THREE.Vector2();", + " this._groundAtCursor = { ground: null, z: 0 };", + " /** A circle following the ground under the cursor. */", + " this.brushRing = new THREE.LineLoop(", + " new THREE.BufferGeometry().setAttribute('position', new THREE.BufferAttribute(new Float32Array(64 * 3), 3)),", + " new THREE.LineBasicMaterial({ color: 0xffffff, depthTest: false, transparent: true })", + " );", + " this.brushRing.renderOrder = 1000;", + " this.brushRing.frustumCulled = false;", + " this.brushRing.raycast = () => {};", + " }", + "", + " /** @param {gdjs.InGameEditor} editor */", + " update(editor) {", + " if (typeof editor.showToolbar !== 'function') return;", + " this.editor = editor;", + " const selectedObjects = editor.getSelectedObjects();", + " const scatter =", + " selectedObjects.length === 1 && selectedObjects[0].__terrainScatter ? selectedObjects[0].__terrainScatter : null;", + " if (scatter !== this.scatter) {", + " this._endStroke();", + " this.scatter = scatter;", + " this.undoSteps.length = 0;", + " }", + " if (scatter) editor.showToolbar('ScatteredModels', this._getToolbarItems());", + " const game = editor.getRuntimeGame();", + " if (!scatter || !this.toolName) {", + " this.brushRing.removeFromParent();", + " return;", + " }", + " const inputManager = game.getInputManager();", + " if (inputManager.wasKeyJustPressed(gdjs.evtTools.input.keysNameToCode.Escape)) {", + " this._endStroke();", + " this.toolName = '';", + " this.brushRing.removeFromParent();", + " return;", + " }", + " editor.captureLeftMouseButton();", + " const hit = this._getPositionUnderCursor(scatter, game);", + " const radius = this._getRadius();", + " this._showBrushRing(scatter, hit, radius);", + "", + " const isPressedForTool =", + " typeof editor.isCapturedLeftMouseButtonPressed === 'function'", + " ? editor.isCapturedLeftMouseButtonPressed()", + " : inputManager.isMouseButtonPressed(0);", + " // The button is released outside the canvas when the mouse is on a toolbar.", + " const isPressed = isPressedForTool && inputManager.isMouseInsideCanvas();", + " if (isPressed && hit && !this.isStroking) {", + " this._startStroke(scatter);", + " }", + " if (this.isStroking && isPressed && hit) {", + " this._applyBrush(scatter, hit, radius);", + " }", + " if (!isPressed) {", + " this._endStroke();", + " }", + " }", + "", + " _getRadius() {", + " const { object } = this.scatter;", + " const maxRadius = Math.max(object.getWidth(), object.getHeight()) / 4;", + " return Math.max(maxRadius * Math.pow(this.sizePercent / 100, 2), 10);", + " }", + "", + " /**", + " * @returns {{x: number, y: number} | null} The position of the scene under the cursor: on the", + " * highest ground hit, or on the plane at the object Z.", + " */", + " _getPositionUnderCursor(scatter, game) {", + " let hit = null;", + " for (const ground of getGrounds(scatter.object.getRuntimeScene())) {", + " if (typeof ground.raycastFromCursor !== 'function') continue;", + " const localHit = ground.raycastFromCursor(game, this.raycaster, this.ndc);", + " if (!localHit) continue;", + " const sceneHit = ground.toScenePosition(localHit);", + " if (!hit || sceneHit.z > hit.z) hit = sceneHit;", + " }", + " if (hit) return hit;", + "", + " const { object } = scatter;", + " const layer = object.getInstanceContainer().getLayer(object.getLayer());", + " const camera = layer.getRenderer().getThreeCamera();", + " if (!camera) return null;", + " const inputManager = game.getInputManager();", + " this.ndc.set(", + " (inputManager.getCursorX() / game.getGameResolutionWidth()) * 2 - 1,", + " -(inputManager.getCursorY() / game.getGameResolutionHeight()) * 2 + 1", + " );", + " this.raycaster.setFromCamera(this.ndc, camera);", + " scatter.group.updateWorldMatrix(true, false);", + " const ray = this.raycaster.ray.clone().applyMatrix4(scatter.group.matrixWorld.clone().invert());", + " const point = ray.intersectPlane(new THREE.Plane(new THREE.Vector3(0, 0, 1), 0), new THREE.Vector3());", + " if (!point) return null;", + " return {", + " x: object.getX() + (point.x / AREA_SIZE) * object.getWidth(),", + " y: object.getY() + (point.y / AREA_SIZE) * object.getHeight(),", + " };", + " }", + "", + " _showBrushRing(scatter, hit, radius) {", + " if (!hit) {", + " this.brushRing.removeFromParent();", + " return;", + " }", + " const { object } = scatter;", + " if (this.brushRing.parent !== scatter.group) scatter.group.add(this.brushRing);", + " const grounds = getGrounds(object.getRuntimeScene());", + " const positions = this.brushRing.geometry.attributes.position;", + " for (let index = 0; index < positions.count; index++) {", + " const angle = (index / positions.count) * Math.PI * 2;", + " const x = hit.x + Math.cos(angle) * radius;", + " const y = hit.y + Math.sin(angle) * radius;", + " const { ground, z } = findHighestGround(grounds, x, y, this._groundAtCursor);", + " positions.setXYZ(", + " index,", + " ((x - object.getX()) / object.getWidth()) * AREA_SIZE,", + " ((y - object.getY()) / object.getHeight()) * AREA_SIZE,", + " (((ground ? z : object.getZ()) + 2 - object.getZ()) / Math.max(object.getDepth(), 0.0001)) * AREA_DEPTH", + " );", + " }", + " positions.needsUpdate = true;", + " }", + "", + " _startStroke(scatter) {", + " this.isStroking = true;", + " this.lastStrokeTime = performance.now();", + " this.undoSteps.push({ mask: scatter.mask.clone(), baseMask: scatter.baseMask });", + " while (this.undoSteps.length > 30) this.undoSteps.shift();", + " }", + "", + " _endStroke() {", + " if (!this.isStroking) return;", + " this.isStroking = false;", + " this._save();", + " }", + "", + " _applyBrush(scatter, hit, radius) {", + " const now = performance.now();", + " const elapsedSeconds = Math.min((now - this.lastStrokeTime) / 1000, 0.1);", + " this.lastStrokeTime = now;", + " const strength = (this.strengthPercent / 100) * elapsedSeconds * 8;", + " scatter.paint(hit.x, hit.y, hit.x, hit.y, radius, this.toolName === 'Add' ? 1 : 0, strength);", + " }", + "", + " _save() {", + " const { scatter, editor } = this;", + " if (!scatter || !editor) return;", + " editor.updateObjectProperties(scatter.object.getName(), { PaintData: scatter.save() });", + " }", + "", + " _undo() {", + " const undoStep = this.undoSteps.pop();", + " // The coverage or the edits may have changed since the stroke.", + " if (!this.scatter || !undoStep || undoStep.baseMask !== this.scatter.baseMask) {", + " this.undoSteps.length = 0;", + " return;", + " }", + " this.scatter.setMask(undoStep.mask);", + " this._save();", + " }", + "", + " /** @returns {Array} */", + " _getToolbarItems() {", + " const toolButton = (toolName, tooltip) => ({", + " type: 'button',", + " id: toolName,", + " tooltip,", + " iconUrl: scatterToolIcons[toolName],", + " isActive: this.toolName === toolName,", + " onClick: () => (this.toolName = this.toolName === toolName ? '' : toolName),", + " });", + " return [", + " toolButton('Add', 'Add copies (drag on the ground)'),", + " toolButton('Erase', 'Erase copies'),", + " { type: 'divider', id: 'BrushDivider' },", + " {", + " type: 'slider',", + " id: 'Size',", + " tooltip: 'Brush size',", + " iconUrl: toolIcons.Size,", + " min: 1,", + " max: 100,", + " value: this.sizePercent,", + " onChange: (value) => (this.sizePercent = value),", + " },", + " {", + " type: 'slider',", + " id: 'Strength',", + " tooltip: 'Brush strength',", + " iconUrl: toolIcons.Strength,", + " min: 5,", + " max: 100,", + " value: this.strengthPercent,", + " onChange: (value) => (this.strengthPercent = value),", + " },", + " {", + " type: 'button',", + " id: 'Undo',", + " tooltip: 'Undo the last stroke on these scattered models',", + " iconUrl: toolIcons.Undo,", + " onClick: () => this._undo(),", + " },", + " ];", + " }", + "}", + "", + "const editorTools = new ScatterEditorTools();", + "if (gdjs.registerInGameEditorPostStepCallback) {", + " gdjs.registerInGameEditorPostStepCallback((editor) => editorTools.update(editor));", + "}", + "", + "gdjs.__terrainScatterExtension = {", + " Scatter,", + " // Exposed for tests.", + " DensityMask,", + " maskCodec,", + " applyEdits,", + "};", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [], + "description": "Define scattered models helper classes JavaScript code.", + "private": true + } + ], + "eventsBasedBehaviors": [], + "eventsBasedObjects": [ + { + "areaMaxX": 4096, + "areaMaxY": 4096, + "areaMaxZ": 600, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "defaultName": "Terrain", + "assetStoreTag": "3d terrains", + "description": "A ground with hills, mountains or valleys that can be sculpted and painted in the scene editor, or changed with events. Add the 3D physics behavior to it to make objects walk or roll on it.", + "fullName": "3D terrain", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXRlcnJhaW4iIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTQsNkwxMC4yNSwxMUwxMy4xLDE0LjhMMTEuNSwxNkM5LjgxLDEzLjc1IDcsMTAgNywxMEwxLDE4SDIzTDE0LDZaIiAvPjwvc3ZnPg==", + "is3D": true, + "isUsingLegacyInstancesRenderer": true, + "name": "Terrain3D", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/10e0a26c0d500830dfe23b94138beb0ef61607ba62bca05a5aa5c849777f1b06_terrain.svg", + "objects": [ + { + "assetStoreId": "", + "name": "AreaPlaceholder", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 4096, + "height": 4096, + "depth": 600, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "Basic", + "tint": "255;255;255", + "frontFaceResourceName": "", + "frontFaceVisible": false, + "frontFaceResourceRepeat": false, + "backFaceResourceName": "", + "backFaceVisible": false, + "backFaceResourceRepeat": false, + "leftFaceResourceName": "", + "leftFaceVisible": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceName": "", + "rightFaceVisible": false, + "rightFaceResourceRepeat": false, + "topFaceResourceName": "", + "topFaceVisible": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceName": "", + "bottomFaceVisible": false, + "bottomFaceResourceRepeat": false + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "AreaPlaceholder" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [ + { + "angle": 0, + "customSize": true, + "width": 4096, + "height": 4096, + "depth": 600, + "keepRatio": true, + "layer": "", + "name": "AreaPlaceholder", + "persistentUuid": "5d7a8b1e-3c2f-4e6a-9b1d-004096004096", + "x": 0, + "y": 0, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": {}, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Terrain3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "for (const child of object.getChildrenContainer().getAdhocListOfAllInstances()) {", + " child.deleteFromScene();", + "}", + "if (object.__terrain3D) object.__terrain3D.dispose();", + "object.__terrain3D = new gdjs.__terrain3DExtension.Terrain(object);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDestroy", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrain3D.dispose();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrain3D.update();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrain3D.onHotReloading();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + } + ], + "objectGroups": [] + }, + { + "fullName": "Raise or lower the ground", + "functionType": "Action", + "name": "RaiseGround", + "sentence": "Raise the ground of _PARAM0_ by _PARAM4_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const height = eventsFunctionContext.getArgument(\"Height\");", + "object.__terrain3D.raise(x, y, x, y, radius, height);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Height to add (negative to lower the ground)", + "name": "Height", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Raise the ground around a position (or lower it with a negative height). It's raised the most at the center and less and less up to the radius.", + "group": "Sculpt" + }, + { + "fullName": "Raise or lower the ground along a line", + "functionType": "Action", + "name": "RaiseGroundAlongLine", + "sentence": "Raise the ground of _PARAM0_ by _PARAM6_ from _PARAM1_; _PARAM2_ to _PARAM3_; _PARAM4_ with a radius of _PARAM5_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x1 = eventsFunctionContext.getArgument(\"X1\");", + "const y1 = eventsFunctionContext.getArgument(\"Y1\");", + "const x2 = eventsFunctionContext.getArgument(\"X2\");", + "const y2 = eventsFunctionContext.getArgument(\"Y2\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const height = eventsFunctionContext.getArgument(\"Height\");", + "object.__terrain3D.raise(x1, y1, x2, y2, radius, height);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the start", + "name": "X1", + "type": "expression" + }, + { + "description": "Y position of the start", + "name": "Y1", + "type": "expression" + }, + { + "description": "X position of the end", + "name": "X2", + "type": "expression" + }, + { + "description": "Y position of the end", + "name": "Y2", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance from the line." + }, + { + "description": "Height to add (negative to lower the ground)", + "name": "Height", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Raise the ground along a line, for example for a ridge, or lower it with a negative height to dig a river bed or a valley.", + "group": "Sculpt" + }, + { + "fullName": "Flatten the ground", + "functionType": "Action", + "name": "FlattenGround", + "sentence": "Flatten the ground of _PARAM0_ to a Z of _PARAM4_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const z = eventsFunctionContext.getArgument(\"Z\");", + "object.__terrain3D.flatten(x, y, x, y, radius, z, 1);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Z position of the flat ground", + "name": "Z", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Flatten the ground around a position to a given height, for example to place a building.", + "group": "Sculpt" + }, + { + "fullName": "Smooth the ground", + "functionType": "Action", + "name": "SmoothGround", + "sentence": "Smooth the ground of _PARAM0_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_ (strength: _PARAM4_)", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const strength = eventsFunctionContext.getArgument(\"Strength\");", + "object.__terrain3D.smooth(x, y, x, y, radius, strength);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Strength (between 0 and 1)", + "name": "Strength", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Smooth the ground around a position, softening bumps and cliffs.", + "group": "Sculpt" + }, + { + "fullName": "Paint a layer", + "functionType": "Action", + "name": "PaintLayer", + "sentence": "Paint layer _PARAM4_ on _PARAM0_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const layer = eventsFunctionContext.getArgument(\"Layer\");", + "object.__terrain3D.paint(x, y, x, y, radius, layer, 1);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Layer (1 to 4)", + "name": "Layer", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Paint a layer (like grass, dirt, rock or sand) on the ground around a position.", + "group": "Paint" + }, + { + "fullName": "Paint a layer along a line", + "functionType": "Action", + "name": "PaintLayerAlongLine", + "sentence": "Paint layer _PARAM6_ on _PARAM0_ from _PARAM1_; _PARAM2_ to _PARAM3_; _PARAM4_ with a radius of _PARAM5_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x1 = eventsFunctionContext.getArgument(\"X1\");", + "const y1 = eventsFunctionContext.getArgument(\"Y1\");", + "const x2 = eventsFunctionContext.getArgument(\"X2\");", + "const y2 = eventsFunctionContext.getArgument(\"Y2\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const layer = eventsFunctionContext.getArgument(\"Layer\");", + "object.__terrain3D.paint(x1, y1, x2, y2, radius, layer, 1);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the start", + "name": "X1", + "type": "expression" + }, + { + "description": "Y position of the start", + "name": "Y1", + "type": "expression" + }, + { + "description": "X position of the end", + "name": "X2", + "type": "expression" + }, + { + "description": "Y position of the end", + "name": "Y2", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance from the line." + }, + { + "description": "Layer (1 to 4)", + "name": "Layer", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Paint a layer on the ground along a line, for example for a path or a road.", + "group": "Paint" + }, + { + "fullName": "Tint the ground", + "functionType": "Action", + "name": "TintGround", + "sentence": "Tint the ground of _PARAM0_ with _PARAM4_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const color = gdjs.rgbOrHexToRGBColor(eventsFunctionContext.getArgument(\"Color\"));", + "object.__terrain3D.tint(x, y, x, y, radius, color, 1);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Color", + "name": "Color", + "type": "color" + } + ], + "objectGroups": [], + "description": "Tint the ground around a position with a color multiplying its layers, like dry grass or darker mud. White removes the tint.", + "group": "Paint" + }, + { + "fullName": "Change the wetness of the ground", + "functionType": "Action", + "name": "WetGround", + "sentence": "Change the wetness of the ground of _PARAM0_ to _PARAM4_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const wetness = eventsFunctionContext.getArgument(\"Wetness\");", + "object.__terrain3D.wet(x, y, x, y, radius, wetness, 1);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Wetness (from 0 for dry to 1)", + "name": "Wetness", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Make the ground around a position wet (darker and shiny, like after rain or near water) or dry.", + "group": "Paint" + }, + { + "fullName": "Ground wetness", + "functionType": "ExpressionAndCondition", + "name": "GroundWetness", + "sentence": "the wetness of the ground at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.getWetnessAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the wetness of the ground at a position, from 0 (dry) to 1, painted or near the water (wet shore).", + "group": "Paint", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Ground tint", + "functionType": "StringExpression", + "name": "GroundTint", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.getTintAt(x, y).join(\";\");" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "The tint painted on the ground at a position, like \"255;255;255\" (white when there is none).", + "group": "Paint", + "expressionType": { + "type": "string" + } + }, + { + "fullName": "Make a hole", + "functionType": "Action", + "name": "MakeHole", + "sentence": "Make a hole in _PARAM0_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "object.__terrain3D.makeHole(x, y, x, y, radius);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Remove the ground in a circle, for the entrance of a tunnel, a cave or a well: there is nothing drawn and no collision there. Fill it again with the \"Fill holes\" action. A height field can't go under itself: put a 3D model (with a collision shape) for the tunnel or the cave under the ground. The physics engine removes the triangles touching a hole, so the hole in the collisions can be up to a cell larger than the one drawn: make the model go a bit further than the hole, or put rocks around the entrance.", + "group": "Holes" + }, + { + "fullName": "Fill holes", + "functionType": "Action", + "name": "FillHoles", + "sentence": "Fill the holes in _PARAM0_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "object.__terrain3D.fillHoles(x, y, x, y, radius);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Fill the holes in a circle with the layers of the ground around them: the ground is drawn and has collisions again.", + "group": "Holes" + }, + { + "fullName": "Carve an entrance", + "functionType": "Action", + "name": "CarveEntrance", + "sentence": "Carve an entrance in _PARAM0_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_, going in at _PARAM4_° with its floor at Z _PARAM5_ and _PARAM6_ pixels of flat ground in front", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const angle = eventsFunctionContext.getArgument(\"Angle\");", + "const floorZ = eventsFunctionContext.getArgument(\"FloorZ\");", + "const length = eventsFunctionContext.getArgument(\"Length\");", + "object.__terrain3D.carveEntrance(x, y, radius, angle, floorZ, length);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression" + }, + { + "description": "Direction going into the ground (in degrees)", + "name": "Angle", + "type": "expression" + }, + { + "description": "Z position of the floor", + "name": "FloorZ", + "type": "expression" + }, + { + "description": "Length of the flat ground in front of the entrance (in pixels)", + "name": "Length", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Make the entrance of a tunnel or a cave on a slope: a hole, with the ground in front of it flattened to the floor of the tunnel.", + "group": "Holes" + }, + { + "fullName": "Hole", + "functionType": "Condition", + "name": "IsHole", + "sentence": "There is a hole in _PARAM0_ at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.isHoleAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Check if there is a hole in the ground at a position.", + "group": "Holes" + }, + { + "fullName": "Ground Z position, with holes", + "functionType": "Expression", + "name": "GroundZWithHoles", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const terrain = object.__terrain3D;", + "eventsFunctionContext.returnValue = terrain.isHoleAt(x, y) ? eventsFunctionContext.getArgument(\"ZInHoles\") : terrain.getHeightAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + }, + { + "description": "Value in holes", + "name": "ZInHoles", + "type": "expression", + "longDescription": "For example, a Z position far under the ground." + } + ], + "objectGroups": [], + "description": "The Z position of the ground at a position, or the given value where there is a hole.", + "group": "Holes", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Generate a relief", + "functionType": "Action", + "name": "GenerateRelief", + "sentence": "Generate a relief of _PARAM1_ on _PARAM0_ with seed _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const relief = eventsFunctionContext.getArgument(\"Relief\");", + "const seed = eventsFunctionContext.getArgument(\"Seed\");", + "object.__terrain3D.generate(relief, seed);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "Relief", + "name": "Relief", + "type": "stringWithSelector", + "supplementaryInformation": "[\"Flat\", \"Hills\", \"Mountains\", \"Island\"]" + }, + { + "description": "Seed", + "name": "Seed", + "type": "expression", + "longDescription": "Each seed gives a different terrain with the same relief." + } + ], + "objectGroups": [], + "description": "Replace the ground with a generated relief. Painted layers are reset.", + "group": "Generation" + }, + { + "fullName": "Load a heightmap", + "functionType": "Action", + "name": "LoadHeightmap", + "sentence": "Load the heights of _PARAM0_ from _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const image = eventsFunctionContext.getArgument(\"Image\");", + "object.__terrain3D.loadHeightmap(image);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "Heightmap image", + "name": "Image", + "type": "imageResource", + "longDescription": "Black is the lowest, white the highest. For more precision, store the height on 16 bits in the red (most significant byte) and green channels." + } + ], + "objectGroups": [], + "description": "Replace the ground with the heights of an image. Painted layers are kept.", + "group": "Generation" + }, + { + "fullName": "Put objects on the ground", + "functionType": "Action", + "name": "PutOnGround", + "sentence": "Put _PARAM1_ on the ground of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrain3D.placeOnGround(eventsFunctionContext.getObjects(\"Objects\"));" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "Objects to put on the ground", + "name": "Objects", + "type": "objectList" + } + ], + "objectGroups": [], + "description": "Move objects up or down so that their bottom is on the ground, under their center. Useful to place trees, rocks or characters on a terrain.", + "group": "Position" + }, + { + "fullName": "Ground Z position", + "functionType": "ExpressionAndCondition", + "name": "GroundZ", + "sentence": "the ground Z position at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.getHeightAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the Z position of the ground at a position of the scene.", + "group": "Position", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Ground slope", + "functionType": "ExpressionAndCondition", + "name": "GroundSlope", + "sentence": "the ground slope at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.getSlopeAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the slope of the ground (angle with the horizontal, in degrees) at a position of the scene.", + "group": "Position", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Ground layer", + "functionType": "ExpressionAndCondition", + "name": "GroundLayer", + "sentence": "the ground layer at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.getLayerAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the layer (from 1 to 4) painted the most at a position of the scene. Useful to play footstep sounds or to slow down characters in water or mud.", + "group": "Paint", + "expressionType": { + "type": "expression" + } + } + ], + "propertyDescriptors": [ + { + "value": "Hills", + "type": "Choice", + "label": "Relief", + "description": "The initial shape, before sculpting.", + "group": "Shape", + "choices": [ + { + "label": "Flat", + "value": "Flat" + }, + { + "label": "Hills", + "value": "Hills" + }, + { + "label": "Mountains", + "value": "Mountains" + }, + { + "label": "Island", + "value": "Island" + } + ], + "name": "Relief" + }, + { + "value": "1", + "type": "Number", + "label": "Seed", + "description": "Each seed gives a different terrain with the same relief.", + "group": "Shape", + "name": "Seed" + }, + { + "value": "", + "type": "Resource", + "label": "Heightmap image", + "description": "Optional. Replaces the relief: black is the lowest, white the highest (the object depth).", + "group": "Shape", + "extraInformation": [ + "image" + ], + "name": "HeightmapImage" + }, + { + "value": "", + "type": "MultilineString", + "label": "Edits", + "description": "Changes of the relief or heightmap, applied in order, as a JSON list, for example [{\"tool\": \"raise\", \"x\": 0.3, \"y\": 0.4, \"radius\": 0.15, \"height\": 0.4}, {\"tool\": \"paint\", \"x\": 0.1, \"y\": 0.9, \"toX\": 0.8, \"toY\": 0.2, \"radius\": 0.02, \"layer\": 2}]. Tools: raise (height added to the ground, negative to lower), flatten (z: the ground is brought to this height, fully at the center, cutting what is above it and filling what is below; with toX and toY, toZ gives the height at the end of the line, for a road going up), smooth (strength), paint (layer, from 1 to 4, and strength), tint (color, like \"120;140;60\", multiplying the layers, white removing the tint, and strength), wetness (amount, from 0 for dry to 1 for wet, and strength), hole (no ground in the radius, for the entrances of tunnels and caves) and fill (the holes in the radius are filled with the layers of the ground around them). x, y and radius are fractions (0 to 1) of the terrain size (x is (scene X - terrain X) / terrain width). height and z are fractions of its depth: the ground at z is at Z = terrain Z + z * terrain depth. Add toX and toY to change the ground along a line (paths, rivers, ridges). Shown in the scene editor.", + "group": "Shape", + "advanced": true, + "name": "Edits" + }, + { + "value": "256", + "type": "Choice", + "label": "Resolution", + "description": "The number of cells on each side of the terrain. Higher is more detailed but slower.", + "group": "Shape", + "choices": [ + { + "label": "64 x 64", + "value": "64" + }, + { + "label": "128 x 128", + "value": "128" + }, + { + "label": "256 x 256", + "value": "256" + }, + { + "label": "512 x 512", + "value": "512" + }, + { + "label": "1024 x 1024", + "value": "1024" + } + ], + "name": "Resolution" + }, + { + "value": "96;160;64", + "type": "Color", + "label": "Layer 1 color (grass)", + "group": "Layers", + "name": "Layer1Color" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 1 texture", + "description": "Optional. Tinted by the layer color.", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer1Texture" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 1 normal map", + "description": "Optional. The bumps of the texture, lit by the lights (an OpenGL normal map, like the textures of ambientCG).", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer1NormalMap" + }, + { + "value": "150;112;72", + "type": "Color", + "label": "Layer 2 color (dirt)", + "group": "Layers", + "name": "Layer2Color" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 2 texture", + "description": "Optional. Tinted by the layer color.", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer2Texture" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 2 normal map", + "description": "Optional. The bumps of the texture, lit by the lights (an OpenGL normal map, like the textures of ambientCG).", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer2NormalMap" + }, + { + "value": "124;118;112", + "type": "Color", + "label": "Layer 3 color (rock)", + "group": "Layers", + "name": "Layer3Color" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 3 texture", + "description": "Optional. Tinted by the layer color.", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer3Texture" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 3 normal map", + "description": "Optional. The bumps of the texture, lit by the lights (an OpenGL normal map, like the textures of ambientCG).", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer3NormalMap" + }, + { + "value": "224;204;148", + "type": "Color", + "label": "Layer 4 color (sand)", + "group": "Layers", + "name": "Layer4Color" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 4 texture", + "description": "Optional. Tinted by the layer color.", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer4Texture" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 4 normal map", + "description": "Optional. The bumps of the texture, lit by the lights (an OpenGL normal map, like the textures of ambientCG).", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer4NormalMap" + }, + { + "value": "1", + "type": "Number", + "label": "Normal map strength", + "description": "How much the normal maps bump the ground: 0 for none, 1 as they are, more to exaggerate them.", + "group": "Layers", + "name": "NormalMapStrength" + }, + { + "value": "256", + "type": "Number", + "label": "Texture size", + "unit": "Pixel", + "description": "The size, in the scene, of one repetition of the layer textures.", + "group": "Layers", + "name": "TextureSize" + }, + { + "value": "0.5", + "type": "Number", + "label": "Height blending", + "description": "Where layers meet, the bright parts of textures (stones, tufts) cover the dark ones (gaps, sand) instead of fading into each other: 0 for none, 1 for the most.", + "group": "Layers", + "name": "HeightBlending" + }, + { + "value": "0.5", + "type": "Number", + "label": "Texture variation", + "description": "Makes the repetition of textures less visible, with patches of bigger and rotated textures and colors slowly varying: 0 for none, 1 for the most.", + "group": "Layers", + "name": "TextureVariation" + }, + { + "value": "false", + "type": "Boolean", + "label": "Wet shore", + "description": "Make the ground wet (darker and shiny) under a Z position, like the sand of a beach near the water.", + "group": "Wetness", + "name": "WetShore" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Wet below Z", + "description": "The ground is wet under this Z position (for example, the Z of a 3D water object).", + "group": "Wetness", + "name": "WetShoreZ" + }, + { + "value": "60", + "type": "Number", + "unit": "Pixel", + "label": "Wet shore fade", + "description": "The ground becomes dry over this height above \"Wet below Z\".", + "group": "Wetness", + "name": "WetShoreFade" + }, + { + "value": "3", + "type": "Choice", + "label": "Layer on steep slopes", + "description": "This layer is shown where the ground is steep, whatever is painted.", + "group": "Layers", + "choices": [ + { + "label": "None", + "value": "0" + }, + { + "label": "Layer 1", + "value": "1" + }, + { + "label": "Layer 2", + "value": "2" + }, + { + "label": "Layer 3", + "value": "3" + }, + { + "label": "Layer 4", + "value": "4" + } + ], + "name": "CliffLayer" + }, + { + "value": "45", + "type": "Number", + "label": "Steep slope angle", + "unit": "DegreeAngle", + "group": "Layers", + "name": "CliffAngle" + }, + { + "value": "true", + "type": "Boolean", + "label": "Cast shadows", + "group": "Rendering", + "name": "CastShadow" + }, + { + "value": "true", + "type": "Boolean", + "label": "Receive shadows", + "group": "Rendering", + "name": "ReceiveShadow" + }, + { + "value": "", + "type": "String", + "label": "Sculpted and painted data", + "description": "Written by the sculpt and paint tools of the scene editor, in a compact format not meant to be read or written by hand: use Edits instead. Clear it to go back to the relief, heightmap and edits.", + "advanced": true, + "name": "SculptData" + } + ] + }, + { + "areaMaxX": 1000, + "areaMaxY": 1000, + "areaMaxZ": 40, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "defaultName": "Grass", + "description": "A field of grass blades swaying in the wind and pushed aside by characters. They grow in the area of the object, on the highest 3D terrain at each point (one field can cover several terrains), or at the Z position of the object where there is no terrain.", + "fullName": "Terrain grass", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0zIDIxYzEtNSAxLTkgMC0xMyAyIDMgMyA3IDMgMTN6bTQgMGMwLTYgMS0xMSA0LTE2LTEgNS0xIDEwIDAgMTZ6bTUgMGMwLTUgMi05IDUtMTItMiA0LTIgOC0xIDEyem01IDBjMS00IDItNiA0LTgtMSAzLTEgNS0xIDh6Ii8+PC9zdmc+", + "is3D": true, + "isUsingLegacyInstancesRenderer": true, + "name": "TerrainGrass", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/732ef90b7fcf5dd9171fe95d0cf262e09159b487304915a4693baa893d9ce16c_grass.svg", + "objects": [ + { + "assetStoreId": "", + "name": "AreaPlaceholder", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 1000, + "height": 1000, + "depth": 40, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "Basic", + "tint": "255;255;255", + "frontFaceResourceName": "", + "frontFaceVisible": false, + "frontFaceResourceRepeat": false, + "backFaceResourceName": "", + "backFaceVisible": false, + "backFaceResourceRepeat": false, + "leftFaceResourceName": "", + "leftFaceVisible": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceName": "", + "rightFaceVisible": false, + "rightFaceResourceRepeat": false, + "topFaceResourceName": "", + "topFaceVisible": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceName": "", + "bottomFaceVisible": false, + "bottomFaceResourceRepeat": false + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "AreaPlaceholder" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [ + { + "angle": 0, + "customSize": true, + "width": 1000, + "height": 1000, + "depth": 40, + "keepRatio": true, + "layer": "", + "name": "AreaPlaceholder", + "persistentUuid": "5d7a8b1e-3c2f-4e6a-9b1d-001000001000", + "x": 0, + "y": 0, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": {}, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Terrain3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Terrain3D::DefineGrassHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "for (const child of object.getChildrenContainer().getAdhocListOfAllInstances()) {", + " child.deleteFromScene();", + "}", + "if (object.__terrainGrass) object.__terrainGrass.dispose();", + "object.__terrainGrass = new gdjs.__terrainGrassExtension.Grass(object);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::TerrainGrass" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDestroy", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrainGrass.dispose();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::TerrainGrass" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const game = runtimeScene.getGame();", + "// Time doesn't pass in the scene editor: real time is used to show the wind.", + "const isInGameEdition = game.isInGameEdition && game.isInGameEdition();", + "const elapsedTime = isInGameEdition ? 1 / 60 : object.getElapsedTime() / 1000;", + "const time = isInGameEdition", + " ? performance.now() / 1000", + " : object.getRuntimeScene().getTimeManager().getTimeFromStart() / 1000;", + "const layer = object.getInstanceContainer().getLayer(object.getLayer());", + "object.__terrainGrass.update(elapsedTime, time, layer.getRenderer().getThreeCamera());" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::TerrainGrass" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrainGrass.updateFromProperties();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::TerrainGrass" + } + ], + "objectGroups": [] + } + ], + "propertyDescriptors": [ + { + "value": "60", + "type": "Number", + "label": "Density", + "description": "The number of blades in a square of 100 x 100 pixels.", + "group": "Blades", + "name": "Density" + }, + { + "value": "36", + "type": "Number", + "label": "Blade height", + "unit": "Pixel", + "description": "The average height of blades (each blade is between 60% and 140% of it).", + "group": "Blades", + "name": "BladeHeight" + }, + { + "value": "5", + "type": "Number", + "label": "Blade width", + "unit": "Pixel", + "group": "Blades", + "name": "BladeWidth" + }, + { + "value": "52;104;36", + "type": "Color", + "label": "Base color", + "group": "Blades", + "name": "BaseColor" + }, + { + "value": "150;196;82", + "type": "Color", + "label": "Tip color", + "group": "Blades", + "name": "TipColor" + }, + { + "value": "1", + "type": "Number", + "label": "Seed", + "description": "Each seed places blades differently.", + "group": "Blades", + "name": "Seed" + }, + { + "value": "1", + "type": "Choice", + "label": "Grows on terrain layer", + "description": "On a 3D terrain, blades only grow where this layer is painted. A new terrain has layer 1 painted everywhere.", + "group": "Ground", + "choices": [ + { + "label": "Any", + "value": "0" + }, + { + "label": "Layer 1", + "value": "1" + }, + { + "label": "Layer 2", + "value": "2" + }, + { + "label": "Layer 3", + "value": "3" + }, + { + "label": "Layer 4", + "value": "4" + } + ], + "name": "GroundLayer" + }, + { + "value": "40", + "type": "Number", + "label": "Maximum slope", + "unit": "DegreeAngle", + "description": "No blades grow on steeper ground.", + "group": "Ground", + "name": "MaxSlope" + }, + { + "value": "1", + "type": "Number", + "label": "Wind strength", + "description": "0 for no wind.", + "group": "Wind", + "name": "WindStrength" + }, + { + "value": "2", + "type": "Number", + "label": "Wind speed", + "group": "Wind", + "name": "WindSpeed" + }, + { + "value": "", + "type": "String", + "label": "Object bending the grass", + "description": "The name of an object pushing blades aside when walking through the grass, like the player. The 8 first instances are used.", + "group": "Wind", + "name": "BendingObject" + }, + { + "value": "2500", + "type": "Number", + "label": "Fade distance", + "unit": "Pixel", + "description": "Blades are less and less dense up to this distance from the camera, and hidden after.", + "group": "Rendering", + "name": "FadeDistance" + } + ] + }, + { + "areaMaxX": 2000, + "areaMaxY": 2000, + "areaMaxZ": 200, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "defaultName": "ScatteredModels", + "assetStoreTag": "3d scattered models", + "description": "Many copies of a 3D model (trees, rocks, bushes, flowers...) placed in the area of the object and drawn all at once, which is much faster than an object for each one. They stand on the highest 3D terrain at each point, or at the Z position of the object where there is no terrain. Paint where they grow with the brushes of the scene editor.", + "fullName": "Scattered models (forests, rocks...)", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0xMCAyMXYtM0gzbDUtNUg1bDUtNUg3bDUtNSA1IDVoLTNsNSA1aC0zbDUgNWgtN3YzeiIvPjwvc3ZnPg==", + "is3D": true, + "isUsingLegacyInstancesRenderer": true, + "name": "ScatteredModels", + "previewIconUrl": "", + "objects": [ + { + "assetStoreId": "", + "name": "AreaPlaceholder", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 2000, + "height": 2000, + "depth": 200, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "Basic", + "tint": "255;255;255", + "frontFaceResourceName": "", + "frontFaceVisible": false, + "frontFaceResourceRepeat": false, + "backFaceResourceName": "", + "backFaceVisible": false, + "backFaceResourceRepeat": false, + "leftFaceResourceName": "", + "leftFaceVisible": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceName": "", + "rightFaceVisible": false, + "rightFaceResourceRepeat": false, + "topFaceResourceName": "", + "topFaceVisible": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceName": "", + "bottomFaceVisible": false, + "bottomFaceResourceRepeat": false + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "AreaPlaceholder" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [ + { + "angle": 0, + "customSize": true, + "width": 2000, + "height": 2000, + "depth": 200, + "keepRatio": true, + "layer": "", + "name": "AreaPlaceholder", + "persistentUuid": "5d7a8b1e-3c2f-4e6a-9b1d-002000002000", + "x": 0, + "y": 0, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": {}, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Terrain3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Terrain3D::DefineScatterHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "for (const child of object.getChildrenContainer().getAdhocListOfAllInstances()) {", + " child.deleteFromScene();", + "}", + "if (object.__terrainScatter) object.__terrainScatter.dispose();", + "object.__terrainScatter = new gdjs.__terrainScatterExtension.Scatter(object);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDestroy", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrainScatter.dispose();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const game = runtimeScene.getGame();", + "const isInGameEdition = game.isInGameEdition && game.isInGameEdition();", + "const elapsedTime = isInGameEdition ? 1 / 60 : object.getElapsedTime() / 1000;", + "const layer = object.getInstanceContainer().getLayer(object.getLayer());", + "object.__terrainScatter.update(elapsedTime, layer.getRenderer().getThreeCamera());" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrainScatter.onHotReloading();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + } + ], + "objectGroups": [] + }, + { + "fullName": "Add copies", + "functionType": "Action", + "name": "AddCopies", + "sentence": "Add copies of _PARAM0_ around _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "object.__terrainScatter.addCopies(x, y, radius);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Paint the density of copies around a position, up to the density of the object (copies removed with \"Erase copies\" come back). The effect fades out up to the radius.", + "group": "Paint" + }, + { + "fullName": "Erase copies", + "functionType": "Action", + "name": "EraseCopies", + "sentence": "Erase the copies of _PARAM0_ in a circle at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "object.__terrainScatter.removeCopies(x, y, radius);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Remove the copies standing in a circle, for example a tree cut by the player (use the position of the nearest copy and a small radius) or a forest cleared for a building. They come back when the scene is restarted.", + "group": "Paint" + }, + { + "fullName": "Number of copies", + "functionType": "ExpressionAndCondition", + "name": "CopyCount", + "sentence": "the number of copies", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "eventsFunctionContext.returnValue = object.__terrainScatter.getCopyCount();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + } + ], + "objectGroups": [], + "description": "the number of copies placed in the area.", + "group": "Copies", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "X position of the nearest copy", + "functionType": "Expression", + "name": "NearestCopyX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const copy = object.__terrainScatter.findNearestCopy(x, y);", + "eventsFunctionContext.returnValue = copy ? copy.x : x;" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "The X position of the base of the copy nearest to a position (the position itself if there is no copy).", + "group": "Copies", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Y position of the nearest copy", + "functionType": "Expression", + "name": "NearestCopyY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const copy = object.__terrainScatter.findNearestCopy(x, y);", + "eventsFunctionContext.returnValue = copy ? copy.y : y;" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "The Y position of the base of the copy nearest to a position (the position itself if there is no copy).", + "group": "Copies", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Z position of the nearest copy", + "functionType": "Expression", + "name": "NearestCopyZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const copy = object.__terrainScatter.findNearestCopy(x, y);", + "eventsFunctionContext.returnValue = copy ? copy.z : object.getZ();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "The Z position of the base of the copy nearest to a position (on the ground).", + "group": "Copies", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Distance to the nearest copy", + "functionType": "ExpressionAndCondition", + "name": "NearestCopyDistance", + "sentence": "the distance from _PARAM1_; _PARAM2_ to the nearest copy", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const copy = object.__terrainScatter.findNearestCopy(x, y);", + "eventsFunctionContext.returnValue = copy ? Math.hypot(copy.x - x, copy.y - y) : Number.MAX_VALUE;" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the distance (on X and Y) from a position to the base of the nearest copy. It's a very big number when there is no copy.", + "group": "Copies", + "expressionType": { + "type": "expression" + } + } + ], + "propertyDescriptors": [ + { + "value": "", + "type": "Resource", + "label": "3D model", + "description": "The model copied everywhere (a .glb file). Models standing on the Y axis, like most models of the asset store, are stood up along Z.", + "group": "Model", + "extraInformation": [ + "model3D" + ], + "name": "Model" + }, + { + "value": "300", + "type": "Number", + "label": "Height of a copy", + "unit": "Pixel", + "description": "Copies keep the proportions of the model.", + "group": "Model", + "name": "Size" + }, + { + "value": "0.8", + "type": "Number", + "label": "Smallest random scale", + "description": "Each copy is scaled at random between the smallest and the largest scale.", + "group": "Model", + "name": "MinScale" + }, + { + "value": "1.2", + "type": "Number", + "label": "Largest random scale", + "group": "Model", + "name": "MaxScale" + }, + { + "value": "true", + "type": "Boolean", + "label": "Random rotation", + "description": "Turn each copy around the Z axis at random.", + "group": "Model", + "name": "RandomRotation" + }, + { + "value": "255;255;255", + "type": "Color", + "label": "Random tint", + "description": "Each copy is tinted between white and this color, at random. White for no tint.", + "group": "Model", + "name": "RandomTint" + }, + { + "value": "0.3", + "type": "Number", + "label": "Density", + "description": "The number of copies in a square of 100 x 100 pixels where the density is fully painted (0.3 is about one copy every 180 pixels). Copies never overlap their neighbors much: they are placed at random in a grid.", + "group": "Placement", + "name": "Density" + }, + { + "value": "1", + "type": "Number", + "label": "Seed", + "description": "Each seed places copies differently.", + "group": "Placement", + "name": "Seed" + }, + { + "value": "Everywhere", + "type": "Choice", + "label": "Copies grow", + "description": "Everywhere in the area, or only where they are painted with the \"Add copies\" brush (or the edits).", + "group": "Placement", + "choices": [ + { + "label": "Everywhere (erase them with the brush)", + "value": "Everywhere" + }, + { + "label": "Only where painted", + "value": "Painted" + } + ], + "name": "Coverage" + }, + { + "value": "1", + "type": "Choice", + "label": "Grows on terrain layer", + "description": "On a 3D terrain, copies only grow where this layer is painted, away from its borders (so that trees don't stand on roads painted with another layer). A new terrain has layer 1 painted everywhere.", + "group": "Ground", + "choices": [ + { + "label": "Any", + "value": "0" + }, + { + "label": "Layer 1", + "value": "1" + }, + { + "label": "Layer 2", + "value": "2" + }, + { + "label": "Layer 3", + "value": "3" + }, + { + "label": "Layer 4", + "value": "4" + } + ], + "name": "GroundLayer" + }, + { + "value": "30", + "type": "Number", + "label": "Maximum slope", + "unit": "DegreeAngle", + "description": "No copies grow on steeper ground.", + "group": "Ground", + "name": "MaxSlope" + }, + { + "value": "-100000", + "type": "Number", + "label": "Minimum ground Z", + "unit": "Pixel", + "description": "No copies grow where the ground is lower (for example, under the water level).", + "group": "Ground", + "name": "MinGroundZ" + }, + { + "value": "100000", + "type": "Number", + "label": "Maximum ground Z", + "unit": "Pixel", + "description": "No copies grow where the ground is higher (for example, above the tree line).", + "group": "Ground", + "name": "MaxGroundZ" + }, + { + "value": "false", + "type": "Boolean", + "label": "Trunk collisions", + "description": "Give each copy a static collision shape around its trunk, for the 3D physics behavior (add it to this object, with the \"Static\" type) and navigation meshes. Without them, these behaviors have no effect on this object.", + "group": "Collisions", + "name": "TrunkCollisions" + }, + { + "value": "30", + "type": "Number", + "label": "Trunk diameter", + "unit": "Pixel", + "description": "The diameter of the collision shape of a copy at the height given in \"Height of a copy\" (it is scaled with the copy).", + "group": "Collisions", + "name": "TrunkDiameter" + }, + { + "value": "8000", + "type": "Number", + "label": "Draw distance", + "unit": "Pixel", + "description": "Copies shrink and are hidden at this distance from the camera.", + "group": "Rendering", + "name": "DrawDistance" + }, + { + "value": "true", + "type": "Boolean", + "label": "Cast shadows", + "group": "Rendering", + "name": "CastShadow" + }, + { + "value": "true", + "type": "Boolean", + "label": "Receive shadows", + "group": "Rendering", + "name": "ReceiveShadow" + }, + { + "value": "", + "type": "MultilineString", + "label": "Edits", + "description": "Where copies are added or erased, applied in order, as a JSON list, for example [{\"tool\": \"add\", \"x\": 0.3, \"y\": 0.4, \"radius\": 0.15}, {\"tool\": \"erase\", \"x\": 0.1, \"y\": 0.9, \"toX\": 0.8, \"toY\": 0.2, \"radius\": 0.02}]. Tools: add (density: from 0 to 1, 1 by default, how dense copies are compared to the Density property) and erase. strength (from 0 to 1) makes a stroke lighter. x, y and radius are fractions (0 to 1) of the object size (x is (scene X - object X) / object width). Add toX and toY to paint along a line (a row of trees, a path through a forest). With \"Copies grow: only where painted\", only the added areas have copies. Shown in the scene editor.", + "group": "Placement", + "advanced": true, + "name": "Edits" + }, + { + "value": "", + "type": "String", + "label": "Painted data", + "description": "Written by the brushes of the scene editor, in a compact format not meant to be read or written by hand: use Edits instead. Clear it to go back to the coverage and the edits.", + "advanced": true, + "name": "PaintData" + } + ] + } + ] + }, + { + "author": "", + "category": "Visual effect", + "dimension": "3D", + "extensionNamespace": "", + "fullName": "3D water", + "gdevelopVersion": ">=5.5.222", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXdhdmVzIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTIwLDEySDIyVjE0SDIwQzE4LjYyLDE0IDE3LjI2LDEzLjY1IDE2LDEzQzEzLjUsMTQuMyAxMC41LDE0LjMgOCwxM0M2Ljc0LDEzLjY1IDUuMzcsMTQgNCwxNEgyVjEySDRDNS4zOSwxMiA2Ljc4LDExLjUzIDgsMTAuNjdDMTAuNDQsMTIuMzggMTMuNTYsMTIuMzggMTYsMTAuNjdDMTcuMjIsMTEuNTMgMTguNjEsMTIgMjAsMTJNMjAsNkgyMlY4SDIwQzE4LjYyLDggMTcuMjYsNy42NSAxNiw3QzEzLjUsOC4zIDEwLjUsOC4zIDgsN0M2Ljc0LDcuNjUgNS4zNyw4IDQsOEgyVjZINEM1LjM5LDYgNi43OCw1LjUzIDgsNC42N0MxMC40NCw2LjM4IDEzLjU2LDYuMzggMTYsNC42N0MxNy4yMiw1LjUzIDE4LjYxLDYgMjAsNk0yMCwxOEgyMlYyMEgyMEMxOC42MiwyMCAxNy4yNiwxOS42NSAxNiwxOUMxMy41LDIwLjMgMTAuNSwyMC4zIDgsMTlDNi43NCwxOS42NSA1LjM3LDIwIDQsMjBIMlYxOEg0QzUuMzksMTggNi43OCwxNy41MyA4LDE2LjY3QzEwLjQ0LDE4LjM4IDEzLjU2LDE4LjM4IDE2LDE2LjY3QzE3LjIyLDE3LjUzIDE4LjYxLDE4IDIwLDE4WiIgLz48L3N2Zz4=", + "name": "Water3D", + "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/waves.svg", + "shortDescription": "A water surface with waves for seas, lakes and rivers, where objects can float.", + "version": "0.1.0", + "description": [ + "A 3D water surface for seas, lakes and rivers, with animated waves, light reflections and foam on wave crests.", + "", + "- Resize it to cover a sea or a lake: waves follow its size. Put it at the Z position of the water level.", + "- **Make objects float** with an action (for objects with the 3D physics behavior), and check if an object is **underwater**.", + "- Read the height of the waves anywhere with an expression, for example to move a boat.", + "- For more realistic ripples, set a seamless **normal map** of water waves in the properties.", + "- Works well with the 3D terrain extension: place the water between hills or around an island." + ], + "origin": { + "identifier": "Water3D", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d", + "water", + "sea", + "lake", + "ocean", + "waves", + "buoyancy" + ], + "authorIds": [], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [ + { + "fullName": "Define helper classes", + "functionType": "Action", + "name": "DefineHelperClasses", + "sentence": "Define helper classes JavaScript code", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (gdjs.__water3DExtension) {", + " return;", + "}", + "", + "// Tested by the gameplay tests of the \"3D island terrain\" example (GDevelop-examples repository).", + "", + "/**", + " * Replace parts of the shader code of a Three.js material, with an error if", + " * a part is missing (for example after an update of Three.js).", + " * @param {string} shaderCode", + " * @param {Array<[string, string]>} replacements The searched code and its replacement.", + " */", + "const patchShaderCode = (shaderCode, replacements) => {", + " for (const [searchedCode, newCode] of replacements) {", + " if (!shaderCode.includes(searchedCode)) {", + " console.error(`Water3D: \"${searchedCode}\" was not found in the shader, which won't look as expected.`);", + " }", + " shaderCode = shaderCode.replace(searchedCode, newCode);", + " }", + " return shaderCode;", + "};", + "", + "// Size of the object inner area: instances are scaled from it.", + "const AREA_SIZE = 1000;", + "// Waves are drawn with vertices up to this distance, in scene units.", + "const WAVE_VERTEX_SPACING = 25;", + "const MAX_SEGMENTS = 256;", + "", + "// The waves are a sum of sines. The same formula is used by the shader and to", + "// compute the surface height for events.", + "const WAVES = [", + " // [direction x, direction y, relative length, relative speed, relative height]", + " [1, 0.3, 1, 1, 0.5],", + " [-0.4, 1, 0.61, 1.27, 0.3],", + " [0.7, -0.7, 0.37, 1.61, 0.2],", + "];", + "const waveDirections = WAVES.map(([x, y]) => {", + " const length = Math.hypot(x, y);", + " return [x / length, y / length];", + "});", + "", + "/**", + " * @param {number} x Scene X position.", + " * @param {number} y Scene Y position.", + " * @param {number} time In seconds.", + " * @param {{waveHeight: number, waveLength: number, waveSpeed: number}} settings", + " * @returns {number} The height of the waves above the water level.", + " */", + "/**", + " * Waves shorter than a few vertices can't be drawn: they are faded out.", + " * @param {number} length", + " * @param {number} vertexSpacing", + " */", + "const getWaveVisibility = (length, vertexSpacing) => {", + " const t = Math.min(Math.max((length / vertexSpacing - 3) / 3, 0), 1);", + " return t * t * (3 - 2 * t);", + "};", + "", + "const getWaveHeight = (x, y, time, settings) => {", + " let height = 0;", + " for (let index = 0; index < WAVES.length; index++) {", + " const [, , relativeLength, relativeSpeed, relativeHeight] = WAVES[index];", + " const [directionX, directionY] = waveDirections[index];", + " const length = settings.waveLength * relativeLength;", + " const frequency = (2 * Math.PI) / length;", + " const phase = (directionX * x + directionY * y) * frequency + time * settings.waveSpeed * relativeSpeed;", + " height += Math.sin(phase) * relativeHeight * getWaveVisibility(length, settings.vertexSpacing);", + " }", + " return height * settings.waveHeight;", + "};", + "", + "const wavesShaderCode = WAVES.map(([, , relativeLength, relativeSpeed, relativeHeight], index) => {", + " const [directionX, directionY] = waveDirections[index];", + " return `", + " {", + " vec2 direction = vec2(${directionX.toFixed(6)}, ${directionY.toFixed(6)});", + " float waveLength = waterWaveLength * ${relativeLength.toFixed(6)};", + " float frequency = 6.2831853 / waveLength;", + " float phase = dot(direction, position) * frequency + waterTime * waterWaveSpeed * ${relativeSpeed.toFixed(6)};", + " float amplitude = waterWaveHeight * ${relativeHeight.toFixed(6)} *", + " smoothstep(0.0, 1.0, clamp((waveLength / waterVertexSpacing - 3.0) / 3.0, 0.0, 1.0));", + " height += sin(phase) * amplitude;", + " slope += direction * cos(phase) * amplitude * frequency;", + " }`;", + "}).join('');", + "", + "// Waves too short for the vertices are drawn with the light instead: their", + "// slope is computed for each pixel (and faded out when smaller than pixels).", + "const shortWavesShaderCode = WAVES.map(([, , relativeLength, relativeSpeed, relativeHeight], index) => {", + " const [directionX, directionY] = waveDirections[index];", + " return `", + " {", + " vec2 direction = vec2(${directionX.toFixed(6)}, ${directionY.toFixed(6)});", + " float waveLength = waterWaveLength * ${relativeLength.toFixed(6)};", + " float frequency = 6.2831853 / waveLength;", + " float phase = dot(direction, position) * frequency + waterTime * waterWaveSpeed * ${relativeSpeed.toFixed(6)};", + " float amplitude = waterWaveHeight * ${relativeHeight.toFixed(6)} *", + " (1.0 - smoothstep(0.0, 1.0, clamp((waveLength / waterVertexSpacing - 3.0) / 3.0, 0.0, 1.0))) *", + " clamp(2.0 - fwidth(phase), 0.0, 1.0);", + " slope += direction * cos(phase) * amplitude * frequency;", + " }`;", + "}).join('');", + "", + "const vertexShaderDeclarations = `", + "uniform float waterWorldScale;", + "uniform float waterTime;", + "uniform float waterWaveHeight;", + "uniform float waterWaveLength;", + "uniform float waterWaveSpeed;", + "uniform float waterVertexSpacing;", + "varying vec2 vWaterWorldXY;", + "varying float vWaterCrest;", + "", + "// Returns the height of the waves, and their slope in slopeOut.", + "float getWaterWaves(vec2 position, out vec2 slopeOut) {", + " float height = 0.0;", + " vec2 slope = vec2(0.0);", + " ${wavesShaderCode}", + " slopeOut = slope;", + " return height;", + "}", + "`;", + "const vertexShaderWaves = `", + "// Waves are computed in scene coordinates (the 3D scene is flipped on Y).", + "vec2 waterScenePosition = (modelMatrix * vec4(position, 1.0)).xy * vec2(waterWorldScale, -waterWorldScale);", + "vec2 waterSlope;", + "float waterHeight = getWaterWaves(waterScenePosition, waterSlope);", + "vWaterCrest = waterWaveHeight > 0.0 ? waterHeight / waterWaveHeight : 0.0;", + "vWaterWorldXY = waterScenePosition;", + "`;", + "const vertexShaderNormal = `", + "transformedNormal = normalize((viewMatrix * vec4(-waterSlope.x, waterSlope.y, 1.0, 0.0)).xyz);", + "`;", + "const vertexShaderPosition = `", + "vec3 transformed = vec3(position.xy, position.z + waterHeight / max(length(modelMatrix[2].xyz) * waterWorldScale, 0.0001));", + "`;", + "const fragmentShaderDeclarations = `", + "uniform float waterTime;", + "uniform float waterWaveHeight;", + "uniform float waterWaveLength;", + "uniform float waterWaveSpeed;", + "uniform float waterVertexSpacing;", + "uniform float waterRippleTime;", + "uniform vec3 waterCrestColor;", + "uniform float waterFoam;", + "uniform sampler2D waterNormalMap;", + "uniform float waterNormalMapSize;", + "uniform float waterHasNormalMap;", + "varying vec2 vWaterWorldXY;", + "varying float vWaterCrest;", + "", + "vec2 getWaterShortWavesSlope(vec2 position) {", + " vec2 slope = vec2(0.0);", + " ${shortWavesShaderCode}", + " // Steeper would only give noise.", + " float slopeLength = length(slope);", + " return slopeLength > 1.0 ? slope / slopeLength : slope;", + "}", + "", + "float waterHash(vec2 p) {", + " return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);", + "}", + "float waterNoise(vec2 p) {", + " vec2 i = floor(p);", + " vec2 f = fract(p);", + " vec2 u = f * f * (3.0 - 2.0 * f);", + " return mix(", + " mix(waterHash(i), waterHash(i + vec2(1.0, 0.0)), u.x),", + " mix(waterHash(i + vec2(0.0, 1.0)), waterHash(i + vec2(1.0, 1.0)), u.x),", + " u.y", + " );", + "}", + "`;", + "const fragmentShaderColor = `", + "// Ripples bend the light: they are a slope in scene coordinates, used for the", + "// normal (and the foam).", + "vec2 rippleSlope;", + "if (waterHasNormalMap > 0.5) {", + " // Two layers of the normal map moving in different directions never repeat", + " // the same pattern.", + " vec2 rippleUv = vWaterWorldXY / waterNormalMapSize;", + " vec2 slope1 = texture2D(waterNormalMap, rippleUv + vec2(0.013, 0.007) * waterRippleTime).xy * 2.0 - 1.0;", + " vec2 slope2 = texture2D(waterNormalMap, rippleUv * 0.71 + vec2(-0.009, 0.011) * waterRippleTime).xy * 2.0 - 1.0;", + " rippleSlope = (slope1 + slope2) * 0.5;", + "} else {", + " vec2 rippleNormalPosition = vWaterWorldXY / 18.0 + vec2(waterRippleTime * 0.4, -waterRippleTime * 0.3);", + " rippleSlope = 0.04 * vec2(", + " waterNoise(rippleNormalPosition) - 0.5,", + " waterNoise(rippleNormalPosition + 7.3) - 0.5", + " );", + "}", + "rippleSlope -= getWaterShortWavesSlope(vWaterWorldXY);", + "// Small ripples moving on top of the waves.", + "vec2 ripplePosition = vWaterWorldXY / 30.0;", + "float ripples =", + " waterNoise(ripplePosition + vec2(waterRippleTime * 0.6, waterRippleTime * 0.2)) +", + " waterNoise(ripplePosition * 1.7 - vec2(waterRippleTime * 0.3, waterRippleTime * 0.5));", + "float crest = smoothstep(0.35, 1.0, vWaterCrest + (ripples - 1.0) * 0.35);", + "diffuseColor.rgb = mix(diffuseColor.rgb, waterCrestColor, crest * 0.6);", + "// Foam only on the highest crests.", + "float foam = smoothstep(1.0 - waterFoam * 0.3, 1.05 - waterFoam * 0.3, vWaterCrest * 0.75 + ripples * 0.15);", + "// With a normal map, foam is in streaks along the ripples.", + "if (waterHasNormalMap > 0.5) foam *= smoothstep(0.1, 0.4, length(rippleSlope));", + "diffuseColor.rgb = mix(diffuseColor.rgb, vec3(1.0), foam * 0.8);", + "diffuseColor.a = mix(diffuseColor.a, 1.0, foam * 0.8);", + "`;", + "const fragmentShaderNormal = `", + "#include ", + "// The 3D scene is flipped on Y.", + "normal = normalize(normal + faceDirection * (viewMatrix * vec4(rippleSlope.x, -rippleSlope.y, 0.0, 0.0)).xyz);", + "`;", + "const fragmentShaderOpacity = `", + "// Water reflects more light when seen from the side (Fresnel effect).", + "vec3 waterViewDirection = isOrthographic ? vec3(0.0, 0.0, 1.0) : normalize(vViewPosition);", + "float waterFresnel = pow(1.0 - clamp(abs(dot(normal, waterViewDirection)), 0.0, 1.0), 4.0);", + "// It reflects the sky, lit like the wave crests.", + "outgoingLight = mix(outgoingLight, waterCrestColor, waterFresnel * 0.5);", + "diffuseColor.a = mix(diffuseColor.a, 1.0, waterFresnel * 0.7);", + "#include ", + "`;", + "", + "const normalMapTextures = new WeakMap();", + "/**", + " * @param {gdjs.RuntimeGame} game", + " * @param {string} resourceName", + " * @returns {THREE.Texture} A repeated texture read as directions, not colors.", + " */", + "const getNormalMapTexture = (game, resourceName) => {", + " const imageTexture = game.getImageManager().getThreeTexture(resourceName);", + " let texture = normalMapTextures.get(imageTexture);", + " if (!texture) {", + " texture = imageTexture.clone();", + " texture.colorSpace = THREE.NoColorSpace;", + " texture.wrapS = THREE.RepeatWrapping;", + " texture.wrapT = THREE.RepeatWrapping;", + " texture.minFilter = THREE.LinearMipmapLinearFilter;", + " texture.generateMipmaps = true;", + " texture.anisotropy = 4;", + " texture.needsUpdate = true;", + " normalMapTextures.set(imageTexture, texture);", + " }", + " return texture;", + "};", + "", + "/**", + " * The water of a Water3D object: a surface with waves, drawn transparent.", + " */", + "class Water {", + " /** @param {gdjs.CustomRuntimeObject3D} object */", + " constructor(object) {", + " this.object = object;", + " this.time = 0;", + " this.uniforms = {", + " // Scene units by 3D world unit.", + " waterWorldScale: { value: 1 },", + " waterTime: { value: 0 },", + " // Ripples are noise: a small time keeps them precise.", + " waterRippleTime: { value: 0 },", + " waterWaveHeight: { value: 0 },", + " waterWaveLength: { value: 1 },", + " waterWaveSpeed: { value: 1 },", + " waterVertexSpacing: { value: 1 },", + " waterCrestColor: { value: new THREE.Color() },", + " waterFoam: { value: 0 },", + " waterNormalMap: { value: null },", + " waterNormalMapSize: { value: 1 },", + " waterHasNormalMap: { value: 0 },", + " };", + " this.material = new THREE.MeshStandardMaterial({", + " transparent: true,", + " roughness: 0.35,", + " metalness: 0,", + " side: THREE.DoubleSide,", + " // Drawn once: the underside is lit with the flipped normal.", + " forceSinglePass: true,", + " depthWrite: false,", + " });", + " this.material.onBeforeCompile = (shader) => {", + " Object.assign(shader.uniforms, this.uniforms);", + " shader.vertexShader = patchShaderCode(shader.vertexShader, [", + " ['#include ', '#include \\n' + vertexShaderDeclarations],", + " ['#include ', '#include \\n' + vertexShaderWaves],", + " ['#include ', '#include \\n' + vertexShaderNormal],", + " ['#include ', vertexShaderPosition],", + " ]);", + " shader.fragmentShader = patchShaderCode(shader.fragmentShader, [", + " ['#include ', '#include \\n' + fragmentShaderDeclarations],", + " ['#include ', '#include \\n' + fragmentShaderColor],", + " ['#include ', fragmentShaderNormal],", + " ['#include ', fragmentShaderOpacity],", + " ]);", + " };", + " this.material.customProgramCacheKey = () => 'Water3D';", + " this.mesh = new THREE.Mesh(new THREE.BufferGeometry(), this.material);", + " this.mesh.receiveShadow = true;", + " // Rays hit the plane of the water, which is all its geometry is (the", + " // shader moves the waves): two triangles instead of those of the waves.", + " const raycastGeometry = new THREE.PlaneGeometry(AREA_SIZE, AREA_SIZE);", + " raycastGeometry.translate(AREA_SIZE / 2, AREA_SIZE / 2, 0);", + " this.raycastGeometry = raycastGeometry;", + " this.mesh.raycast = function (raycaster, intersects) {", + " const drawnGeometry = this.geometry;", + " this.geometry = raycastGeometry;", + " THREE.Mesh.prototype.raycast.call(this, raycaster, intersects);", + " this.geometry = drawnGeometry;", + " };", + " object.get3DRendererObject().add(this.mesh);", + " this.segmentsX = 0;", + " this.segmentsY = 0;", + " this.settings = { waveHeight: 0, waveLength: 1, waveSpeed: 1, vertexSpacing: 1 };", + " this.updateFromProperties();", + " }", + "", + " updateFromProperties() {", + " const { object } = this;", + " this.material.color.set(gdjs.rgbOrHexStringToNumber(object._getColor()));", + " this.material.opacity = gdjs.evtTools.common.clamp(object._getOpacity() / 255, 0, 1);", + " this.uniforms.waterCrestColor.value.set(gdjs.rgbOrHexStringToNumber(object._getCrestColor()));", + " this.uniforms.waterFoam.value = gdjs.evtTools.common.clamp(object._getFoam(), 0, 1);", + " const normalMapName = object._getNormalMap();", + " this.uniforms.waterHasNormalMap.value = normalMapName ? 1 : 0;", + " this.uniforms.waterNormalMap.value = normalMapName", + " ? getNormalMapTexture(object.getRuntimeScene().getGame(), normalMapName)", + " : null;", + " this.uniforms.waterNormalMapSize.value = Math.max(object._getNormalMapSize(), 1);", + " this.settings.waveHeight = Math.max(object._getWaveHeight(), 0);", + " this.settings.waveLength = Math.max(object._getWaveLength(), 1);", + " this.settings.waveSpeed = object._getWaveSpeed();", + " this.uniforms.waterWaveHeight.value = this.settings.waveHeight;", + " this.uniforms.waterWaveLength.value = this.settings.waveLength;", + " this.uniforms.waterWaveSpeed.value = this.settings.waveSpeed;", + " this._updateGeometryIfNeeded();", + " }", + "", + " /** Waves need vertices: their count follows the size of the water. */", + " _updateGeometryIfNeeded() {", + " const { object } = this;", + " const hasWaves = this.settings.waveHeight > 0;", + " const segmentsX = hasWaves", + " ? gdjs.evtTools.common.clamp(Math.ceil(object.getWidth() / WAVE_VERTEX_SPACING), 1, MAX_SEGMENTS)", + " : 1;", + " const segmentsY = hasWaves", + " ? gdjs.evtTools.common.clamp(Math.ceil(object.getHeight() / WAVE_VERTEX_SPACING), 1, MAX_SEGMENTS)", + " : 1;", + " this.settings.vertexSpacing = Math.max(object.getWidth() / segmentsX, object.getHeight() / segmentsY);", + " this.uniforms.waterVertexSpacing.value = this.settings.vertexSpacing;", + " const waveHeight = this.settings.waveHeight / Math.max(object.getScaleZ(), 0.0001);", + " if (segmentsX !== this.segmentsX || segmentsY !== this.segmentsY) {", + " this.segmentsX = segmentsX;", + " this.segmentsY = segmentsY;", + " const geometry = new THREE.PlaneGeometry(AREA_SIZE, AREA_SIZE, segmentsX, segmentsY);", + " geometry.translate(AREA_SIZE / 2, AREA_SIZE / 2, 0);", + " geometry.boundingBox = new THREE.Box3();", + " geometry.boundingSphere = new THREE.Sphere();", + " this.mesh.geometry.dispose();", + " this.mesh.geometry = geometry;", + " }", + " // Waves go above and under the plane.", + " const { boundingBox, boundingSphere } = this.mesh.geometry;", + " if (boundingBox.max.z !== waveHeight) {", + " boundingBox.min.set(0, 0, -waveHeight);", + " boundingBox.max.set(AREA_SIZE, AREA_SIZE, waveHeight);", + " boundingBox.getBoundingSphere(boundingSphere);", + " }", + " }", + "", + " /** @returns {number} The Z position of the water surface, with the waves. */", + " getSurfaceZ(x, y) {", + " return this.object.getZ() + getWaveHeight(x, y, this.time, this.settings);", + " }", + "", + " /** @returns {boolean} true if an object center is under the surface. */", + " isUnderwater(object) {", + " if (!gdjs.Base3DHandler.is3D(object)) return false;", + " const x = object.getCenterXInScene();", + " const y = object.getCenterYInScene();", + " return this.isOver(x, y) && object.getCenterZInScene() < this.getSurfaceZ(x, y);", + " }", + "", + " /** @returns {boolean} true if the position is above the water, inside its area. */", + " isOver(x, y) {", + " const { object } = this;", + " return (", + " x >= object.getX() &&", + " x <= object.getX() + object.getWidth() &&", + " y >= object.getY() &&", + " y <= object.getY() + object.getHeight()", + " );", + " }", + "", + " /**", + " * Push objects up where they are under the surface, and slow them down.", + " * @param {gdjs.RuntimeObject[]} objects", + " * @param {string} physicsBehaviorName", + " * @param {number} buoyancy 1 to float half submerged, higher to float higher.", + " */", + " applyBuoyancy(objects, physicsBehaviorName, buoyancy) {", + " for (const object of objects) {", + " /** @type {gdjs.Physics3DRuntimeBehavior | null} */", + " const physics = object.getBehavior(physicsBehaviorName);", + " if (!physics || !physics.isDynamic()) continue;", + " const centerX = object.getCenterXInScene();", + " const centerY = object.getCenterYInScene();", + " if (!this.isOver(centerX, centerY)) continue;", + " const depth = Math.max(object.getDepth(), 1);", + " const surfaceZ = this.getSurfaceZ(centerX, centerY);", + " const submergedRatio = gdjs.evtTools.common.clamp((surfaceZ - physics.owner3D.getUnrotatedAABBMinZ()) / depth, 0, 1);", + " if (submergedRatio <= 0) continue;", + " // Forces are in newtons, velocities are converted from pixels to meters.", + " const gravityZ = physics.getGravityZ();", + " const worldScale = physics.getWorldScale();", + " const mass = physics.getMass();", + " // Archimedes: floats with half of its depth submerged when buoyancy is 1.", + " const buoyancyForce = -2 * buoyancy * submergedRatio * mass * gravityZ;", + " // Water drag, to stop bouncing forever.", + " const drag = (4 * submergedRatio * mass) / worldScale;", + " physics.applyForceAtCenter(", + " -physics.getLinearVelocityX() * drag,", + " -physics.getLinearVelocityY() * drag,", + " buoyancyForce - physics.getLinearVelocityZ() * drag", + " );", + " }", + " }", + "", + " /** @param {number} time In seconds, the same for all water objects. */", + " update(time) {", + " this.time = time;", + " const scene = this.object.getRuntimeScene();", + " // 1 in GDevelop versions before the world scale.", + " this.uniforms.waterWorldScale.value =", + " typeof scene.getRenderer3DWorldScale === 'function' ? scene.getRenderer3DWorldScale() : 1;", + " this.uniforms.waterTime.value = time;", + " this.uniforms.waterRippleTime.value = time % 1000;", + " this._updateGeometryIfNeeded();", + " }", + "", + " dispose() {", + " this.mesh.removeFromParent();", + " this.mesh.geometry.dispose();", + " this.raycastGeometry.dispose();", + " this.material.dispose();", + " }", + "}", + "", + "gdjs.__water3DExtension = { Water, getWaveHeight };" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [], + "description": "Define helper classes JavaScript code.", + "private": true + } + ], + "eventsBasedBehaviors": [], + "eventsBasedObjects": [ + { + "areaMaxX": 1000, + "areaMaxY": 1000, + "areaMaxZ": 1, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "defaultName": "Water", + "description": "A water surface with waves, for seas, lakes and rivers. Objects can float on it.", + "fullName": "3D water", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXdhdmVzIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTIwLDEySDIyVjE0SDIwQzE4LjYyLDE0IDE3LjI2LDEzLjY1IDE2LDEzQzEzLjUsMTQuMyAxMC41LDE0LjMgOCwxM0M2Ljc0LDEzLjY1IDUuMzcsMTQgNCwxNEgyVjEySDRDNS4zOSwxMiA2Ljc4LDExLjUzIDgsMTAuNjdDMTAuNDQsMTIuMzggMTMuNTYsMTIuMzggMTYsMTAuNjdDMTcuMjIsMTEuNTMgMTguNjEsMTIgMjAsMTJNMjAsNkgyMlY4SDIwQzE4LjYyLDggMTcuMjYsNy42NSAxNiw3QzEzLjUsOC4zIDEwLjUsOC4zIDgsN0M2Ljc0LDcuNjUgNS4zNyw4IDQsOEgyVjZINEM1LjM5LDYgNi43OCw1LjUzIDgsNC42N0MxMC40NCw2LjM4IDEzLjU2LDYuMzggMTYsNC42N0MxNy4yMiw1LjUzIDE4LjYxLDYgMjAsNk0yMCwxOEgyMlYyMEgyMEMxOC42MiwyMCAxNy4yNiwxOS42NSAxNiwxOUMxMy41LDIwLjMgMTAuNSwyMC4zIDgsMTlDNi43NCwxOS42NSA1LjM3LDIwIDQsMjBIMlYxOEg0QzUuMzksMTggNi43OCwxNy41MyA4LDE2LjY3QzEwLjQ0LDE4LjM4IDEzLjU2LDE4LjM4IDE2LDE2LjY3QzE3LjIyLDE3LjUzIDE4LjYxLDE4IDIwLDE4WiIgLz48L3N2Zz4=", + "is3D": true, + "isUsingLegacyInstancesRenderer": true, + "name": "Water3D", + "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/waves.svg", + "objects": [ + { + "assetStoreId": "", + "name": "AreaPlaceholder", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 1000, + "height": 1000, + "depth": 1, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "Basic", + "tint": "255;255;255", + "frontFaceResourceName": "", + "frontFaceVisible": false, + "frontFaceResourceRepeat": false, + "backFaceResourceName": "", + "backFaceVisible": false, + "backFaceResourceRepeat": false, + "leftFaceResourceName": "", + "leftFaceVisible": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceName": "", + "rightFaceVisible": false, + "rightFaceResourceRepeat": false, + "topFaceResourceName": "", + "topFaceVisible": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceName": "", + "bottomFaceVisible": false, + "bottomFaceResourceRepeat": false + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "AreaPlaceholder" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [ + { + "angle": 0, + "customSize": true, + "width": 1000, + "height": 1000, + "depth": 1, + "keepRatio": true, + "layer": "", + "name": "AreaPlaceholder", + "persistentUuid": "5d7a8b1e-3c2f-4e6a-9b1d-001000001000", + "x": 0, + "y": 0, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": {}, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Water3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "for (const child of object.getChildrenContainer().getAdhocListOfAllInstances()) {", + " child.deleteFromScene();", + "}", + "if (object.__water3D) object.__water3D.dispose();", + "object.__water3D = new gdjs.__water3DExtension.Water(object);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Water3D::Water3D" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDestroy", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__water3D.dispose();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Water3D::Water3D" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const game = runtimeScene.getGame();", + "// Time doesn't pass in the scene editor: real time is used to show the waves.", + "const time = game.isInGameEdition && game.isInGameEdition()", + " ? performance.now() / 1000", + " : object.getRuntimeScene().getTimeManager().getTimeFromStart() / 1000;", + "object.__water3D.update(time);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Water3D::Water3D" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__water3D.updateFromProperties();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Water3D::Water3D" + } + ], + "objectGroups": [] + }, + { + "fullName": "Water surface Z position", + "functionType": "ExpressionAndCondition", + "name": "SurfaceZ", + "sentence": "the water surface Z position at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__water3D.getSurfaceZ(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Water3D::Water3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the Z position of the water surface, with the waves, at a position of the scene.", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Object is underwater", + "functionType": "Condition", + "name": "IsUnderwater", + "sentence": "_PARAM1_ is under the water of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const water = object.__water3D;", + "eventsFunctionContext.returnValue = gdjs.evtTools.object.pickObjectsIf(", + " (objectToCheck) => water.isUnderwater(objectToCheck),", + " eventsFunctionContext.getObjectsLists(\"Objects\"),", + " false,", + " null", + ");" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Water3D::Water3D" + }, + { + "description": "Objects", + "name": "Objects", + "type": "objectList" + } + ], + "objectGroups": [], + "description": "Check if the center of an object is under the water surface." + }, + { + "fullName": "Make objects float", + "functionType": "Action", + "name": "ApplyBuoyancy", + "sentence": "Make _PARAM1_ float on _PARAM0_ with a buoyancy of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const objectsToFloat = eventsFunctionContext.getObjects(\"Objects\");", + "const behaviorName = eventsFunctionContext.getBehaviorName(\"Physics3D\");", + "const buoyancy = eventsFunctionContext.getArgument(\"Buoyancy\");", + "object.__water3D.applyBuoyancy(objectsToFloat, behaviorName, buoyancy);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Water3D::Water3D" + }, + { + "description": "Objects to make float", + "name": "Objects", + "type": "objectList" + }, + { + "description": "3D physics behavior", + "name": "Physics3D", + "type": "behavior", + "supplementaryInformation": "Physics3D::Physics3DBehavior" + }, + { + "description": "Buoyancy", + "name": "Buoyancy", + "type": "expression", + "longDescription": "1 makes objects float half submerged. Lower values make them sink, higher values float higher.", + "defaultValue": "1" + } + ], + "objectGroups": [], + "description": "Push objects with the 3D physics behavior up when they are in the water, and slow them down. Run this action every frame." + } + ], + "propertyDescriptors": [ + { + "value": "40;120;170", + "type": "Color", + "label": "Color", + "group": "Color", + "name": "Color" + }, + { + "value": "200", + "type": "Number", + "label": "Opacity", + "description": "From 0 (invisible) to 255 (opaque).", + "group": "Color", + "name": "Opacity" + }, + { + "value": "140;210;220", + "type": "Color", + "label": "Color of wave crests", + "description": "Also the color of the sky reflected on the water, seen from the side.", + "group": "Color", + "name": "CrestColor" + }, + { + "value": "0.3", + "type": "Number", + "label": "Foam", + "description": "From 0 (no foam) to 1 (a lot).", + "group": "Color", + "name": "Foam" + }, + { + "value": "6", + "type": "Number", + "label": "Wave height", + "unit": "Pixel", + "description": "0 for flat water. Waves shorter than a few times the size of the water divided by 256 don't move the surface: they are only drawn with the light.", + "group": "Waves", + "name": "WaveHeight" + }, + { + "value": "300", + "type": "Number", + "label": "Wave length", + "unit": "Pixel", + "group": "Waves", + "name": "WaveLength" + }, + { + "value": "1.5", + "type": "Number", + "label": "Wave speed", + "group": "Waves", + "name": "WaveSpeed" + }, + { + "value": "", + "type": "Resource", + "label": "Ripples normal map", + "description": "Optional. A seamless normal map of small waves, moving on the surface to reflect the light like real water.", + "group": "Ripples", + "extraInformation": [ + "image" + ], + "name": "NormalMap" + }, + { + "value": "600", + "type": "Number", + "label": "Ripples size", + "unit": "Pixel", + "description": "The size of the normal map on the surface.", + "group": "Ripples", + "name": "NormalMapSize" + } + ] + } + ] + } + ], + "externalLayouts": [] +} diff --git a/examples/starting-expanded-3D-platformer/theme-slots.json b/examples/starting-expanded-3D-platformer/theme-slots.json new file mode 100644 index 000000000..abe9a4b49 --- /dev/null +++ b/examples/starting-expanded-3D-platformer/theme-slots.json @@ -0,0 +1,73 @@ +{ + "version": 1, + "objects": { + "scene:Game Scene/Player": "character.player", + "scene:Game Scene/Obstacle": { + "front": "env.wall", + "left": "env.wall", + "right": "env.wall", + "top": "env.wall", + "bottom": "env.wall" + }, + "scene:Game Scene/Coin": "pickup.coin", + "scene:Game Scene/PushableBox": { + "front": "env.crate", + "left": "env.crate", + "right": "env.crate", + "top": "env.crate", + "bottom": "env.crate" + }, + "scene:Game Scene/Trees": { + "Model": "nature.tree" + }, + "scene:Game Scene/PineTrees": { + "Model": "nature.tree" + }, + "scene:Game Scene/Rocks": { + "Model": "nature.rock.big" + }, + "scene:Game Scene/Stones": { + "Model": "nature.rock.small" + }, + "scene:Game Scene/Flowers": { + "Model": "nature.grass" + }, + "scene:Game Scene/Mushrooms": { + "Model": "nature.grass" + }, + "scene:Game Scene/Terrain": { + "Layer1Texture": { + "slot": "terrain.ground", + "normalMap": "Layer1NormalMap" + }, + "Layer2Texture": { + "slot": "terrain.dirt", + "normalMap": "Layer2NormalMap" + }, + "Layer3Texture": { + "slot": "terrain.rock", + "normalMap": "Layer3NormalMap" + }, + "Layer4Texture": { + "slot": "terrain.sand", + "normalMap": "Layer4NormalMap" + } + }, + "scene:Game Scene/Grass": { + "BaseColor": "terrain.grass.base", + "TipColor": "terrain.grass.tip" + } + }, + "effects": { + "effect:Game Scene//SkyBox": "sky.day" + }, + "ignoredObjects": [ + "scene:Game Scene/Flag", + "scene:Game Scene/Sign", + "scene:Game Scene/MapLimit", + "object:Water3D::Water3D/AreaPlaceholder", + "object:Terrain3D::Terrain3D/AreaPlaceholder", + "object:Terrain3D::TerrainGrass/AreaPlaceholder", + "object:Terrain3D::ScatteredModels/AreaPlaceholder" + ] +} diff --git a/examples/starting-expanded-3d-car-racing/README.md b/examples/starting-expanded-3d-car-racing/README.md new file mode 100644 index 000000000..bd1fde9b7 --- /dev/null +++ b/examples/starting-expanded-3d-car-racing/README.md @@ -0,0 +1,3 @@ +# 3D Racing Expanded + +A 3D car racing game with a third-person camera view, on a race track made with a 3D terrain: a start straight with grandstands and pits, a climb up a hill, hairpins and a descent through a wooded valley. The player accelerates and steers using keyboard or mobile controls, and the car uses physics-based movement. Checkpoints along the track count completed laps and measure lap times, and the UI displays the current lap number and most recent lap time. diff --git a/examples/starting-expanded-3d-car-racing/assets/Barrier Red.glb b/examples/starting-expanded-3d-car-racing/assets/Barrier Red.glb new file mode 100644 index 000000000..0bcc52385 Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/Barrier Red.glb differ diff --git a/examples/starting-expanded-3d-car-racing/assets/Barrier White.glb b/examples/starting-expanded-3d-car-racing/assets/Barrier White.glb new file mode 100644 index 000000000..9a43f25a3 Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/Barrier White.glb differ diff --git a/examples/starting-expanded-3d-car-racing/assets/Billboard.glb b/examples/starting-expanded-3d-car-racing/assets/Billboard.glb new file mode 100644 index 000000000..7a576c181 Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/Billboard.glb differ diff --git a/examples/starting-expanded-3d-car-racing/assets/Flag Checkers.glb b/examples/starting-expanded-3d-car-racing/assets/Flag Checkers.glb new file mode 100644 index 000000000..58e144a0c Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/Flag Checkers.glb differ diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Flat dark joystick border horizontal.png b/examples/starting-expanded-3d-car-racing/assets/Flat dark joystick border horizontal.png similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Flat dark joystick border horizontal.png rename to examples/starting-expanded-3d-car-racing/assets/Flat dark joystick border horizontal.png diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Flat dark joystick border vertical.png b/examples/starting-expanded-3d-car-racing/assets/Flat dark joystick border vertical.png similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Flat dark joystick border vertical.png rename to examples/starting-expanded-3d-car-racing/assets/Flat dark joystick border vertical.png diff --git a/examples/starting-expanded-3d-car-racing/assets/Flat dark joystick thumb.png b/examples/starting-expanded-3d-car-racing/assets/Flat dark joystick thumb.png new file mode 100644 index 000000000..8acf179f8 Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/Flat dark joystick thumb.png differ diff --git a/examples/starting-expanded-3d-car-racing/assets/Grand Stand Covered.glb b/examples/starting-expanded-3d-car-racing/assets/Grand Stand Covered.glb new file mode 100644 index 000000000..bd0c9334e Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/Grand Stand Covered.glb differ diff --git a/examples/starting-expanded-3d-car-racing/assets/NotoSansArabicUI-Black.ttf b/examples/starting-expanded-3d-car-racing/assets/NotoSansArabicUI-Black.ttf new file mode 100644 index 000000000..9541c2cf3 Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/NotoSansArabicUI-Black.ttf differ diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Orange - Sedan Sports.glb b/examples/starting-expanded-3d-car-racing/assets/Orange - Sedan Sports.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Orange - Sedan Sports.glb rename to examples/starting-expanded-3d-car-racing/assets/Orange - Sedan Sports.glb diff --git a/examples/starting-expanded-3d-car-racing/assets/Overhead Lights.glb b/examples/starting-expanded-3d-car-racing/assets/Overhead Lights.glb new file mode 100644 index 000000000..0e8b373a5 Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/Overhead Lights.glb differ diff --git a/examples/starting-expanded-3d-car-racing/assets/Pits Garage.glb b/examples/starting-expanded-3d-car-racing/assets/Pits Garage.glb new file mode 100644 index 000000000..ca0426fd9 Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/Pits Garage.glb differ diff --git a/examples/starting-expanded-3d-car-racing/assets/Pits Office Roof.glb b/examples/starting-expanded-3d-car-racing/assets/Pits Office Roof.glb new file mode 100644 index 000000000..ca322376b Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/Pits Office Roof.glb differ diff --git a/examples/starting-expanded-3d-car-racing/assets/Rock Large A.glb b/examples/starting-expanded-3d-car-racing/assets/Rock Large A.glb new file mode 100644 index 000000000..7add51429 Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/Rock Large A.glb differ diff --git a/examples/starting-expanded-3d-car-racing/assets/Skybox_Back.png b/examples/starting-expanded-3d-car-racing/assets/Skybox_Back.png new file mode 100644 index 000000000..aa0891ef2 Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/Skybox_Back.png differ diff --git a/examples/starting-expanded-3d-car-racing/assets/Skybox_Bottom.png b/examples/starting-expanded-3d-car-racing/assets/Skybox_Bottom.png new file mode 100644 index 000000000..16e34f3da Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/Skybox_Bottom.png differ diff --git a/examples/starting-expanded-3d-car-racing/assets/Skybox_Front.png b/examples/starting-expanded-3d-car-racing/assets/Skybox_Front.png new file mode 100644 index 000000000..018d5faac Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/Skybox_Front.png differ diff --git a/examples/starting-expanded-3d-car-racing/assets/Skybox_Left.png b/examples/starting-expanded-3d-car-racing/assets/Skybox_Left.png new file mode 100644 index 000000000..e4053f388 Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/Skybox_Left.png differ diff --git a/examples/starting-expanded-3d-car-racing/assets/Skybox_Right.png b/examples/starting-expanded-3d-car-racing/assets/Skybox_Right.png new file mode 100644 index 000000000..d14c192b1 Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/Skybox_Right.png differ diff --git a/examples/starting-expanded-3d-car-racing/assets/Skybox_Top.png b/examples/starting-expanded-3d-car-racing/assets/Skybox_Top.png new file mode 100644 index 000000000..e83ef6a1b Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/Skybox_Top.png differ diff --git a/examples/starting-expanded-3d-car-racing/assets/StartingCheckpointArrow.png b/examples/starting-expanded-3d-car-racing/assets/StartingCheckpointArrow.png new file mode 100644 index 000000000..89e116751 Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/StartingCheckpointArrow.png differ diff --git a/examples/starting-expanded-3d-car-racing/assets/StartingFinishLine.png b/examples/starting-expanded-3d-car-racing/assets/StartingFinishLine.png new file mode 100644 index 000000000..8d67fb47e Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/StartingFinishLine.png differ diff --git a/examples/starting-expanded-3d-car-racing/assets/Tent Roof.glb b/examples/starting-expanded-3d-car-racing/assets/Tent Roof.glb new file mode 100644 index 000000000..e01adc65b Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/Tent Roof.glb differ diff --git a/examples/starting-expanded-3d-car-racing/assets/TerrainAsphalt.jpg b/examples/starting-expanded-3d-car-racing/assets/TerrainAsphalt.jpg new file mode 100644 index 000000000..29647465d Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/TerrainAsphalt.jpg differ diff --git a/examples/starting-expanded-3d-car-racing/assets/TerrainAsphaltNormal.jpg b/examples/starting-expanded-3d-car-racing/assets/TerrainAsphaltNormal.jpg new file mode 100644 index 000000000..e45282c97 Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/TerrainAsphaltNormal.jpg differ diff --git a/examples/starting-expanded-3d-car-racing/assets/TerrainGrass.jpg b/examples/starting-expanded-3d-car-racing/assets/TerrainGrass.jpg new file mode 100644 index 000000000..220cddc0d Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/TerrainGrass.jpg differ diff --git a/examples/starting-expanded-3d-car-racing/assets/TerrainGrassNormal.jpg b/examples/starting-expanded-3d-car-racing/assets/TerrainGrassNormal.jpg new file mode 100644 index 000000000..cd5053235 Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/TerrainGrassNormal.jpg differ diff --git a/examples/starting-expanded-3d-car-racing/assets/TerrainGravel.jpg b/examples/starting-expanded-3d-car-racing/assets/TerrainGravel.jpg new file mode 100644 index 000000000..2a46a0937 Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/TerrainGravel.jpg differ diff --git a/examples/starting-expanded-3d-car-racing/assets/TerrainGravelNormal.jpg b/examples/starting-expanded-3d-car-racing/assets/TerrainGravelNormal.jpg new file mode 100644 index 000000000..a63a2771c Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/TerrainGravelNormal.jpg differ diff --git a/examples/starting-expanded-3d-car-racing/assets/TerrainRock.jpg b/examples/starting-expanded-3d-car-racing/assets/TerrainRock.jpg new file mode 100644 index 000000000..d838e88fb Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/TerrainRock.jpg differ diff --git a/examples/starting-expanded-3d-car-racing/assets/TerrainRockNormal.jpg b/examples/starting-expanded-3d-car-racing/assets/TerrainRockNormal.jpg new file mode 100644 index 000000000..7c8288e66 Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/TerrainRockNormal.jpg differ diff --git a/examples/starting-expanded-3d-car-racing/assets/Traffic Cone.glb b/examples/starting-expanded-3d-car-racing/assets/Traffic Cone.glb new file mode 100644 index 000000000..5c95979e1 Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/Traffic Cone.glb differ diff --git a/examples/starting-expanded-3d-car-racing/assets/Tree Default.glb b/examples/starting-expanded-3d-car-racing/assets/Tree Default.glb new file mode 100644 index 000000000..a9d653635 Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/Tree Default.glb differ diff --git a/examples/starting-expanded-3d-car-racing/assets/Tree Pine Default A.glb b/examples/starting-expanded-3d-car-racing/assets/Tree Pine Default A.glb new file mode 100644 index 000000000..03b620f7f Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/assets/Tree Pine Default A.glb differ diff --git a/examples/starting-expanded-3d-car-racing/preview.png b/examples/starting-expanded-3d-car-racing/preview.png new file mode 100644 index 000000000..7bb7283db Binary files /dev/null and b/examples/starting-expanded-3d-car-racing/preview.png differ diff --git a/examples/starting-expanded-3d-car-racing/starting-expanded-3d-car-racing.json b/examples/starting-expanded-3d-car-racing/starting-expanded-3d-car-racing.json new file mode 100644 index 000000000..51b454b14 --- /dev/null +++ b/examples/starting-expanded-3d-car-racing/starting-expanded-3d-car-racing.json @@ -0,0 +1,22384 @@ +{ + "firstLayout": "", + "gdVersion": { + "build": 283, + "major": 5, + "minor": 6, + "revision": 0 + }, + "properties": { + "adaptGameResolutionAtRuntime": true, + "antialiasingMode": "MSAA", + "antialisingEnabledOnMobile": false, + "folderProject": false, + "orientation": "landscape", + "packageName": "com.example.gamename", + "pixelsRounding": false, + "projectUuid": "a2514b43-d603-5db5-a47f-6f1475496353", + "scaleMode": "linear", + "sizeOnStartupMode": "", + "templateSlug": "starting-3d-driving", + "version": "1.0.0", + "name": "3D Racing Expanded", + "description": "A 3D racing game on a track made with a 3D terrain: climbs, descents and hairpins, with grandstands, pits and barriers.", + "author": "", + "windowWidth": 1280, + "windowHeight": 720, + "latestCompilationDirectory": "", + "maxFPS": 60, + "minFPS": 20, + "verticalSync": false, + "platformSpecificAssets": { + "android-icon-144": "", + "android-icon-192": "", + "android-icon-36": "", + "android-icon-48": "", + "android-icon-72": "", + "android-icon-96": "", + "android-windowSplashScreenAnimatedIcon": "", + "desktop-icon-512": "", + "ios-icon-100": "", + "ios-icon-1024": "", + "ios-icon-114": "", + "ios-icon-120": "", + "ios-icon-144": "", + "ios-icon-152": "", + "ios-icon-167": "", + "ios-icon-180": "", + "ios-icon-20": "", + "ios-icon-29": "", + "ios-icon-40": "", + "ios-icon-50": "", + "ios-icon-57": "", + "ios-icon-58": "", + "ios-icon-60": "", + "ios-icon-72": "", + "ios-icon-76": "", + "ios-icon-80": "", + "ios-icon-87": "" + }, + "loadingScreen": { + "backgroundColor": 0, + "backgroundFadeInDuration": 0.2, + "backgroundImageResourceName": "", + "gdevelopLogoStyle": "light", + "logoAndProgressFadeInDuration": 0.2, + "logoAndProgressLogoFadeInDelay": 0, + "minDuration": 1.5, + "progressBarColor": 16777215, + "progressBarHeight": 20, + "progressBarMaxWidth": 200, + "progressBarMinWidth": 40, + "progressBarWidthPercent": 30, + "showGDevelopSplash": true, + "showProgressBar": true + }, + "watermark": { + "placement": "bottom-left", + "showWatermark": true + }, + "authorIds": [], + "authorUsernames": [], + "categories": [], + "playableDevices": [ + "mobile" + ], + "extensionProperties": [], + "platforms": [ + { + "name": "GDevelop JS platform" + } + ], + "currentPlatform": "GDevelop JS platform" + }, + "resources": { + "resources": [ + { + "file": "assets/Flat dark joystick thumb.png", + "kind": "image", + "metadata": "", + "name": "assets\\Flat dark joystick thumb.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Orange - Sedan Sports.glb", + "kind": "model3D", + "metadata": "", + "name": "assets\\Orange - Sedan Sports.glb", + "userAdded": false + }, + { + "file": "assets/Traffic Cone.glb", + "kind": "model3D", + "metadata": "", + "name": "Traffic Cone.glb", + "userAdded": false, + "origin": { + "identifier": "https://asset-resources.gdevelop.io/public-resources/3D Toon Shooter/Prop/341d0630877a91adc1813baeb761051a5f9768cdbd5a083a7d8b1972a187ed34_Traffic Cone.glb", + "name": "Traffic Cone.glb" + } + }, + { + "file": "assets/StartingFinishLine.png", + "kind": "image", + "metadata": "", + "name": "assets\\StartingFinishLine.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/NotoSansArabicUI-Black.ttf", + "kind": "font", + "metadata": "", + "name": "assets\\NotoSansArabicUI-Black.ttf", + "userAdded": false + }, + { + "file": "assets/StartingCheckpointArrow.png", + "kind": "image", + "metadata": "", + "name": "assets\\StartingCheckpointArrow.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Front.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Front.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Back.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Back.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Top.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Top.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Bottom.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Bottom.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Left.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Left.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Right.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Right.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Flat dark joystick border vertical.png", + "kind": "image", + "metadata": "", + "name": "assets/Flat dark joystick border vertical.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Flat dark joystick border horizontal.png", + "kind": "image", + "metadata": "", + "name": "assets/Flat dark joystick border horizontal.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/TerrainGrass.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainGrass.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainGrassNormal.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainGrassNormal.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainAsphalt.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainAsphalt.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainAsphaltNormal.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainAsphaltNormal.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainRock.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainRock.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainRockNormal.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainRockNormal.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainGravel.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainGravel.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainGravelNormal.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainGravelNormal.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/Grand Stand Covered.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Grand Stand Covered.glb", + "userAdded": true + }, + { + "file": "assets/Pits Garage.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Pits Garage.glb", + "userAdded": true + }, + { + "file": "assets/Pits Office Roof.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Pits Office Roof.glb", + "userAdded": true + }, + { + "file": "assets/Overhead Lights.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Overhead Lights.glb", + "userAdded": true + }, + { + "file": "assets/Billboard.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Billboard.glb", + "userAdded": true + }, + { + "file": "assets/Barrier White.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Barrier White.glb", + "userAdded": true + }, + { + "file": "assets/Barrier Red.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Barrier Red.glb", + "userAdded": true + }, + { + "file": "assets/Tent Roof.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Tent Roof.glb", + "userAdded": true + }, + { + "file": "assets/Flag Checkers.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Flag Checkers.glb", + "userAdded": true + }, + { + "file": "assets/Tree Default.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Tree Default.glb", + "userAdded": true + }, + { + "file": "assets/Tree Pine Default A.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Tree Pine Default A.glb", + "userAdded": true + }, + { + "file": "assets/Rock Large A.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Rock Large A.glb", + "userAdded": true + } + ] + }, + "objects": [], + "objectsFolderStructure": { + "folderName": "__ROOT" + }, + "objectsGroups": [], + "variables": [], + "layouts": [ + { + "b": 255, + "disableInputWhenNotFocused": true, + "mangledName": "Game_32Scene", + "name": "Game Scene", + "r": 255, + "standardSortMethod": true, + "stopSoundsOnStartup": true, + "title": "", + "v": 255, + "uiSettings": { + "grid": false, + "gridType": "rectangular", + "gridWidth": 64, + "gridHeight": 64, + "gridDepth": 32, + "gridOffsetX": 0, + "gridOffsetY": 0, + "gridOffsetZ": 0, + "gridColor": 10401023, + "gridAlpha": 0.8, + "snap": false, + "zoomFactor": 0.49508400737491426, + "windowMask": false, + "selectedLayer": "", + "gameEditorMode": "embedded-game" + }, + "objectsGroups": [], + "variables": [], + "instances": [ + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "MobileControls", + "locked": true, + "name": "Speed_Joystick", + "persistentUuid": "98bcbea1-1b37-46a3-8686-ff716b1b1024", + "width": 0, + "x": 1072, + "y": 537, + "zOrder": 9, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "MobileControls", + "locked": true, + "name": "Steer_Joystick", + "persistentUuid": "6c0d6cbc-a8d1-4930-b2e4-a83ac76f2d7e", + "width": 0, + "x": 208, + "y": 537, + "zOrder": 10, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 1, + "height": 128, + "keepRatio": true, + "layer": "UI", + "locked": true, + "name": "LapText", + "persistentUuid": "0df2e3be-2714-4f3a-9d8d-070d5aafb98e", + "width": 416, + "x": 32, + "y": 32, + "zOrder": 13, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 8192, + "width": 8192, + "layer": "", + "locked": false, + "name": "Terrain", + "persistentUuid": "54a3e997-9aa7-5ebe-8d70-0e5715041e5e", + "x": -4096, + "y": -4096, + "z": 0, + "zOrder": 0, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 1600 + }, + { + "angle": 0, + "customSize": true, + "height": 200, + "width": 8592, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "afcfd0ad-14ce-536f-882e-ed5b953ecc7d", + "x": -4296, + "y": -4296, + "z": -500, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 4000 + }, + { + "angle": 0, + "customSize": true, + "height": 200, + "width": 8592, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "96e2dbd7-ac3b-561f-ae03-14ebd3e0e843", + "x": -4296, + "y": 4096, + "z": -500, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 4000 + }, + { + "angle": 0, + "customSize": true, + "height": 8192, + "width": 200, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "6ec39b88-db74-5835-96a5-118ef2153ee8", + "x": -4296, + "y": -4096, + "z": -500, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 4000 + }, + { + "angle": 0, + "customSize": true, + "height": 8192, + "width": 200, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "4aae8bf6-7a86-5c10-a5d5-a2f12e77760d", + "x": 4096, + "y": -4096, + "z": -500, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 4000 + }, + { + "angle": 0, + "customSize": true, + "height": 8192, + "width": 8192, + "layer": "", + "locked": false, + "name": "Grass", + "persistentUuid": "0090ef9e-f632-545e-a9e9-d5c1e746c1e4", + "x": -4096, + "y": -4096, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 40 + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "PlayerCar", + "persistentUuid": "6dafd803-ec02-5ce9-8663-cd5771a807ee", + "x": -1600, + "y": 2600, + "z": 0, + "zOrder": 8, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": true, + "height": 128, + "width": 440, + "layer": "", + "locked": false, + "name": "FinishLine_3D", + "persistentUuid": "09396370-b173-54ea-bdb1-8f8762cf2618", + "x": -756, + "y": 2536, + "z": 0.5, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 2 + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "OverheadLights", + "persistentUuid": "770c0113-32dc-5ef4-ad4b-74c2fc5c0345", + "x": -536, + "y": 2600, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "GrandStand", + "persistentUuid": "e034c155-8523-5aaf-bf19-8fe971548c3c", + "x": -2100, + "y": 2080, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "GrandStand", + "persistentUuid": "9d2ef138-9c15-509e-b140-43deb1150362", + "x": -1600, + "y": 2080, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "GrandStand", + "persistentUuid": "0ad8e5ce-d303-5006-8ed5-7fbf81add31e", + "x": -1100, + "y": 2080, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "GrandStand", + "persistentUuid": "f633c05b-6244-5ab0-a838-86f3ab2caf2a", + "x": -100, + "y": 2080, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "GrandStand", + "persistentUuid": "e8c140a8-0a3f-5aa6-98f5-93c088872652", + "x": 400, + "y": 2080, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "GrandStand", + "persistentUuid": "3ca8846b-6f7e-583e-ad44-a98f2d31a7f1", + "x": 900, + "y": 2080, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "PitsGarage", + "persistentUuid": "79fd681f-5bc9-57b0-ad07-ca4e3b92ae67", + "x": -1400, + "y": 3300, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "PitsGarage", + "persistentUuid": "30fcdfbd-abdc-5894-be18-93ff283fa5c4", + "x": -1080, + "y": 3300, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "PitsGarage", + "persistentUuid": "784e5e26-9a81-5cfb-bcaa-c9c9a14529b8", + "x": -760, + "y": 3300, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "PitsGarage", + "persistentUuid": "7a161555-cc1a-56cf-b80d-f95b4298d4f3", + "x": -440, + "y": 3300, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "PitsGarage", + "persistentUuid": "639c4bba-173a-5742-8839-226462a3d05a", + "x": -120, + "y": 3300, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "PitsGarage", + "persistentUuid": "5cd50169-0eb2-5347-a5d4-d32c04469ad1", + "x": 200, + "y": 3300, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "PitsOffice", + "persistentUuid": "737a32a1-7fd6-5d0b-8466-fe0ccab97da8", + "x": 560, + "y": 3300, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Tent", + "persistentUuid": "752fd2ec-7311-522b-ae12-5289370a41c7", + "x": -2400, + "y": 3300, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Tent", + "persistentUuid": "98e846fb-09e1-50d0-9b49-25c47550b2a1", + "x": -2050, + "y": 3300, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "FlagCheckers", + "persistentUuid": "26fb229b-f9a4-51d7-978c-f6714cc0cce8", + "x": -536, + "y": 2880, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "TrafficCone", + "persistentUuid": "6083c614-0731-5626-8294-39c41344d624", + "x": 800, + "y": 2860, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "TrafficCone", + "persistentUuid": "7f23fd7c-3d92-54ea-af63-c023e1cafd87", + "x": 864, + "y": 2860, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "TrafficCone", + "persistentUuid": "2794fcd5-81ec-5e1e-a6b8-8e52d2e93fa0", + "x": 928, + "y": 2860, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "TrafficCone", + "persistentUuid": "0d29f66f-d6aa-5875-b0ce-7aa416f38e91", + "x": 800, + "y": 2900, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "TrafficCone", + "persistentUuid": "679d5567-b048-50c2-a2cb-8dee23154b69", + "x": 864, + "y": 2900, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "TrafficCone", + "persistentUuid": "854a80c2-fd99-5ec8-82c4-7cc28c498133", + "x": 928, + "y": 2900, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "CheckpointArrow", + "persistentUuid": "9be29d69-e2fd-5c77-a7d2-51dded3919f7", + "x": -600, + "y": 2536, + "z": 2, + "zOrder": 2, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [ + { + "name": "FinishLine", + "type": "boolean", + "value": true + }, + { + "name": "Checkpoint", + "type": "number", + "value": 1 + } + ] + }, + { + "angle": -76, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "CheckpointArrow", + "persistentUuid": "18b599dc-e662-5bea-8eaf-d3c23d10b94a", + "x": 3008, + "y": 1363, + "z": 3.5, + "zOrder": 2, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [ + { + "name": "Checkpoint", + "type": "number", + "value": 2 + } + ] + }, + { + "angle": -122, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "CheckpointArrow", + "persistentUuid": "3266bbf1-de36-53be-93ed-c2a44701e6c7", + "x": 2773, + "y": -2600, + "z": 43.3, + "zOrder": 2, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [ + { + "name": "Checkpoint", + "type": "number", + "value": 3 + } + ] + }, + { + "angle": 156, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "CheckpointArrow", + "persistentUuid": "69f989c2-9e39-553a-89a4-a57b28a104ac", + "x": -147, + "y": -807, + "z": 2.3, + "zOrder": 2, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [ + { + "name": "Checkpoint", + "type": "number", + "value": 4 + } + ] + }, + { + "angle": 93, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "CheckpointArrow", + "persistentUuid": "c057f5ac-aa64-5a1b-a157-0b108318c3d3", + "x": -3352, + "y": -1212, + "z": 29.4, + "zOrder": 2, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [ + { + "name": "Checkpoint", + "type": "number", + "value": 5 + } + ] + }, + { + "angle": 11.3, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BarrierRed", + "persistentUuid": "c9f541c5-6a0f-58d6-86f8-d800eb64f2b5", + "x": -2358.6, + "y": 2903.9, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -38.7, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "fa0fa594-2716-5b31-8758-59c33dc0a06c", + "x": 2794.3, + "y": 2409.8, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -110.3, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "ca0b2325-1e91-5a70-977f-32a53af554f9", + "x": 2277.4, + "y": -1017.1, + "z": 227.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -93.5, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BarrierRed", + "persistentUuid": "fcc8a044-7e53-5296-b0c8-514167c0112d", + "x": 2219.5, + "y": -1292.5, + "z": 192.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -75.5, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "19b3eb85-7624-53f8-a567-762930b69c04", + "x": 2247.3, + "y": -1578.2, + "z": 161.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -73.4, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "90621fd7-4440-55b7-9851-aa2f6c182210", + "x": 3107.4, + "y": -2161.6, + "z": 35.7, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -96.1, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BarrierRed", + "persistentUuid": "b263c085-6987-5ce8-887c-9fdb8ac3655e", + "x": 3135.8, + "y": -2468.9, + "z": 23.9, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -122.1, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "7094365c-0439-58ff-bff2-fe29d5254646", + "x": 3029.7, + "y": -2774.6, + "z": 15.8, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -134.6, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BarrierRed", + "persistentUuid": "5521192c-f301-548d-b586-db8287d7400d", + "x": 2867.5, + "y": -2979.6, + "z": 7.7, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -160.8, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "ab498546-545e-5e08-bef4-d2c223ef5c23", + "x": 2255.1, + "y": -3366.8, + "z": 4.5, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -173.1, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BarrierRed", + "persistentUuid": "600ba14c-3b81-526e-8081-f0b0b459770d", + "x": 2000.9, + "y": -3425.9, + "z": 10, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 171.9, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "d8421f02-01d9-531d-8847-77e98316bc78", + "x": 1727.7, + "y": -3423.1, + "z": 8.9, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 130.7, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "0b79b9c8-4417-5c49-b351-7490e3bf229e", + "x": 512.2, + "y": -2767.7, + "z": 4.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 112.1, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BarrierRed", + "persistentUuid": "a42df3bd-1436-538e-9465-cfa17f17aadb", + "x": 360.7, + "y": -2519.5, + "z": 6.3, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 110.3, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "5bb8cccb-f77b-5a3c-bc8d-0b72ba4014c1", + "x": 748.6, + "y": -1329.1, + "z": 6.8, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 141.9, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "54e73f5e-aedf-5acc-a8ab-d6a9e21700f6", + "x": 174.3, + "y": -576.2, + "z": 0.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 155.7, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BarrierRed", + "persistentUuid": "b468eff6-2d45-5eea-8d0f-ef77d8f1abd7", + "x": -54.9, + "y": -437.6, + "z": 0.2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -177.7, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "41c0d16a-ca7b-548c-9a64-c07549fac99f", + "x": -375.3, + "y": -375.2, + "z": 0.3, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -151.9, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BarrierRed", + "persistentUuid": "7b383d56-2aff-5820-aeca-de84fcb4b5e7", + "x": -686.6, + "y": -460.2, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -139.1, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "a93c51f1-a9e5-50f1-a13e-a3ed1df0709a", + "x": -1819.1, + "y": -2558.1, + "z": 17, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -165.3, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BarrierRed", + "persistentUuid": "18824070-8879-555b-8038-edb80277e549", + "x": -2106.1, + "y": -2709.5, + "z": 21.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 168.7, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "eadbfa4f-2694-59ae-b991-665967528036", + "x": -2429.4, + "y": -2718.9, + "z": 24.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 153.8, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BarrierRed", + "persistentUuid": "575585cf-6aac-5043-8ccb-afa1c23b1479", + "x": -2687.4, + "y": -2631.3, + "z": 30.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 55.8, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "15757383-70a9-528d-babb-f77928b09b1f", + "x": -3079.9, + "y": 2441.4, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 41.3, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BarrierRed", + "persistentUuid": "6194b357-abb8-51c1-8c69-ca8ab2c16716", + "x": -2900.6, + "y": 2644.7, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 24.6, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "86dff372-de17-5ff1-b11e-bcf56dbf897f", + "x": -2637.6, + "y": 2810.2, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 82.6, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Billboard", + "persistentUuid": "bc9d0dab-c068-568d-b488-27442cfdcfa5", + "x": 1254.2, + "y": 3077, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -214, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Billboard", + "persistentUuid": "d2f9bf7d-e3b1-5a85-96fb-496f3f4e1863", + "x": 2398.1, + "y": -416.5, + "z": 179.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 40.7, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Billboard", + "persistentUuid": "3418e709-1121-5486-994c-ac394ff5530e", + "x": 1160.8, + "y": -2340.8, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -8.9, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Billboard", + "persistentUuid": "34d6966e-ef7b-5094-a4a2-076d0c7ea615", + "x": -2721.8, + "y": 1173.8, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 8192, + "width": 8192, + "layer": "", + "locked": true, + "name": "Trees", + "persistentUuid": "f8bc03ff-9f44-521d-8c40-13feca4a5ddf", + "x": -4096, + "y": -4096, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 8192, + "width": 8192, + "layer": "", + "locked": true, + "name": "PineTrees", + "persistentUuid": "f793ba12-cdda-55ba-98cb-9d14e0f47dec", + "x": -4096, + "y": -4096, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 8192, + "width": 8192, + "layer": "", + "locked": true, + "name": "Rocks", + "persistentUuid": "34577757-0aea-5c77-bad3-e2b9b032c0aa", + "x": -4096, + "y": -4096, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + } + ], + "objects": [ + { + "assetStoreId": "cddab55130dbd5a0b36f763200e876fb514aa608e1b057a136ebdb5f80006e2c", + "name": "PlayerCar", + "persistentUuid": "191c2cfe-430f-4fdb-9f74-2d8cb2fcd734", + "type": "Scene3D::Model3DObject", + "variables": [ + { + "name": "LastCheckPoint", + "persistentUuid": "1940c891-b587-424a-95ed-90a80ca00807", + "type": "number", + "value": 0 + }, + { + "name": "LapNumber", + "persistentUuid": "998b192e-cbb7-41bf-858e-e06bcca957a6", + "type": "number", + "value": 0 + } + ], + "effects": [], + "behaviors": [ + { + "isFolded": true, + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Dynamic", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "shapeOrientation": "Z", + "shapeDimensionA": 74, + "shapeDimensionB": 39, + "shapeDimensionC": 27, + "density": 1, + "friction": 10, + "restitution": 0.5, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 6, + "layers": 17, + "masks": 17, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 6, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": -25, + "massOverride": 1500 + }, + { + "isFolded": true, + "name": "PhysicsCar3D", + "type": "Physics3D::PhysicsCar3D", + "physics3D": "Physics3D", + "steerAngleMax": 70, + "beginningSteerSpeed": 140, + "endSteerSpeed": 10, + "mass": 1500, + "engineTorqueMax": 8000, + "engineSpeedMax": 6000, + "engineInertia": 0.1, + "reverseGearRatio1": -2.9, + "gearRatio1": 2.66, + "gearRatio2": 1.78, + "gearRatio3": 1.3, + "gearRatio4": 1, + "gearRatio5": 0.74, + "gearRatio6": 0, + "backWheelOffsetX": -6, + "frontWheelOffsetX": 10, + "wheelOffsetY": 3, + "wheelOffsetZ": 0, + "wheelRadius": 6, + "wheelWidth": 3, + "brakeTorqueMax": 3000, + "handBrakeTorqueMax": 6000, + "hasBackWheelDrive": false, + "hasFrontWheelDrive": true, + "pitchRollAngleMax": 89 + }, + { + "isFolded": true, + "name": "PhysicsCar3DKeyboardMapper", + "type": "PhysicsCar3DKeyMapper::PhysicsCar3DKeyboardMapper", + "PhysicsCar3D": "PhysicsCar3D", + "HandBrake": "Space", + "Up": "Up", + "Left": "Left", + "Down": "Down", + "Right": "Right" + }, + { + "name": "PhysicsCar3DMultitouchMapper", + "type": "SpriteMultitouchJoystick::PhysicsCar3DMultitouchMapper", + "PhysicsCar3D": "PhysicsCar3D", + "ControllerIdentifier": 1, + "SteerJoystickIdentifier": "Primary", + "SpeedJoystickIdentifier": "Secondary", + "HandBrakeButton": "B" + }, + { + "name": "ThirdPersonCamera", + "type": "ThirdPersonCamera::ThirdPersonCamera", + "Object3D": "Object3D", + "RotationHalfwayDuration": 0.125, + "TranslationZHalfwayDuration": 0.25, + "Distance": 450, + "LateralOffset": 0, + "AheadOffset": 200, + "OffsetZ": 0, + "RotationAngleOffset": 0, + "ElevationAngleOffset": 10, + "FollowFreeAreaZMax": 0, + "FollowFreeAreaZMin": 0, + "RotationLogSpeed": 2.0247e-320, + "TranslationZLogSpeed": 2.0247e-320, + "IsCalledManually": false, + "CameraZ": 0, + "LocalOffsetY": 200, + "LocalOffsetX": 0, + "OffsetY": 200, + "ElevationHalfwayDuration": 1, + "IsElevatingWithObject": false, + "OffsetX": 0 + } + ], + "content": { + "centerLocation": "ObjectCenter", + "crossfadeDuration": 0, + "depth": 33, + "height": 39, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets\\Orange - Sedan Sports.glb", + "originLocation": "ModelOrigin", + "rotationX": 90, + "rotationY": 0, + "rotationZ": -90, + "width": 74, + "animations": [] + } + }, + { + "assetStoreId": "e71bd69f896d6c7531b48c65ceb5da25071d4fbdeb518aeceecba8d21f34ed8d", + "name": "Speed_Joystick", + "persistentUuid": "f0dcaf34-2fe1-4333-aef2-6bb1f660efb2", + "type": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "variant": "", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "DeadZoneRadius": 0.2, + "JoystickIdentifier": "Secondary" + }, + "childrenContent": { + "Border": { + "adaptCollisionMaskAutomatically": false, + "updateIfNotVisible": false, + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": false, + "image": "assets/Flat dark joystick border vertical.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 0, + "y": 0 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [] + } + ] + } + ] + } + ] + }, + "Thumb": { + "adaptCollisionMaskAutomatically": false, + "updateIfNotVisible": false, + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": false, + "image": "assets\\Flat dark joystick thumb.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 0, + "y": 0 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [] + } + ] + } + ] + } + ] + } + } + }, + { + "assetStoreId": "e71bd69f896d6c7531b48c65ceb5da25071d4fbdeb518aeceecba8d21f34ed8d", + "name": "Steer_Joystick", + "persistentUuid": "340cca5d-d51e-4e7b-ad28-bc0dc6bcf73e", + "type": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "variant": "", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "JoystickIdentifier": "Primary", + "DeadZoneRadius": 0.2 + }, + "childrenContent": { + "Border": { + "adaptCollisionMaskAutomatically": false, + "updateIfNotVisible": false, + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": false, + "image": "assets/Flat dark joystick border horizontal.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 0, + "y": 0 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [] + } + ] + } + ] + } + ] + }, + "Thumb": { + "adaptCollisionMaskAutomatically": false, + "updateIfNotVisible": false, + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": false, + "image": "assets\\Flat dark joystick thumb.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 0, + "y": 0 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [] + } + ] + } + ] + } + ] + } + } + }, + { + "assetStoreId": "8f75165255eac00288d9b81c0063f2a3bdb26a7748fc7b2e17963a426f0a0aa4", + "name": "TrafficCone", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Dynamic", + "bullet": false, + "fixedRotation": false, + "shape": "Cylinder", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 2, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 2, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "ObjectCenter", + "crossfadeDuration": 0.10000000149011612, + "depth": 32, + "height": 32, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "Traffic Cone.glb", + "originLocation": "ModelOrigin", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 90, + "width": 32, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "FinishLine_3D", + "persistentUuid": "91cf8a93-038f-4ba8-bdb9-c8b6f793d194", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 128, + "height": 128, + "depth": 1, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "frontFaceResourceName": "assets\\StartingFinishLine.png", + "backFaceResourceName": "", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "assets\\StartingFinishLine.png", + "rightFaceResourceName": "assets\\StartingFinishLine.png", + "topFaceResourceName": "assets\\StartingFinishLine.png", + "bottomFaceResourceName": "assets\\StartingFinishLine.png", + "frontFaceVisible": true, + "backFaceVisible": false, + "leftFaceVisible": true, + "rightFaceVisible": true, + "topFaceVisible": true, + "bottomFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceRepeat": false, + "leftFaceResourceRepeat": true, + "rightFaceResourceRepeat": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceRepeat": true, + "materialType": "StandardWithoutMetalness", + "tint": "255;255;255", + "isCastingShadow": false, + "isReceivingShadow": true, + "tileScale": 0.2 + } + }, + { + "assetStoreId": "", + "bold": false, + "italic": false, + "name": "LapText", + "persistentUuid": "8febc900-33a2-426a-a78c-0aa7d9ae611f", + "smoothed": true, + "type": "TextObject::Text", + "underlined": false, + "variables": [], + "effects": [], + "behaviors": [], + "string": "Time: 00:00\nLap: 1", + "font": "assets\\NotoSansArabicUI-Black.ttf", + "textAlignment": "left", + "characterSize": 32, + "color": { + "b": 255, + "g": 255, + "r": 255 + }, + "content": { + "bold": false, + "isOutlineEnabled": true, + "isShadowEnabled": true, + "italic": false, + "outlineColor": "0;0;0", + "outlineThickness": 2, + "shadowAngle": 90, + "shadowBlurRadius": 2, + "shadowColor": "0;0;0", + "shadowDistance": 4, + "shadowOpacity": 127, + "smoothed": true, + "underlined": false, + "text": "Time: 00:00\nLap: 1", + "font": "assets\\NotoSansArabicUI-Black.ttf", + "textAlignment": "left", + "verticalTextAlignment": "top", + "characterSize": 32, + "lineHeight": 0, + "color": "255;255;255" + } + }, + { + "assetStoreId": "", + "name": "CheckpointArrow", + "persistentUuid": "71080efb-67c7-4f21-98b3-30c38f5c58e6", + "type": "Scene3D::Cube3DObject", + "variables": [ + { + "name": "Checkpoint", + "type": "number", + "value": 0 + }, + { + "name": "FinishLine", + "type": "boolean", + "value": false + } + ], + "effects": [], + "behaviors": [], + "content": { + "width": 128, + "height": 128, + "depth": 2, + "enableTextureTransparency": true, + "facesOrientation": "Y", + "frontFaceResourceName": "assets\\StartingCheckpointArrow.png", + "backFaceResourceName": "", + "leftFaceResourceName": "", + "rightFaceResourceName": "", + "topFaceResourceName": "", + "bottomFaceResourceName": "", + "backFaceUpThroughWhichAxisRotation": "X", + "frontFaceVisible": true, + "backFaceVisible": false, + "leftFaceVisible": false, + "rightFaceVisible": false, + "topFaceVisible": false, + "bottomFaceVisible": false, + "frontFaceResourceRepeat": false, + "backFaceResourceRepeat": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceRepeat": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceRepeat": false, + "materialType": "Basic", + "tint": "255;255;255", + "isCastingShadow": false, + "isReceivingShadow": false + } + }, + { + "assetStoreId": "", + "name": "Terrain", + "type": "Terrain3D::Terrain3D", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 7, + "restitution": 0, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "Relief": "Flat", + "Seed": 1, + "Resolution": "256", + "TextureSize": 350, + "CliffLayer": "3", + "CliffAngle": 38, + "NormalMapStrength": 1, + "HeightBlending": 0.8, + "TextureVariation": 0.5, + "Edits": "[{\"tool\":\"raise\",\"x\":0,\"y\":0,\"toX\":1,\"toY\":0,\"radius\":0.1038,\"height\":0.375},{\"tool\":\"raise\",\"x\":0,\"y\":1,\"toX\":1,\"toY\":1,\"radius\":0.1038,\"height\":0.375},{\"tool\":\"raise\",\"x\":0,\"y\":0,\"toX\":0,\"toY\":1,\"radius\":0.1038,\"height\":0.375},{\"tool\":\"raise\",\"x\":1,\"y\":0,\"toX\":1,\"toY\":1,\"radius\":0.1038,\"height\":0.375},{\"tool\":\"raise\",\"x\":0.3169,\"y\":0,\"radius\":0.0977,\"height\":0.2813},{\"tool\":\"raise\",\"x\":0.6465,\"y\":0,\"radius\":0.0854,\"height\":0.2375},{\"tool\":\"raise\",\"x\":1,\"y\":0.5488,\"radius\":0.0977,\"height\":0.3125},{\"tool\":\"raise\",\"x\":1,\"y\":0.1826,\"radius\":0.0854,\"height\":0.2625},{\"tool\":\"raise\",\"x\":0.5977,\"y\":1,\"radius\":0.1099,\"height\":0.325},{\"tool\":\"raise\",\"x\":0.1582,\"y\":1,\"radius\":0.0854,\"height\":0.2375},{\"tool\":\"raise\",\"x\":0,\"y\":0.6221,\"radius\":0.0977,\"height\":0.2813},{\"tool\":\"raise\",\"x\":0,\"y\":0.207,\"radius\":0.0854,\"height\":0.25},{\"tool\":\"raise\",\"x\":0.8296,\"y\":0.3901,\"radius\":0.1831,\"height\":0.1875},{\"tool\":\"raise\",\"x\":0.7808,\"y\":0.2314,\"radius\":0.0977,\"height\":0.05},{\"tool\":\"raise\",\"x\":0.1948,\"y\":0.2925,\"radius\":0.1343,\"height\":0.1313},{\"tool\":\"raise\",\"x\":0.4146,\"y\":0.5488,\"radius\":0.1099,\"height\":0.0563},{\"tool\":\"raise\",\"x\":0.5366,\"y\":0.2681,\"radius\":0.0854,\"height\":0.0438},{\"tool\":\"smooth\",\"x\":0.5,\"y\":0.5,\"radius\":0.4395,\"strength\":0.3},{\"tool\":\"flatten\",\"x\":0.1704,\"y\":0.8235,\"toX\":0.6587,\"toY\":0.8235,\"radius\":0.0916,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1704,\"y\":0.8235,\"toX\":0.6587,\"toY\":0.8235,\"radius\":0.0916,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1704,\"y\":0.8235,\"toX\":0.6587,\"toY\":0.8235,\"radius\":0.0916,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1826,\"y\":0.9028,\"toX\":0.6221,\"toY\":0.9028,\"radius\":0.0537,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1826,\"y\":0.9028,\"toX\":0.6221,\"toY\":0.9028,\"radius\":0.0537,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1826,\"y\":0.9028,\"toX\":0.6221,\"toY\":0.9028,\"radius\":0.0537,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1826,\"y\":0.9028,\"toX\":0.6221,\"toY\":0.9028,\"radius\":0.0537,\"z\":0},{\"tool\":\"flatten\",\"x\":0.207,\"y\":0.8174,\"toX\":0.231,\"toY\":0.8221,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.231,\"y\":0.8221,\"toX\":0.2552,\"toY\":0.824,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.2552,\"y\":0.824,\"toX\":0.2797,\"toY\":0.8241,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.2797,\"y\":0.8241,\"toX\":0.3041,\"toY\":0.8232,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.3041,\"y\":0.8232,\"toX\":0.3285,\"toY\":0.822,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.3285,\"y\":0.822,\"toX\":0.3528,\"toY\":0.8204,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.3528,\"y\":0.8204,\"toX\":0.3772,\"toY\":0.8191,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.3772,\"y\":0.8191,\"toX\":0.4016,\"toY\":0.8179,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4016,\"y\":0.8179,\"toX\":0.426,\"toY\":0.8174,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.426,\"y\":0.8174,\"toX\":0.4504,\"toY\":0.8176,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4504,\"y\":0.8176,\"toX\":0.4749,\"toY\":0.8182,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4749,\"y\":0.8182,\"toX\":0.4993,\"toY\":0.8191,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4993,\"y\":0.8191,\"toX\":0.5237,\"toY\":0.8199,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.5237,\"y\":0.8199,\"toX\":0.548,\"toY\":0.8207,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.548,\"y\":0.8207,\"toX\":0.5724,\"toY\":0.821,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.5724,\"y\":0.821,\"toX\":0.5968,\"toY\":0.8208,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.5968,\"y\":0.8208,\"toX\":0.6212,\"toY\":0.8197,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.6212,\"y\":0.8197,\"toX\":0.6455,\"toY\":0.8175,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.6455,\"y\":0.8175,\"toX\":0.6698,\"toY\":0.8143,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.6698,\"y\":0.8143,\"toX\":0.694,\"toY\":0.8108,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.694,\"y\":0.8108,\"toX\":0.7179,\"toY\":0.8065,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.7179,\"y\":0.8065,\"toX\":0.7418,\"toY\":0.8013,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.7418,\"y\":0.8013,\"toX\":0.7653,\"toY\":0.7946,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.7653,\"y\":0.7946,\"toX\":0.788,\"toY\":0.7858,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.788,\"y\":0.7858,\"toX\":0.8094,\"toY\":0.7742,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.8094,\"y\":0.7742,\"toX\":0.8285,\"toY\":0.7589,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.8285,\"y\":0.7589,\"toX\":0.8442,\"toY\":0.7402,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.8442,\"y\":0.7402,\"toX\":0.8569,\"toY\":0.7195,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.8569,\"y\":0.7195,\"toX\":0.8671,\"toY\":0.6973,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.8671,\"y\":0.6973,\"toX\":0.875,\"toY\":0.6742,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.875,\"y\":0.6742,\"toX\":0.8811,\"toY\":0.6505,\"radius\":0.0483,\"z\":0.0001},{\"tool\":\"flatten\",\"x\":0.8811,\"y\":0.6505,\"toX\":0.8859,\"toY\":0.6266,\"radius\":0.0483,\"z\":0.0014},{\"tool\":\"flatten\",\"x\":0.8859,\"y\":0.6266,\"toX\":0.8881,\"toY\":0.6023,\"radius\":0.0483,\"z\":0.0055},{\"tool\":\"flatten\",\"x\":0.8881,\"y\":0.6023,\"toX\":0.8883,\"toY\":0.5779,\"radius\":0.0483,\"z\":0.0134},{\"tool\":\"flatten\",\"x\":0.8883,\"y\":0.5779,\"toX\":0.8867,\"toY\":0.5536,\"radius\":0.0483,\"z\":0.0254},{\"tool\":\"flatten\",\"x\":0.8867,\"y\":0.5536,\"toX\":0.8837,\"toY\":0.5293,\"radius\":0.0483,\"z\":0.04},{\"tool\":\"flatten\",\"x\":0.8837,\"y\":0.5293,\"toX\":0.8794,\"toY\":0.5052,\"radius\":0.0483,\"z\":0.055},{\"tool\":\"flatten\",\"x\":0.8794,\"y\":0.5052,\"toX\":0.8739,\"toY\":0.4814,\"radius\":0.0483,\"z\":0.07},{\"tool\":\"flatten\",\"x\":0.8739,\"y\":0.4814,\"toX\":0.8661,\"toY\":0.4584,\"radius\":0.0483,\"z\":0.085},{\"tool\":\"flatten\",\"x\":0.8661,\"y\":0.4584,\"toX\":0.8546,\"toY\":0.4369,\"radius\":0.0483,\"z\":0.1},{\"tool\":\"flatten\",\"x\":0.8546,\"y\":0.4369,\"toX\":0.8413,\"toY\":0.4164,\"radius\":0.0483,\"z\":0.115},{\"tool\":\"flatten\",\"x\":0.8413,\"y\":0.4164,\"toX\":0.8276,\"toY\":0.3961,\"radius\":0.0483,\"z\":0.13},{\"tool\":\"flatten\",\"x\":0.8276,\"y\":0.3961,\"toX\":0.8154,\"toY\":0.375,\"radius\":0.0483,\"z\":0.1438},{\"tool\":\"flatten\",\"x\":0.8154,\"y\":0.375,\"toX\":0.807,\"toY\":0.3522,\"radius\":0.0483,\"z\":0.1467},{\"tool\":\"flatten\",\"x\":0.807,\"y\":0.3522,\"toX\":0.8055,\"toY\":0.328,\"radius\":0.0483,\"z\":0.1366},{\"tool\":\"flatten\",\"x\":0.8055,\"y\":0.328,\"toX\":0.8116,\"toY\":0.3044,\"radius\":0.0483,\"z\":0.1224},{\"tool\":\"flatten\",\"x\":0.8116,\"y\":0.3044,\"toX\":0.8213,\"toY\":0.282,\"radius\":0.0483,\"z\":0.1074},{\"tool\":\"flatten\",\"x\":0.8213,\"y\":0.282,\"toX\":0.8319,\"toY\":0.2599,\"radius\":0.0483,\"z\":0.0923},{\"tool\":\"flatten\",\"x\":0.8319,\"y\":0.2599,\"toX\":0.8419,\"toY\":0.2377,\"radius\":0.0483,\"z\":0.0773},{\"tool\":\"flatten\",\"x\":0.8419,\"y\":0.2377,\"toX\":0.8489,\"toY\":0.2144,\"radius\":0.0483,\"z\":0.0624},{\"tool\":\"flatten\",\"x\":0.8489,\"y\":0.2144,\"toX\":0.8463,\"toY\":0.1904,\"radius\":0.0483,\"z\":0.0475},{\"tool\":\"flatten\",\"x\":0.8463,\"y\":0.1904,\"toX\":0.8334,\"toY\":0.1698,\"radius\":0.0483,\"z\":0.0326},{\"tool\":\"flatten\",\"x\":0.8334,\"y\":0.1698,\"toX\":0.8163,\"toY\":0.1525,\"radius\":0.0483,\"z\":0.0177},{\"tool\":\"flatten\",\"x\":0.8163,\"y\":0.1525,\"toX\":0.7966,\"toY\":0.1381,\"radius\":0.0483,\"z\":0.0066},{\"tool\":\"flatten\",\"x\":0.7966,\"y\":0.1381,\"toX\":0.7751,\"toY\":0.1265,\"radius\":0.0483,\"z\":0.0023},{\"tool\":\"flatten\",\"x\":0.7751,\"y\":0.1265,\"toX\":0.7521,\"toY\":0.1184,\"radius\":0.0483,\"z\":0.0012},{\"tool\":\"flatten\",\"x\":0.7521,\"y\":0.1184,\"toX\":0.7279,\"toY\":0.1155,\"radius\":0.0483,\"z\":0.0006},{\"tool\":\"flatten\",\"x\":0.7279,\"y\":0.1155,\"toX\":0.7039,\"toY\":0.1189,\"radius\":0.0483,\"z\":0.0002},{\"tool\":\"flatten\",\"x\":0.7039,\"y\":0.1189,\"toX\":0.6805,\"toY\":0.1261,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.6805,\"y\":0.1261,\"toX\":0.6581,\"toY\":0.1356,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.6581,\"y\":0.1356,\"toX\":0.6365,\"toY\":0.1472,\"radius\":0.0483,\"z\":0.0005},{\"tool\":\"flatten\",\"x\":0.6365,\"y\":0.1472,\"toX\":0.6161,\"toY\":0.1606,\"radius\":0.0483,\"z\":0.0025},{\"tool\":\"flatten\",\"x\":0.6161,\"y\":0.1606,\"toX\":0.5973,\"toY\":0.176,\"radius\":0.0483,\"z\":0.0057},{\"tool\":\"flatten\",\"x\":0.5973,\"y\":0.176,\"toX\":0.5814,\"toY\":0.1945,\"radius\":0.0483,\"z\":0.0092},{\"tool\":\"flatten\",\"x\":0.5814,\"y\":0.1945,\"toX\":0.5723,\"toY\":0.217,\"radius\":0.0483,\"z\":0.0117},{\"tool\":\"flatten\",\"x\":0.5723,\"y\":0.217,\"toX\":0.5685,\"toY\":0.2412,\"radius\":0.0483,\"z\":0.0129},{\"tool\":\"flatten\",\"x\":0.5685,\"y\":0.2412,\"toX\":0.5669,\"toY\":0.2655,\"radius\":0.0483,\"z\":0.0131},{\"tool\":\"flatten\",\"x\":0.5669,\"y\":0.2655,\"toX\":0.5657,\"toY\":0.2899,\"radius\":0.0483,\"z\":0.0131},{\"tool\":\"flatten\",\"x\":0.5657,\"y\":0.2899,\"toX\":0.5624,\"toY\":0.3141,\"radius\":0.0483,\"z\":0.0131},{\"tool\":\"flatten\",\"x\":0.5624,\"y\":0.3141,\"toX\":0.554,\"toY\":0.3369,\"radius\":0.0483,\"z\":0.0126},{\"tool\":\"flatten\",\"x\":0.554,\"y\":0.3369,\"toX\":0.5409,\"toY\":0.3575,\"radius\":0.0483,\"z\":0.0106},{\"tool\":\"flatten\",\"x\":0.5409,\"y\":0.3575,\"toX\":0.5259,\"toY\":0.3767,\"radius\":0.0483,\"z\":0.0074},{\"tool\":\"flatten\",\"x\":0.5259,\"y\":0.3767,\"toX\":0.509,\"toY\":0.3943,\"radius\":0.0483,\"z\":0.004},{\"tool\":\"flatten\",\"x\":0.509,\"y\":0.3943,\"toX\":0.4899,\"toY\":0.4093,\"radius\":0.0483,\"z\":0.0014},{\"tool\":\"flatten\",\"x\":0.4899,\"y\":0.4093,\"toX\":0.4677,\"toY\":0.4193,\"radius\":0.0483,\"z\":0.0003},{\"tool\":\"flatten\",\"x\":0.4677,\"y\":0.4193,\"toX\":0.4436,\"toY\":0.4183,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4436,\"y\":0.4183,\"toX\":0.4221,\"toY\":0.4069,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4221,\"y\":0.4069,\"toX\":0.4032,\"toY\":0.3915,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4032,\"y\":0.3915,\"toX\":0.3857,\"toY\":0.3744,\"radius\":0.0483,\"z\":0.0001},{\"tool\":\"flatten\",\"x\":0.3857,\"y\":0.3744,\"toX\":0.3688,\"toY\":0.3569,\"radius\":0.0483,\"z\":0.0009},{\"tool\":\"flatten\",\"x\":0.3688,\"y\":0.3569,\"toX\":0.3517,\"toY\":0.3395,\"radius\":0.0483,\"z\":0.0028},{\"tool\":\"flatten\",\"x\":0.3517,\"y\":0.3395,\"toX\":0.3351,\"toY\":0.3215,\"radius\":0.0483,\"z\":0.0055},{\"tool\":\"flatten\",\"x\":0.3351,\"y\":0.3215,\"toX\":0.3206,\"toY\":0.3019,\"radius\":0.0483,\"z\":0.0086},{\"tool\":\"flatten\",\"x\":0.3206,\"y\":0.3019,\"toX\":0.3069,\"toY\":0.2817,\"radius\":0.0483,\"z\":0.0116},{\"tool\":\"flatten\",\"x\":0.3069,\"y\":0.2817,\"toX\":0.2935,\"toY\":0.2614,\"radius\":0.0483,\"z\":0.0148},{\"tool\":\"flatten\",\"x\":0.2935,\"y\":0.2614,\"toX\":0.2794,\"toY\":0.2413,\"radius\":0.0483,\"z\":0.019},{\"tool\":\"flatten\",\"x\":0.2794,\"y\":0.2413,\"toX\":0.2639,\"toY\":0.2224,\"radius\":0.0483,\"z\":0.0246},{\"tool\":\"flatten\",\"x\":0.2639,\"y\":0.2224,\"toX\":0.2456,\"toY\":0.2065,\"radius\":0.0483,\"z\":0.0312},{\"tool\":\"flatten\",\"x\":0.2456,\"y\":0.2065,\"toX\":0.2223,\"toY\":0.2004,\"radius\":0.0483,\"z\":0.0379},{\"tool\":\"flatten\",\"x\":0.2223,\"y\":0.2004,\"toX\":0.1985,\"toY\":0.2052,\"radius\":0.0483,\"z\":0.0433},{\"tool\":\"flatten\",\"x\":0.1985,\"y\":0.2052,\"toX\":0.1766,\"toY\":0.2159,\"radius\":0.0483,\"z\":0.047},{\"tool\":\"flatten\",\"x\":0.1766,\"y\":0.2159,\"toX\":0.157,\"toY\":0.2303,\"radius\":0.0483,\"z\":0.0489},{\"tool\":\"flatten\",\"x\":0.157,\"y\":0.2303,\"toX\":0.1396,\"toY\":0.2476,\"radius\":0.0483,\"z\":0.0493},{\"tool\":\"flatten\",\"x\":0.1396,\"y\":0.2476,\"toX\":0.125,\"toY\":0.2671,\"radius\":0.0483,\"z\":0.048},{\"tool\":\"flatten\",\"x\":0.125,\"y\":0.2671,\"toX\":0.1134,\"toY\":0.2885,\"radius\":0.0483,\"z\":0.0447},{\"tool\":\"flatten\",\"x\":0.1134,\"y\":0.2885,\"toX\":0.1055,\"toY\":0.3115,\"radius\":0.0483,\"z\":0.0398},{\"tool\":\"flatten\",\"x\":0.1055,\"y\":0.3115,\"toX\":0.1011,\"toY\":0.3356,\"radius\":0.0483,\"z\":0.0337},{\"tool\":\"flatten\",\"x\":0.1011,\"y\":0.3356,\"toX\":0.0986,\"toY\":0.3599,\"radius\":0.0483,\"z\":0.0269},{\"tool\":\"flatten\",\"x\":0.0986,\"y\":0.3599,\"toX\":0.0974,\"toY\":0.3843,\"radius\":0.0483,\"z\":0.02},{\"tool\":\"flatten\",\"x\":0.0974,\"y\":0.3843,\"toX\":0.097,\"toY\":0.4086,\"radius\":0.0483,\"z\":0.0139},{\"tool\":\"flatten\",\"x\":0.097,\"y\":0.4086,\"toX\":0.0973,\"toY\":0.433,\"radius\":0.0483,\"z\":0.0091},{\"tool\":\"flatten\",\"x\":0.0973,\"y\":0.433,\"toX\":0.098,\"toY\":0.4574,\"radius\":0.0483,\"z\":0.0058},{\"tool\":\"flatten\",\"x\":0.098,\"y\":0.4574,\"toX\":0.0991,\"toY\":0.4818,\"radius\":0.0483,\"z\":0.0039},{\"tool\":\"flatten\",\"x\":0.0991,\"y\":0.4818,\"toX\":0.1003,\"toY\":0.5062,\"radius\":0.0483,\"z\":0.003},{\"tool\":\"flatten\",\"x\":0.1003,\"y\":0.5062,\"toX\":0.1017,\"toY\":0.5306,\"radius\":0.0483,\"z\":0.0023},{\"tool\":\"flatten\",\"x\":0.1017,\"y\":0.5306,\"toX\":0.103,\"toY\":0.5549,\"radius\":0.0483,\"z\":0.0016},{\"tool\":\"flatten\",\"x\":0.103,\"y\":0.5549,\"toX\":0.1041,\"toY\":0.5793,\"radius\":0.0483,\"z\":0.0011},{\"tool\":\"flatten\",\"x\":0.1041,\"y\":0.5793,\"toX\":0.1055,\"toY\":0.6038,\"radius\":0.0483,\"z\":0.0006},{\"tool\":\"flatten\",\"x\":0.1055,\"y\":0.6038,\"toX\":0.1073,\"toY\":0.6281,\"radius\":0.0483,\"z\":0.0003},{\"tool\":\"flatten\",\"x\":0.1073,\"y\":0.6281,\"toX\":0.1099,\"toY\":0.6523,\"radius\":0.0483,\"z\":0.0001},{\"tool\":\"flatten\",\"x\":0.1099,\"y\":0.6523,\"toX\":0.1136,\"toY\":0.6765,\"radius\":0.0483,\"z\":0.0001},{\"tool\":\"flatten\",\"x\":0.1136,\"y\":0.6765,\"toX\":0.1191,\"toY\":0.7003,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1191,\"y\":0.7003,\"toX\":0.1267,\"toY\":0.7234,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1267,\"y\":0.7234,\"toX\":0.136,\"toY\":0.7461,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.136,\"y\":0.7461,\"toX\":0.1465,\"toY\":0.7681,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1465,\"y\":0.7681,\"toX\":0.1602,\"toY\":0.7882,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1602,\"y\":0.7882,\"toX\":0.1785,\"toY\":0.8043,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1785,\"y\":0.8043,\"toX\":0.207,\"toY\":0.8174,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.207,\"y\":0.8174,\"toX\":0.231,\"toY\":0.8221,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.231,\"y\":0.8221,\"toX\":0.2552,\"toY\":0.824,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.2552,\"y\":0.824,\"toX\":0.2797,\"toY\":0.8241,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.2797,\"y\":0.8241,\"toX\":0.3041,\"toY\":0.8232,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.3041,\"y\":0.8232,\"toX\":0.3285,\"toY\":0.822,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.3285,\"y\":0.822,\"toX\":0.3528,\"toY\":0.8204,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.3528,\"y\":0.8204,\"toX\":0.3772,\"toY\":0.8191,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.3772,\"y\":0.8191,\"toX\":0.4016,\"toY\":0.8179,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4016,\"y\":0.8179,\"toX\":0.426,\"toY\":0.8174,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.426,\"y\":0.8174,\"toX\":0.4504,\"toY\":0.8176,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4504,\"y\":0.8176,\"toX\":0.4749,\"toY\":0.8182,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4749,\"y\":0.8182,\"toX\":0.4993,\"toY\":0.8191,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4993,\"y\":0.8191,\"toX\":0.5237,\"toY\":0.8199,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.5237,\"y\":0.8199,\"toX\":0.548,\"toY\":0.8207,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.548,\"y\":0.8207,\"toX\":0.5724,\"toY\":0.821,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.5724,\"y\":0.821,\"toX\":0.5968,\"toY\":0.8208,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.5968,\"y\":0.8208,\"toX\":0.6212,\"toY\":0.8197,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.6212,\"y\":0.8197,\"toX\":0.6455,\"toY\":0.8175,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.6455,\"y\":0.8175,\"toX\":0.6698,\"toY\":0.8143,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.6698,\"y\":0.8143,\"toX\":0.694,\"toY\":0.8108,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.694,\"y\":0.8108,\"toX\":0.7179,\"toY\":0.8065,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.7179,\"y\":0.8065,\"toX\":0.7418,\"toY\":0.8013,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.7418,\"y\":0.8013,\"toX\":0.7653,\"toY\":0.7946,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.7653,\"y\":0.7946,\"toX\":0.788,\"toY\":0.7858,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.788,\"y\":0.7858,\"toX\":0.8094,\"toY\":0.7742,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.8094,\"y\":0.7742,\"toX\":0.8285,\"toY\":0.7589,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.8285,\"y\":0.7589,\"toX\":0.8442,\"toY\":0.7402,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.8442,\"y\":0.7402,\"toX\":0.8569,\"toY\":0.7195,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.8569,\"y\":0.7195,\"toX\":0.8671,\"toY\":0.6973,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.8671,\"y\":0.6973,\"toX\":0.875,\"toY\":0.6742,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.875,\"y\":0.6742,\"toX\":0.8811,\"toY\":0.6505,\"radius\":0.0483,\"z\":0.0001},{\"tool\":\"flatten\",\"x\":0.8811,\"y\":0.6505,\"toX\":0.8859,\"toY\":0.6266,\"radius\":0.0483,\"z\":0.0014},{\"tool\":\"flatten\",\"x\":0.8859,\"y\":0.6266,\"toX\":0.8881,\"toY\":0.6023,\"radius\":0.0483,\"z\":0.0055},{\"tool\":\"flatten\",\"x\":0.8881,\"y\":0.6023,\"toX\":0.8883,\"toY\":0.5779,\"radius\":0.0483,\"z\":0.0134},{\"tool\":\"flatten\",\"x\":0.8883,\"y\":0.5779,\"toX\":0.8867,\"toY\":0.5536,\"radius\":0.0483,\"z\":0.0254},{\"tool\":\"flatten\",\"x\":0.8867,\"y\":0.5536,\"toX\":0.8837,\"toY\":0.5293,\"radius\":0.0483,\"z\":0.04},{\"tool\":\"flatten\",\"x\":0.8837,\"y\":0.5293,\"toX\":0.8794,\"toY\":0.5052,\"radius\":0.0483,\"z\":0.055},{\"tool\":\"flatten\",\"x\":0.8794,\"y\":0.5052,\"toX\":0.8739,\"toY\":0.4814,\"radius\":0.0483,\"z\":0.07},{\"tool\":\"flatten\",\"x\":0.8739,\"y\":0.4814,\"toX\":0.8661,\"toY\":0.4584,\"radius\":0.0483,\"z\":0.085},{\"tool\":\"flatten\",\"x\":0.8661,\"y\":0.4584,\"toX\":0.8546,\"toY\":0.4369,\"radius\":0.0483,\"z\":0.1},{\"tool\":\"flatten\",\"x\":0.8546,\"y\":0.4369,\"toX\":0.8413,\"toY\":0.4164,\"radius\":0.0483,\"z\":0.115},{\"tool\":\"flatten\",\"x\":0.8413,\"y\":0.4164,\"toX\":0.8276,\"toY\":0.3961,\"radius\":0.0483,\"z\":0.13},{\"tool\":\"flatten\",\"x\":0.8276,\"y\":0.3961,\"toX\":0.8154,\"toY\":0.375,\"radius\":0.0483,\"z\":0.1438},{\"tool\":\"flatten\",\"x\":0.8154,\"y\":0.375,\"toX\":0.807,\"toY\":0.3522,\"radius\":0.0483,\"z\":0.1467},{\"tool\":\"flatten\",\"x\":0.807,\"y\":0.3522,\"toX\":0.8055,\"toY\":0.328,\"radius\":0.0483,\"z\":0.1366},{\"tool\":\"flatten\",\"x\":0.8055,\"y\":0.328,\"toX\":0.8116,\"toY\":0.3044,\"radius\":0.0483,\"z\":0.1224},{\"tool\":\"flatten\",\"x\":0.8116,\"y\":0.3044,\"toX\":0.8213,\"toY\":0.282,\"radius\":0.0483,\"z\":0.1074},{\"tool\":\"flatten\",\"x\":0.8213,\"y\":0.282,\"toX\":0.8319,\"toY\":0.2599,\"radius\":0.0483,\"z\":0.0923},{\"tool\":\"flatten\",\"x\":0.8319,\"y\":0.2599,\"toX\":0.8419,\"toY\":0.2377,\"radius\":0.0483,\"z\":0.0773},{\"tool\":\"flatten\",\"x\":0.8419,\"y\":0.2377,\"toX\":0.8489,\"toY\":0.2144,\"radius\":0.0483,\"z\":0.0624},{\"tool\":\"flatten\",\"x\":0.8489,\"y\":0.2144,\"toX\":0.8463,\"toY\":0.1904,\"radius\":0.0483,\"z\":0.0475},{\"tool\":\"flatten\",\"x\":0.8463,\"y\":0.1904,\"toX\":0.8334,\"toY\":0.1698,\"radius\":0.0483,\"z\":0.0326},{\"tool\":\"flatten\",\"x\":0.8334,\"y\":0.1698,\"toX\":0.8163,\"toY\":0.1525,\"radius\":0.0483,\"z\":0.0177},{\"tool\":\"flatten\",\"x\":0.8163,\"y\":0.1525,\"toX\":0.7966,\"toY\":0.1381,\"radius\":0.0483,\"z\":0.0066},{\"tool\":\"flatten\",\"x\":0.7966,\"y\":0.1381,\"toX\":0.7751,\"toY\":0.1265,\"radius\":0.0483,\"z\":0.0023},{\"tool\":\"flatten\",\"x\":0.7751,\"y\":0.1265,\"toX\":0.7521,\"toY\":0.1184,\"radius\":0.0483,\"z\":0.0012},{\"tool\":\"flatten\",\"x\":0.7521,\"y\":0.1184,\"toX\":0.7279,\"toY\":0.1155,\"radius\":0.0483,\"z\":0.0006},{\"tool\":\"flatten\",\"x\":0.7279,\"y\":0.1155,\"toX\":0.7039,\"toY\":0.1189,\"radius\":0.0483,\"z\":0.0002},{\"tool\":\"flatten\",\"x\":0.7039,\"y\":0.1189,\"toX\":0.6805,\"toY\":0.1261,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.6805,\"y\":0.1261,\"toX\":0.6581,\"toY\":0.1356,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.6581,\"y\":0.1356,\"toX\":0.6365,\"toY\":0.1472,\"radius\":0.0483,\"z\":0.0005},{\"tool\":\"flatten\",\"x\":0.6365,\"y\":0.1472,\"toX\":0.6161,\"toY\":0.1606,\"radius\":0.0483,\"z\":0.0025},{\"tool\":\"flatten\",\"x\":0.6161,\"y\":0.1606,\"toX\":0.5973,\"toY\":0.176,\"radius\":0.0483,\"z\":0.0057},{\"tool\":\"flatten\",\"x\":0.5973,\"y\":0.176,\"toX\":0.5814,\"toY\":0.1945,\"radius\":0.0483,\"z\":0.0092},{\"tool\":\"flatten\",\"x\":0.5814,\"y\":0.1945,\"toX\":0.5723,\"toY\":0.217,\"radius\":0.0483,\"z\":0.0117},{\"tool\":\"flatten\",\"x\":0.5723,\"y\":0.217,\"toX\":0.5685,\"toY\":0.2412,\"radius\":0.0483,\"z\":0.0129},{\"tool\":\"flatten\",\"x\":0.5685,\"y\":0.2412,\"toX\":0.5669,\"toY\":0.2655,\"radius\":0.0483,\"z\":0.0131},{\"tool\":\"flatten\",\"x\":0.5669,\"y\":0.2655,\"toX\":0.5657,\"toY\":0.2899,\"radius\":0.0483,\"z\":0.0131},{\"tool\":\"flatten\",\"x\":0.5657,\"y\":0.2899,\"toX\":0.5624,\"toY\":0.3141,\"radius\":0.0483,\"z\":0.0131},{\"tool\":\"flatten\",\"x\":0.5624,\"y\":0.3141,\"toX\":0.554,\"toY\":0.3369,\"radius\":0.0483,\"z\":0.0126},{\"tool\":\"flatten\",\"x\":0.554,\"y\":0.3369,\"toX\":0.5409,\"toY\":0.3575,\"radius\":0.0483,\"z\":0.0106},{\"tool\":\"flatten\",\"x\":0.5409,\"y\":0.3575,\"toX\":0.5259,\"toY\":0.3767,\"radius\":0.0483,\"z\":0.0074},{\"tool\":\"flatten\",\"x\":0.5259,\"y\":0.3767,\"toX\":0.509,\"toY\":0.3943,\"radius\":0.0483,\"z\":0.004},{\"tool\":\"flatten\",\"x\":0.509,\"y\":0.3943,\"toX\":0.4899,\"toY\":0.4093,\"radius\":0.0483,\"z\":0.0014},{\"tool\":\"flatten\",\"x\":0.4899,\"y\":0.4093,\"toX\":0.4677,\"toY\":0.4193,\"radius\":0.0483,\"z\":0.0003},{\"tool\":\"flatten\",\"x\":0.4677,\"y\":0.4193,\"toX\":0.4436,\"toY\":0.4183,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4436,\"y\":0.4183,\"toX\":0.4221,\"toY\":0.4069,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4221,\"y\":0.4069,\"toX\":0.4032,\"toY\":0.3915,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4032,\"y\":0.3915,\"toX\":0.3857,\"toY\":0.3744,\"radius\":0.0483,\"z\":0.0001},{\"tool\":\"flatten\",\"x\":0.3857,\"y\":0.3744,\"toX\":0.3688,\"toY\":0.3569,\"radius\":0.0483,\"z\":0.0009},{\"tool\":\"flatten\",\"x\":0.3688,\"y\":0.3569,\"toX\":0.3517,\"toY\":0.3395,\"radius\":0.0483,\"z\":0.0028},{\"tool\":\"flatten\",\"x\":0.3517,\"y\":0.3395,\"toX\":0.3351,\"toY\":0.3215,\"radius\":0.0483,\"z\":0.0055},{\"tool\":\"flatten\",\"x\":0.3351,\"y\":0.3215,\"toX\":0.3206,\"toY\":0.3019,\"radius\":0.0483,\"z\":0.0086},{\"tool\":\"flatten\",\"x\":0.3206,\"y\":0.3019,\"toX\":0.3069,\"toY\":0.2817,\"radius\":0.0483,\"z\":0.0116},{\"tool\":\"flatten\",\"x\":0.3069,\"y\":0.2817,\"toX\":0.2935,\"toY\":0.2614,\"radius\":0.0483,\"z\":0.0148},{\"tool\":\"flatten\",\"x\":0.2935,\"y\":0.2614,\"toX\":0.2794,\"toY\":0.2413,\"radius\":0.0483,\"z\":0.019},{\"tool\":\"flatten\",\"x\":0.2794,\"y\":0.2413,\"toX\":0.2639,\"toY\":0.2224,\"radius\":0.0483,\"z\":0.0246},{\"tool\":\"flatten\",\"x\":0.2639,\"y\":0.2224,\"toX\":0.2456,\"toY\":0.2065,\"radius\":0.0483,\"z\":0.0312},{\"tool\":\"flatten\",\"x\":0.2456,\"y\":0.2065,\"toX\":0.2223,\"toY\":0.2004,\"radius\":0.0483,\"z\":0.0379},{\"tool\":\"flatten\",\"x\":0.2223,\"y\":0.2004,\"toX\":0.1985,\"toY\":0.2052,\"radius\":0.0483,\"z\":0.0433},{\"tool\":\"flatten\",\"x\":0.1985,\"y\":0.2052,\"toX\":0.1766,\"toY\":0.2159,\"radius\":0.0483,\"z\":0.047},{\"tool\":\"flatten\",\"x\":0.1766,\"y\":0.2159,\"toX\":0.157,\"toY\":0.2303,\"radius\":0.0483,\"z\":0.0489},{\"tool\":\"flatten\",\"x\":0.157,\"y\":0.2303,\"toX\":0.1396,\"toY\":0.2476,\"radius\":0.0483,\"z\":0.0493},{\"tool\":\"flatten\",\"x\":0.1396,\"y\":0.2476,\"toX\":0.125,\"toY\":0.2671,\"radius\":0.0483,\"z\":0.048},{\"tool\":\"flatten\",\"x\":0.125,\"y\":0.2671,\"toX\":0.1134,\"toY\":0.2885,\"radius\":0.0483,\"z\":0.0447},{\"tool\":\"flatten\",\"x\":0.1134,\"y\":0.2885,\"toX\":0.1055,\"toY\":0.3115,\"radius\":0.0483,\"z\":0.0398},{\"tool\":\"flatten\",\"x\":0.1055,\"y\":0.3115,\"toX\":0.1011,\"toY\":0.3356,\"radius\":0.0483,\"z\":0.0337},{\"tool\":\"flatten\",\"x\":0.1011,\"y\":0.3356,\"toX\":0.0986,\"toY\":0.3599,\"radius\":0.0483,\"z\":0.0269},{\"tool\":\"flatten\",\"x\":0.0986,\"y\":0.3599,\"toX\":0.0974,\"toY\":0.3843,\"radius\":0.0483,\"z\":0.02},{\"tool\":\"flatten\",\"x\":0.0974,\"y\":0.3843,\"toX\":0.097,\"toY\":0.4086,\"radius\":0.0483,\"z\":0.0139},{\"tool\":\"flatten\",\"x\":0.097,\"y\":0.4086,\"toX\":0.0973,\"toY\":0.433,\"radius\":0.0483,\"z\":0.0091},{\"tool\":\"flatten\",\"x\":0.0973,\"y\":0.433,\"toX\":0.098,\"toY\":0.4574,\"radius\":0.0483,\"z\":0.0058},{\"tool\":\"flatten\",\"x\":0.098,\"y\":0.4574,\"toX\":0.0991,\"toY\":0.4818,\"radius\":0.0483,\"z\":0.0039},{\"tool\":\"flatten\",\"x\":0.0991,\"y\":0.4818,\"toX\":0.1003,\"toY\":0.5062,\"radius\":0.0483,\"z\":0.003},{\"tool\":\"flatten\",\"x\":0.1003,\"y\":0.5062,\"toX\":0.1017,\"toY\":0.5306,\"radius\":0.0483,\"z\":0.0023},{\"tool\":\"flatten\",\"x\":0.1017,\"y\":0.5306,\"toX\":0.103,\"toY\":0.5549,\"radius\":0.0483,\"z\":0.0016},{\"tool\":\"flatten\",\"x\":0.103,\"y\":0.5549,\"toX\":0.1041,\"toY\":0.5793,\"radius\":0.0483,\"z\":0.0011},{\"tool\":\"flatten\",\"x\":0.1041,\"y\":0.5793,\"toX\":0.1055,\"toY\":0.6038,\"radius\":0.0483,\"z\":0.0006},{\"tool\":\"flatten\",\"x\":0.1055,\"y\":0.6038,\"toX\":0.1073,\"toY\":0.6281,\"radius\":0.0483,\"z\":0.0003},{\"tool\":\"flatten\",\"x\":0.1073,\"y\":0.6281,\"toX\":0.1099,\"toY\":0.6523,\"radius\":0.0483,\"z\":0.0001},{\"tool\":\"flatten\",\"x\":0.1099,\"y\":0.6523,\"toX\":0.1136,\"toY\":0.6765,\"radius\":0.0483,\"z\":0.0001},{\"tool\":\"flatten\",\"x\":0.1136,\"y\":0.6765,\"toX\":0.1191,\"toY\":0.7003,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1191,\"y\":0.7003,\"toX\":0.1267,\"toY\":0.7234,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1267,\"y\":0.7234,\"toX\":0.136,\"toY\":0.7461,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.136,\"y\":0.7461,\"toX\":0.1465,\"toY\":0.7681,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1465,\"y\":0.7681,\"toX\":0.1602,\"toY\":0.7882,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1602,\"y\":0.7882,\"toX\":0.1785,\"toY\":0.8043,\"radius\":0.0483,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1785,\"y\":0.8043,\"toX\":0.207,\"toY\":0.8174,\"radius\":0.0483,\"z\":0},{\"tool\":\"smooth\",\"x\":0.207,\"y\":0.8174,\"toX\":0.2797,\"toY\":0.8241,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.2797,\"y\":0.8241,\"toX\":0.3528,\"toY\":0.8204,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.3528,\"y\":0.8204,\"toX\":0.426,\"toY\":0.8174,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.426,\"y\":0.8174,\"toX\":0.4993,\"toY\":0.8191,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.4993,\"y\":0.8191,\"toX\":0.5724,\"toY\":0.821,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.5724,\"y\":0.821,\"toX\":0.6455,\"toY\":0.8175,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.6455,\"y\":0.8175,\"toX\":0.7179,\"toY\":0.8065,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.7179,\"y\":0.8065,\"toX\":0.788,\"toY\":0.7858,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.788,\"y\":0.7858,\"toX\":0.8442,\"toY\":0.7402,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.8442,\"y\":0.7402,\"toX\":0.875,\"toY\":0.6742,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.875,\"y\":0.6742,\"toX\":0.8881,\"toY\":0.6023,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.8881,\"y\":0.6023,\"toX\":0.8837,\"toY\":0.5293,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.8837,\"y\":0.5293,\"toX\":0.8661,\"toY\":0.4584,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.8661,\"y\":0.4584,\"toX\":0.8276,\"toY\":0.3961,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.8276,\"y\":0.3961,\"toX\":0.8055,\"toY\":0.328,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.8055,\"y\":0.328,\"toX\":0.8319,\"toY\":0.2599,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.8319,\"y\":0.2599,\"toX\":0.8463,\"toY\":0.1904,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.8463,\"y\":0.1904,\"toX\":0.7966,\"toY\":0.1381,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.7966,\"y\":0.1381,\"toX\":0.7279,\"toY\":0.1155,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.7279,\"y\":0.1155,\"toX\":0.6581,\"toY\":0.1356,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.6581,\"y\":0.1356,\"toX\":0.5973,\"toY\":0.176,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.5973,\"y\":0.176,\"toX\":0.5685,\"toY\":0.2412,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.5685,\"y\":0.2412,\"toX\":0.5624,\"toY\":0.3141,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.5624,\"y\":0.3141,\"toX\":0.5259,\"toY\":0.3767,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.5259,\"y\":0.3767,\"toX\":0.4677,\"toY\":0.4193,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.4677,\"y\":0.4193,\"toX\":0.4032,\"toY\":0.3915,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.4032,\"y\":0.3915,\"toX\":0.3517,\"toY\":0.3395,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.3517,\"y\":0.3395,\"toX\":0.3069,\"toY\":0.2817,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.3069,\"y\":0.2817,\"toX\":0.2639,\"toY\":0.2224,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.2639,\"y\":0.2224,\"toX\":0.1985,\"toY\":0.2052,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.1985,\"y\":0.2052,\"toX\":0.1396,\"toY\":0.2476,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.1396,\"y\":0.2476,\"toX\":0.1055,\"toY\":0.3115,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.1055,\"y\":0.3115,\"toX\":0.0974,\"toY\":0.3843,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.0974,\"y\":0.3843,\"toX\":0.098,\"toY\":0.4574,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.098,\"y\":0.4574,\"toX\":0.1017,\"toY\":0.5306,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.1017,\"y\":0.5306,\"toX\":0.1055,\"toY\":0.6038,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.1055,\"y\":0.6038,\"toX\":0.1136,\"toY\":0.6765,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.1136,\"y\":0.6765,\"toX\":0.136,\"toY\":0.7461,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.136,\"y\":0.7461,\"toX\":0.1785,\"toY\":0.8043,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.1785,\"y\":0.8043,\"toX\":0.2552,\"toY\":0.824,\"radius\":0.0403,\"strength\":0.4},{\"tool\":\"paint\",\"x\":0.207,\"y\":0.8174,\"toX\":0.2797,\"toY\":0.8241,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.2797,\"y\":0.8241,\"toX\":0.3528,\"toY\":0.8204,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.3528,\"y\":0.8204,\"toX\":0.426,\"toY\":0.8174,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.426,\"y\":0.8174,\"toX\":0.4993,\"toY\":0.8191,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.4993,\"y\":0.8191,\"toX\":0.5724,\"toY\":0.821,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.5724,\"y\":0.821,\"toX\":0.6455,\"toY\":0.8175,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.6455,\"y\":0.8175,\"toX\":0.7179,\"toY\":0.8065,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.7179,\"y\":0.8065,\"toX\":0.788,\"toY\":0.7858,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.788,\"y\":0.7858,\"toX\":0.8442,\"toY\":0.7402,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.8442,\"y\":0.7402,\"toX\":0.875,\"toY\":0.6742,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.875,\"y\":0.6742,\"toX\":0.8881,\"toY\":0.6023,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.8881,\"y\":0.6023,\"toX\":0.8837,\"toY\":0.5293,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.8837,\"y\":0.5293,\"toX\":0.8661,\"toY\":0.4584,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.8661,\"y\":0.4584,\"toX\":0.8276,\"toY\":0.3961,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.8276,\"y\":0.3961,\"toX\":0.8055,\"toY\":0.328,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.8055,\"y\":0.328,\"toX\":0.8319,\"toY\":0.2599,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.8319,\"y\":0.2599,\"toX\":0.8463,\"toY\":0.1904,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.8463,\"y\":0.1904,\"toX\":0.7966,\"toY\":0.1381,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.7966,\"y\":0.1381,\"toX\":0.7279,\"toY\":0.1155,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.7279,\"y\":0.1155,\"toX\":0.6581,\"toY\":0.1356,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.6581,\"y\":0.1356,\"toX\":0.5973,\"toY\":0.176,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.5973,\"y\":0.176,\"toX\":0.5685,\"toY\":0.2412,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.5685,\"y\":0.2412,\"toX\":0.5624,\"toY\":0.3141,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.5624,\"y\":0.3141,\"toX\":0.5259,\"toY\":0.3767,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.5259,\"y\":0.3767,\"toX\":0.4677,\"toY\":0.4193,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.4677,\"y\":0.4193,\"toX\":0.4032,\"toY\":0.3915,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.4032,\"y\":0.3915,\"toX\":0.3517,\"toY\":0.3395,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.3517,\"y\":0.3395,\"toX\":0.3069,\"toY\":0.2817,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.3069,\"y\":0.2817,\"toX\":0.2639,\"toY\":0.2224,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.2639,\"y\":0.2224,\"toX\":0.1985,\"toY\":0.2052,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.1985,\"y\":0.2052,\"toX\":0.1396,\"toY\":0.2476,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.1396,\"y\":0.2476,\"toX\":0.1055,\"toY\":0.3115,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.1055,\"y\":0.3115,\"toX\":0.0974,\"toY\":0.3843,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.0974,\"y\":0.3843,\"toX\":0.098,\"toY\":0.4574,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.098,\"y\":0.4574,\"toX\":0.1017,\"toY\":0.5306,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.1017,\"y\":0.5306,\"toX\":0.1055,\"toY\":0.6038,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.1055,\"y\":0.6038,\"toX\":0.1136,\"toY\":0.6765,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.1136,\"y\":0.6765,\"toX\":0.136,\"toY\":0.7461,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.136,\"y\":0.7461,\"toX\":0.1785,\"toY\":0.8043,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.1785,\"y\":0.8043,\"toX\":0.2552,\"toY\":0.824,\"radius\":0.0387,\"layer\":4,\"strength\":0.7},{\"tool\":\"paint\",\"x\":0.207,\"y\":0.8174,\"toX\":0.2797,\"toY\":0.8241,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.2797,\"y\":0.8241,\"toX\":0.3528,\"toY\":0.8204,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3528,\"y\":0.8204,\"toX\":0.426,\"toY\":0.8174,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.426,\"y\":0.8174,\"toX\":0.4993,\"toY\":0.8191,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.4993,\"y\":0.8191,\"toX\":0.5724,\"toY\":0.821,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5724,\"y\":0.821,\"toX\":0.6455,\"toY\":0.8175,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.6455,\"y\":0.8175,\"toX\":0.7179,\"toY\":0.8065,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.7179,\"y\":0.8065,\"toX\":0.788,\"toY\":0.7858,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.788,\"y\":0.7858,\"toX\":0.8442,\"toY\":0.7402,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8442,\"y\":0.7402,\"toX\":0.875,\"toY\":0.6742,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.875,\"y\":0.6742,\"toX\":0.8881,\"toY\":0.6023,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8881,\"y\":0.6023,\"toX\":0.8837,\"toY\":0.5293,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8837,\"y\":0.5293,\"toX\":0.8661,\"toY\":0.4584,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8661,\"y\":0.4584,\"toX\":0.8276,\"toY\":0.3961,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8276,\"y\":0.3961,\"toX\":0.8055,\"toY\":0.328,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8055,\"y\":0.328,\"toX\":0.8319,\"toY\":0.2599,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8319,\"y\":0.2599,\"toX\":0.8463,\"toY\":0.1904,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8463,\"y\":0.1904,\"toX\":0.7966,\"toY\":0.1381,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.7966,\"y\":0.1381,\"toX\":0.7279,\"toY\":0.1155,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.7279,\"y\":0.1155,\"toX\":0.6581,\"toY\":0.1356,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.6581,\"y\":0.1356,\"toX\":0.5973,\"toY\":0.176,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5973,\"y\":0.176,\"toX\":0.5685,\"toY\":0.2412,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5685,\"y\":0.2412,\"toX\":0.5624,\"toY\":0.3141,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5624,\"y\":0.3141,\"toX\":0.5259,\"toY\":0.3767,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5259,\"y\":0.3767,\"toX\":0.4677,\"toY\":0.4193,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.4677,\"y\":0.4193,\"toX\":0.4032,\"toY\":0.3915,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.4032,\"y\":0.3915,\"toX\":0.3517,\"toY\":0.3395,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3517,\"y\":0.3395,\"toX\":0.3069,\"toY\":0.2817,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3069,\"y\":0.2817,\"toX\":0.2639,\"toY\":0.2224,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.2639,\"y\":0.2224,\"toX\":0.1985,\"toY\":0.2052,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1985,\"y\":0.2052,\"toX\":0.1396,\"toY\":0.2476,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1396,\"y\":0.2476,\"toX\":0.1055,\"toY\":0.3115,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1055,\"y\":0.3115,\"toX\":0.0974,\"toY\":0.3843,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.0974,\"y\":0.3843,\"toX\":0.098,\"toY\":0.4574,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.098,\"y\":0.4574,\"toX\":0.1017,\"toY\":0.5306,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1017,\"y\":0.5306,\"toX\":0.1055,\"toY\":0.6038,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1055,\"y\":0.6038,\"toX\":0.1136,\"toY\":0.6765,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1136,\"y\":0.6765,\"toX\":0.136,\"toY\":0.7461,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.136,\"y\":0.7461,\"toX\":0.1785,\"toY\":0.8043,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1785,\"y\":0.8043,\"toX\":0.2552,\"toY\":0.824,\"radius\":0.0295,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3169,\"y\":0.8723,\"toX\":0.5854,\"toY\":0.8723,\"radius\":0.0244,\"layer\":2,\"strength\":1}]", + "Layer1Color": "255;255;255", + "Layer1Texture": "assets/TerrainGrass.jpg", + "Layer1NormalMap": "assets/TerrainGrassNormal.jpg", + "Layer2Color": "255;255;255", + "Layer2Texture": "assets/TerrainAsphalt.jpg", + "Layer2NormalMap": "assets/TerrainAsphaltNormal.jpg", + "Layer3Color": "255;255;255", + "Layer3Texture": "assets/TerrainRock.jpg", + "Layer3NormalMap": "assets/TerrainRockNormal.jpg", + "Layer4Color": "255;255;255", + "Layer4Texture": "assets/TerrainGravel.jpg", + "Layer4NormalMap": "assets/TerrainGravelNormal.jpg" + } + }, + { + "assetStoreId": "", + "name": "MapLimit", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "width": 100, + "height": 100, + "depth": 100, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "frontFaceResourceName": "", + "backFaceResourceName": "", + "leftFaceResourceName": "", + "rightFaceResourceName": "", + "topFaceResourceName": "", + "bottomFaceResourceName": "", + "backFaceUpThroughWhichAxisRotation": "X", + "frontFaceVisible": false, + "backFaceVisible": false, + "leftFaceVisible": false, + "rightFaceVisible": false, + "topFaceVisible": false, + "bottomFaceVisible": false, + "frontFaceResourceRepeat": true, + "backFaceResourceRepeat": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceRepeat": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceRepeat": false, + "materialType": "StandardWithoutMetalness", + "isCastingShadow": false, + "isReceivingShadow": true + } + }, + { + "assetStoreId": "", + "name": "Grass", + "type": "Terrain3D::TerrainGrass", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Density": 20, + "BendingObject": "PlayerCar", + "BladeHeight": 14, + "BladeWidth": 2, + "BaseColor": "40;62;22", + "TipColor": "112;130;52", + "FadeDistance": 2500 + } + }, + { + "assetStoreId": "ea5c320b1b9f604b45a6af7748b0a5aa04ec92d8d362bb98bd049a9bbcd87bc5", + "name": "GrandStand", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 420, + "height": 361.1, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Grand Stand Covered.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 354, + "animations": [] + } + }, + { + "assetStoreId": "960a8b961079a74e859ca4a0d23e0a5760e9067e32a88b7e6f7c4a7717701110", + "name": "PitsGarage", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 192.7, + "height": 300, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Pits Garage.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 275.2, + "animations": [] + } + }, + { + "assetStoreId": "bffac80ac25bb3c2d5e9d6f8313b596b8a2e88884484f6c880d8648ec44773cf", + "name": "PitsOffice", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 294.8, + "height": 360, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Pits Office Roof.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 346.2, + "animations": [] + } + }, + { + "assetStoreId": "c06b079155e9b9aa894523c61adf3303e39b2883b62e5a399d04fe28cfbb5eaf", + "name": "OverheadLights", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 286.4, + "height": 78.4, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Overhead Lights.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 520, + "animations": [] + } + }, + { + "assetStoreId": "bda8cee1be3e5d1eb1cda41ae857c92851824e90b7f773f105dc81bb6dcda4a2", + "name": "Billboard", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 260, + "height": 123.8, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Billboard.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 260, + "animations": [] + } + }, + { + "assetStoreId": "5517aa7e8bd5de5bc367d44b14c31983a9da0c4acc10d4c62dd559f23c9aa253", + "name": "Barrier", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 78.7, + "height": 73.8, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Barrier White.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 150, + "animations": [] + } + }, + { + "assetStoreId": "43bea02639c48bf780fd3f64ec5212277a45f677cdd713d68d9f89d971e3a908", + "name": "BarrierRed", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 78.7, + "height": 73.8, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Barrier Red.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 150, + "animations": [] + } + }, + { + "assetStoreId": "7a7b272ce67718b9b28aad8ae670adefe4f02bd018789faee21f06a9546e92ed", + "name": "Tent", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 165.5, + "height": 260, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Tent Roof.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 147.1, + "animations": [] + } + }, + { + "assetStoreId": "8e7eaf9d0abd51143774f81fc1493fbe82a51f1e226a2fc49a4b1c90444ce8b3", + "name": "FlagCheckers", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 160, + "height": 4.5, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Flag Checkers.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 25.2, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "Trees", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "Model": "assets/Tree Default.glb", + "Size": 280, + "Density": 0.12, + "Seed": 1, + "Coverage": "Painted", + "Edits": "[{\"tool\":\"add\",\"x\":0.2635,\"y\":0.5437,\"radius\":0.0537},{\"tool\":\"add\",\"x\":0.2218,\"y\":0.3529,\"radius\":0.0507},{\"tool\":\"add\",\"x\":0.3344,\"y\":0.6191,\"radius\":0.0522},{\"tool\":\"add\",\"x\":0.4663,\"y\":0.1039,\"radius\":0.0616},{\"tool\":\"add\",\"x\":0.6819,\"y\":0.6298,\"radius\":0.0464},{\"tool\":\"add\",\"x\":0.4348,\"y\":0.1554,\"radius\":0.0488},{\"tool\":\"add\",\"x\":0.4551,\"y\":0.525,\"radius\":0.0477},{\"tool\":\"add\",\"x\":0.408,\"y\":0.1903,\"radius\":0.0474},{\"tool\":\"add\",\"x\":0.6476,\"y\":0.4825,\"radius\":0.059},{\"tool\":\"add\",\"x\":0.3716,\"y\":0.5291,\"radius\":0.0649},{\"tool\":\"add\",\"x\":0.6993,\"y\":0.6394,\"radius\":0.0623},{\"tool\":\"add\",\"x\":0.3185,\"y\":0.6984,\"radius\":0.0648},{\"tool\":\"erase\",\"x\":0.4146,\"y\":0.8174,\"radius\":0.1831},{\"tool\":\"erase\",\"x\":0.4346,\"y\":0.8174,\"radius\":0.0366},{\"tool\":\"erase\",\"x\":0.2437,\"y\":0.7539,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.3047,\"y\":0.7539,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.3657,\"y\":0.7539,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.4878,\"y\":0.7539,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.5488,\"y\":0.7539,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.6099,\"y\":0.7539,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.3291,\"y\":0.9028,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.3682,\"y\":0.9028,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.4072,\"y\":0.9028,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.4463,\"y\":0.9028,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.4854,\"y\":0.9028,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.5244,\"y\":0.9028,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.5684,\"y\":0.9028,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.207,\"y\":0.9028,\"radius\":0.0208},{\"tool\":\"erase\",\"x\":0.2498,\"y\":0.9028,\"radius\":0.0208},{\"tool\":\"erase\",\"x\":0.2121,\"y\":0.8545,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.8411,\"y\":0.7942,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.778,\"y\":0.3758,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.7709,\"y\":0.3422,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.7743,\"y\":0.3073,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.8793,\"y\":0.2361,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.8828,\"y\":0.1986,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.8698,\"y\":0.1613,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.85,\"y\":0.1363,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.7753,\"y\":0.089,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.7443,\"y\":0.0818,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.7109,\"y\":0.0821,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.5625,\"y\":0.1621,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.544,\"y\":0.1924,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.5914,\"y\":0.3378,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.5213,\"y\":0.4297,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.4933,\"y\":0.4466,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.4542,\"y\":0.4542,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.4162,\"y\":0.4438,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.2779,\"y\":0.1877,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.2429,\"y\":0.1692,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.2034,\"y\":0.1681,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.1719,\"y\":0.1788,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.124,\"y\":0.798,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.1459,\"y\":0.8228,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.178,\"y\":0.843,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.6531,\"y\":0.8756,\"radius\":0.0208},{\"tool\":\"erase\",\"x\":0.7927,\"y\":0.4492,\"radius\":0.0208},{\"tool\":\"erase\",\"x\":0.6417,\"y\":0.2143,\"radius\":0.0208},{\"tool\":\"erase\",\"x\":0.1678,\"y\":0.6433,\"radius\":0.0208}]", + "MaxGroundZ": 160, + "MaxSlope": 25, + "TrunkCollisions": true, + "TrunkDiameter": 28 + } + }, + { + "assetStoreId": "", + "name": "PineTrees", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "Model": "assets/Tree Pine Default A.glb", + "Size": 340, + "Density": 0.3, + "Seed": 2, + "Coverage": "Painted", + "Edits": "[{\"tool\":\"add\",\"x\":0.2002,\"y\":0.3579,\"radius\":0.0261},{\"tool\":\"add\",\"x\":0.223,\"y\":0.2997,\"radius\":0.0323},{\"tool\":\"add\",\"x\":0.2003,\"y\":0.3381,\"radius\":0.0211},{\"tool\":\"add\",\"x\":0.7577,\"y\":0.4452,\"radius\":0.0344},{\"tool\":\"add\",\"x\":0.2054,\"y\":0.3472,\"radius\":0.0275},{\"tool\":\"add\",\"x\":0.8983,\"y\":0.3542,\"radius\":0.0214},{\"tool\":\"add\",\"x\":0.7546,\"y\":0.4084,\"radius\":0.021},{\"tool\":\"erase\",\"x\":0.4346,\"y\":0.8174,\"radius\":0.0366},{\"tool\":\"erase\",\"x\":0.2437,\"y\":0.7539,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.3047,\"y\":0.7539,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.3657,\"y\":0.7539,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.4878,\"y\":0.7539,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.5488,\"y\":0.7539,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.6099,\"y\":0.7539,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.3291,\"y\":0.9028,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.3682,\"y\":0.9028,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.4072,\"y\":0.9028,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.4463,\"y\":0.9028,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.4854,\"y\":0.9028,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.5244,\"y\":0.9028,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.5684,\"y\":0.9028,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.207,\"y\":0.9028,\"radius\":0.0208},{\"tool\":\"erase\",\"x\":0.2498,\"y\":0.9028,\"radius\":0.0208},{\"tool\":\"erase\",\"x\":0.2121,\"y\":0.8545,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.8411,\"y\":0.7942,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.778,\"y\":0.3758,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.7709,\"y\":0.3422,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.7743,\"y\":0.3073,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.8793,\"y\":0.2361,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.8828,\"y\":0.1986,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.8698,\"y\":0.1613,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.85,\"y\":0.1363,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.7753,\"y\":0.089,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.7443,\"y\":0.0818,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.7109,\"y\":0.0821,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.5625,\"y\":0.1621,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.544,\"y\":0.1924,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.5914,\"y\":0.3378,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.5213,\"y\":0.4297,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.4933,\"y\":0.4466,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.4542,\"y\":0.4542,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.4162,\"y\":0.4438,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.2779,\"y\":0.1877,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.2429,\"y\":0.1692,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.2034,\"y\":0.1681,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.1719,\"y\":0.1788,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.124,\"y\":0.798,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.1459,\"y\":0.8228,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.178,\"y\":0.843,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.6531,\"y\":0.8756,\"radius\":0.0208},{\"tool\":\"erase\",\"x\":0.7927,\"y\":0.4492,\"radius\":0.0208},{\"tool\":\"erase\",\"x\":0.6417,\"y\":0.2143,\"radius\":0.0208},{\"tool\":\"erase\",\"x\":0.1678,\"y\":0.6433,\"radius\":0.0208}]", + "MinGroundZ": 120, + "MaxGroundZ": 560, + "MaxSlope": 28, + "TrunkCollisions": true, + "TrunkDiameter": 24 + } + }, + { + "assetStoreId": "", + "name": "Rocks", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "Model": "assets/Rock Large A.glb", + "Size": 46, + "Density": 0.06, + "Seed": 3, + "Coverage": "Painted", + "Edits": "[{\"tool\":\"add\",\"x\":0.1321,\"y\":0.1101,\"radius\":0.0398,\"density\":1},{\"tool\":\"add\",\"x\":0.1262,\"y\":0.9385,\"radius\":0.0449,\"density\":1},{\"tool\":\"add\",\"x\":0.475,\"y\":0.718,\"radius\":0.0258,\"density\":1},{\"tool\":\"add\",\"x\":0.457,\"y\":0.6403,\"radius\":0.0485,\"density\":1},{\"tool\":\"add\",\"x\":0.6945,\"y\":0.3838,\"radius\":0.0369,\"density\":1},{\"tool\":\"add\",\"x\":0.6885,\"y\":0.2445,\"radius\":0.032,\"density\":1},{\"tool\":\"add\",\"x\":0.7798,\"y\":0.6312,\"radius\":0.0322,\"density\":1},{\"tool\":\"add\",\"x\":0.5742,\"y\":0.5272,\"radius\":0.0427,\"density\":1},{\"tool\":\"add\",\"x\":0.7095,\"y\":0.5465,\"radius\":0.0319,\"density\":1},{\"tool\":\"add\",\"x\":0.2457,\"y\":0.4232,\"radius\":0.0303,\"density\":1},{\"tool\":\"add\",\"x\":0.262,\"y\":0.5504,\"radius\":0.0487,\"density\":1},{\"tool\":\"add\",\"x\":0.7567,\"y\":0.6095,\"radius\":0.0388,\"density\":1},{\"tool\":\"add\",\"x\":0.6825,\"y\":0.6858,\"radius\":0.0391,\"density\":1},{\"tool\":\"add\",\"x\":0.1908,\"y\":0.4464,\"radius\":0.026,\"density\":1},{\"tool\":\"erase\",\"x\":0.4146,\"y\":0.8174,\"radius\":0.1831},{\"tool\":\"erase\",\"x\":0.4346,\"y\":0.8174,\"radius\":0.0366},{\"tool\":\"erase\",\"x\":0.2437,\"y\":0.7539,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.3047,\"y\":0.7539,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.3657,\"y\":0.7539,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.4878,\"y\":0.7539,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.5488,\"y\":0.7539,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.6099,\"y\":0.7539,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.3291,\"y\":0.9028,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.3682,\"y\":0.9028,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.4072,\"y\":0.9028,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.4463,\"y\":0.9028,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.4854,\"y\":0.9028,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.5244,\"y\":0.9028,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.5684,\"y\":0.9028,\"radius\":0.0269},{\"tool\":\"erase\",\"x\":0.207,\"y\":0.9028,\"radius\":0.0208},{\"tool\":\"erase\",\"x\":0.2498,\"y\":0.9028,\"radius\":0.0208},{\"tool\":\"erase\",\"x\":0.2121,\"y\":0.8545,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.8411,\"y\":0.7942,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.778,\"y\":0.3758,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.7709,\"y\":0.3422,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.7743,\"y\":0.3073,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.8793,\"y\":0.2361,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.8828,\"y\":0.1986,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.8698,\"y\":0.1613,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.85,\"y\":0.1363,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.7753,\"y\":0.089,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.7443,\"y\":0.0818,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.7109,\"y\":0.0821,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.5625,\"y\":0.1621,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.544,\"y\":0.1924,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.5914,\"y\":0.3378,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.5213,\"y\":0.4297,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.4933,\"y\":0.4466,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.4542,\"y\":0.4542,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.4162,\"y\":0.4438,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.2779,\"y\":0.1877,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.2429,\"y\":0.1692,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.2034,\"y\":0.1681,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.1719,\"y\":0.1788,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.124,\"y\":0.798,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.1459,\"y\":0.8228,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.178,\"y\":0.843,\"radius\":0.014},{\"tool\":\"erase\",\"x\":0.6531,\"y\":0.8756,\"radius\":0.0208},{\"tool\":\"erase\",\"x\":0.7927,\"y\":0.4492,\"radius\":0.0208},{\"tool\":\"erase\",\"x\":0.6417,\"y\":0.2143,\"radius\":0.0208},{\"tool\":\"erase\",\"x\":0.1678,\"y\":0.6433,\"radius\":0.0208}]", + "MaxSlope": 30, + "MinScale": 0.6, + "MaxScale": 1.3, + "TrunkCollisions": true, + "TrunkDiameter": 110 + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "FinishLine_3D" + }, + { + "objectName": "CheckpointArrow" + }, + { + "objectName": "PlayerCar" + }, + { + "objectName": "TrafficCone" + }, + { + "objectName": "LapText" + }, + { + "folderName": "Mobile Controls", + "children": [ + { + "objectName": "Speed_Joystick" + }, + { + "objectName": "Steer_Joystick" + } + ] + }, + { + "folderName": "World", + "children": [ + { + "objectName": "Terrain" + }, + { + "objectName": "MapLimit" + }, + { + "objectName": "Grass" + } + ] + }, + { + "folderName": "Decoration", + "children": [ + { + "objectName": "GrandStand" + }, + { + "objectName": "PitsGarage" + }, + { + "objectName": "PitsOffice" + }, + { + "objectName": "OverheadLights" + }, + { + "objectName": "Billboard" + }, + { + "objectName": "Barrier" + }, + { + "objectName": "BarrierRed" + }, + { + "objectName": "Tent" + }, + { + "objectName": "FlagCheckers" + } + ] + }, + { + "folderName": "Nature", + "children": [ + { + "objectName": "Trees" + }, + { + "objectName": "PineTrees" + }, + { + "objectName": "Rocks" + } + ] + } + ] + }, + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberObjectVariable" + }, + "parameters": [ + "PlayerCar", + "LastCheckPoint", + "=", + "SceneInstancesCount(CheckpointArrow)" + ] + }, + { + "type": { + "value": "Hide" + }, + "parameters": [ + "CheckpointArrow" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "PickNearest" + }, + "parameters": [ + "CheckpointArrow", + "PlayerCar.CenterX()", + "PlayerCar.CenterY()", + "" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "AngleDifference(CheckpointArrow.Angle(), CheckpointArrow.AngleToObject(PlayerCar))", + "<", + "90" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "AngleDifference(CheckpointArrow.Angle(), CheckpointArrow.AngleToObject(PlayerCar))", + ">", + "-90" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberObjectVariable" + }, + "parameters": [ + "CheckpointArrow", + "Checkpoint", + "=", + "PlayerCar.LastCheckPoint + 1" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberObjectVariable" + }, + "parameters": [ + "PlayerCar", + "LastCheckPoint", + "=", + "CheckpointArrow.Checkpoint" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanObjectVariable" + }, + "parameters": [ + "CheckpointArrow", + "FinishLine", + "True", + "Player.LastCheckPoint+1" + ] + }, + { + "type": { + "value": "NumberObjectVariable" + }, + "parameters": [ + "PlayerCar", + "LastCheckPoint", + "=", + "SceneInstancesCount(CheckpointArrow)" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberObjectVariable" + }, + "parameters": [ + "PlayerCar", + "LastCheckPoint", + "=", + "1" + ] + }, + { + "type": { + "value": "SetNumberObjectVariable" + }, + "parameters": [ + "PlayerCar", + "LapNumber", + "+", + "1" + ] + }, + { + "type": { + "value": "ResetObjectTimer" + }, + "parameters": [ + "PlayerCar", + "\"LapTimer\"" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "LapText", + "Text", + "=", + "\"Time: \" + ToString(roundTo(PlayerCar.ObjectTimerElapsedTime(\"LapTimer\"), 1)) + \"\nLap: \" + PlayerCar.LapNumber" + ] + } + ] + } + ], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 12000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [ + { + "effectType": "Scene3D::DirectionalLight", + "name": "3D Sun Light", + "doubleParameters": { + "distanceFromCamera": 1500, + "elevation": 40, + "frustumSize": 2500, + "intensity": 0.75, + "minimumShadowBias": 0.00001, + "rotation": 300, + "shadowDistance": 3000 + }, + "stringParameters": { + "color": "255;255;255", + "shadowQuality": "medium", + "shadowRenderingMode": "fittedToCameraView", + "top": "Z+" + }, + "booleanParameters": { + "isCastingShadow": true + } + }, + { + "effectType": "Scene3D::HemisphereLight", + "name": "3D Ambient Hemisphere Light", + "doubleParameters": { + "elevation": 40, + "intensity": 0.33, + "rotation": 300 + }, + "stringParameters": { + "groundColor": "127;127;127", + "skyColor": "255;255;255", + "top": "Z+" + }, + "booleanParameters": {} + }, + { + "effectType": "Scene3D::Skybox", + "name": "SkyBox", + "doubleParameters": {}, + "stringParameters": { + "backFaceResourceName": "assets/Skybox_Back.png", + "bottomFaceResourceName": "assets/Skybox_Bottom.png", + "frontFaceResourceName": "assets/Skybox_Front.png", + "leftFaceResourceName": "assets/Skybox_Left.png", + "rightFaceResourceName": "assets/Skybox_Right.png", + "top": "Z+", + "topFaceResourceName": "assets/Skybox_Top.png" + }, + "booleanParameters": {} + } + ] + }, + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 12000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "MobileControls", + "renderingType": "", + "visibility": true, + "cameras": [], + "effects": [] + }, + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 12000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "UI", + "renderingType": "", + "visibility": true, + "cameras": [], + "effects": [] + } + ], + "behaviorsSharedData": [ + { + "name": "Animation", + "type": "AnimatableCapability::AnimatableBehavior" + }, + { + "name": "Effect", + "type": "EffectCapability::EffectBehavior" + }, + { + "name": "Flippable", + "type": "FlippableCapability::FlippableBehavior" + }, + { + "name": "Object3D", + "type": "Scene3D::Base3DBehavior" + }, + { + "name": "Opacity", + "type": "OpacityCapability::OpacityBehavior" + }, + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "gravityX": 0, + "gravityY": 0, + "gravityZ": -9.8, + "worldScale": 20 + }, + { + "name": "PhysicsCar3D", + "type": "Physics3D::PhysicsCar3D" + }, + { + "name": "PhysicsCar3DKeyboardMapper", + "type": "PhysicsCar3DKeyMapper::PhysicsCar3DKeyboardMapper" + }, + { + "name": "PhysicsCar3DMultitouchMapper", + "type": "SpriteMultitouchJoystick::PhysicsCar3DMultitouchMapper" + }, + { + "name": "Resizable", + "type": "ResizableCapability::ResizableBehavior" + }, + { + "name": "Scale", + "type": "ScalableCapability::ScalableBehavior" + }, + { + "name": "Text", + "type": "TextContainerCapability::TextContainerBehavior" + }, + { + "name": "ThirdPersonCamera", + "type": "ThirdPersonCamera::ThirdPersonCamera" + } + ] + } + ], + "externalEvents": [], + "tests": [ + { + "description": "Holding the accelerator revs the engine and drives the car forward along its heading; it stays put when nothing is pressed.", + "name": "Accelerating drives the car forward", + "type": "gameplay", + "source": [ + "// The core of the game: the accelerator drives the car forward along its", + "// heading (\"Up\", per PhysicsCar3DKeyboardMapper).", + "await harness.goToScene('Game Scene');", + "// Faster in the software renderer used to run tests (grass is thousands of blades, trees are drawn in batches it is slow with).", + "harness.setGameResolutionSize(160, 90);", + "harness.getCurrentRuntimeScene().getLayer('').setEffectBooleanParameter('3D Sun Light', 'isCastingShadow', false);", + "for (const grass of harness.getCurrentRuntimeScene().getObjects('Grass')) grass.hide(true);", + "for (const object of harness.getCurrentRuntimeScene().getAdhocListOfAllInstances()) if (object.type === 'Terrain3D::ScatteredModels') object.hide(true);", + "harness.setTimeoutMs(60000);", + "harness.watch('PlayerCar');", + "", + "const getCar = () => harness.getObjects('PlayerCar')[0];", + "", + "// Let the car settle on the road.", + "await harness.stepFrames(10);", + "const start = getCar();", + "harness.assert(", + " start.behaviors.PhysicsCar3D.state.IsOnFloor === true,", + " 'The car rests on the road.'", + ");", + "const headingRadians = (start.angle * Math.PI) / 180;", + "", + "// Without any input the car does not drive away by itself.", + "await harness.stepFrames(8);", + "const idle = getCar();", + "const idleDistance = Math.hypot(idle.centerX - start.centerX, idle.centerY - start.centerY);", + "harness.assert(", + " idleDistance < 10,", + " 'The car stays put while no key is pressed (drifted ' + idleDistance.toFixed(1) + 'px).'", + ");", + "", + "// Accelerate.", + "harness.setKeyPressed('Up', true);", + "let maxEngineSpeed = 0;", + "await harness.stepFrames(38, {", + " onFrame: () => {", + " maxEngineSpeed = Math.max(", + " maxEngineSpeed,", + " getCar().behaviors.PhysicsCar3D.state.EngineSpeed", + " );", + " },", + "});", + "harness.releaseAllInputs();", + "const after = getCar();", + "", + "const travelX = after.centerX - idle.centerX;", + "const travelY = after.centerY - idle.centerY;", + "const travelled = Math.hypot(travelX, travelY);", + "const forwardDistance =", + " travelX * Math.cos(headingRadians) + travelY * Math.sin(headingRadians);", + "console.log(", + " 'travelled=' + Math.round(travelled) +", + " ' forward=' + Math.round(forwardDistance) +", + " ' maxEngineSpeed=' + Math.round(maxEngineSpeed) +", + " ' gear=' + after.behaviors.PhysicsCar3D.state.CurrentGear +", + " ' angleDelta=' + Math.round(after.angle - start.angle)", + ");", + "", + "harness.assert(", + " maxEngineSpeed > start.behaviors.PhysicsCar3D.state.EngineSpeed,", + " 'The engine revs up while accelerating (reached ' + Math.round(maxEngineSpeed) + ').'", + ");", + "harness.assert(", + " forwardDistance > 65,", + " 'Holding the accelerator drives the car forward (drove ' +", + " Math.round(forwardDistance) + 'px along its heading).'", + ");", + "harness.assert(", + " forwardDistance > 0.9 * travelled,", + " 'The car drives along its heading rather than sideways.'", + ");", + "harness.assert(", + " Math.abs(after.angle - start.angle) < 15,", + " 'The car keeps going straight while no steering key is pressed (turned by ' +", + " Math.round(after.angle - start.angle) + ' degrees).'", + ");" + ] + }, + { + "description": "The car is lined up before the finish line and driven over it: the lap counter goes up and the next checkpoint is the first of the new lap.", + "name": "Driving over the finish line counts a lap", + "type": "gameplay", + "source": [ + "// What makes it a race: driving past the finish line counts a lap and puts", + "// the car back on the first checkpoint.", + "await harness.goToScene('Game Scene');", + "// Faster in the software renderer used to run tests (grass is thousands of blades, trees are drawn in batches it is slow with).", + "harness.setGameResolutionSize(160, 90);", + "harness.getCurrentRuntimeScene().getLayer('').setEffectBooleanParameter('3D Sun Light', 'isCastingShadow', false);", + "for (const grass of harness.getCurrentRuntimeScene().getObjects('Grass')) grass.hide(true);", + "for (const object of harness.getCurrentRuntimeScene().getAdhocListOfAllInstances()) if (object.type === 'Terrain3D::ScatteredModels') object.hide(true);", + "harness.setTimeoutMs(60000);", + "harness.watch('PlayerCar');", + "", + "const getCar = () => harness.getObjects('PlayerCar')[0];", + "/** The race progress the game keeps in the car's own variables. */", + "const readProgress = () => {", + " const read = (name) => {", + " const variable = harness.getObjectVariable('PlayerCar', name);", + " return variable ? Number(variable.value) : null;", + " };", + " return { lap: read('LapNumber'), checkpoint: read('LastCheckPoint') };", + "};", + "", + "await harness.stepFrames(10);", + "const finishLine = harness.getObjects('CheckpointArrow').find((arrow) => {", + " const variable = arrow.variables.find((one) => one.name === 'FinishLine');", + " return variable && variable.value === true;", + "});", + "harness.assert(!!finishLine, 'The track has a finish line.');", + "", + "const atStart = readProgress();", + "console.log('atStart=' + JSON.stringify(atStart));", + "harness.assert(atStart.lap === 0, 'No lap has been run yet.');", + "", + "// Arrange: line the car up a short run-up before the finish line, on the", + "// track it starts on and pointing the way it already points. Driving over", + "// the line, and what that counts for, is still up to the game.", + "const car = getCar();", + "const RUN_UP = 100;", + "harness.assert(", + " car.centerX < finishLine.centerX,", + " 'The car starts before the finish line.'", + ");", + "harness.setObjectPosition(", + " car.id,", + " car.x + (finishLine.centerX - RUN_UP - car.centerX),", + " car.y + (finishLine.centerY - car.centerY),", + " car.z", + ");", + "await harness.stepFrames(8);", + "", + "// Being next to the finish line is not enough: it has to be driven over.", + "const beforeDriving = readProgress();", + "harness.assert(", + " beforeDriving.lap === 0,", + " 'Standing just before the finish line does not count a lap.'", + ");", + "", + "harness.setKeyPressed('Up', true);", + "const lapped = await harness.stepUntil(() => readProgress().lap >= 1, {", + " maxFrames: 55,", + "});", + "harness.releaseAllInputs();", + "", + "const finalCar = getCar();", + "const finalProgress = readProgress();", + "console.log(", + " 'final=' + JSON.stringify(finalProgress) +", + " ' carX=' + Math.round(finalCar.centerX) +", + " ' finishLineX=' + Math.round(finishLine.centerX)", + ");", + "", + "harness.assert(", + " finalCar.centerX > finishLine.centerX,", + " 'The car drove over the finish line (it is at x=' +", + " Math.round(finalCar.centerX) + ', the line is at x=' +", + " Math.round(finishLine.centerX) + ').'", + ");", + "harness.assert(", + " lapped && finalProgress.lap === 1,", + " 'Driving over the finish line counts a lap (the lap counter is at ' +", + " finalProgress.lap + ').'", + ");", + "harness.assert(", + " finalProgress.checkpoint === 1,", + " 'The next checkpoint to reach is the first one of the new lap (it is ' +", + " finalProgress.checkpoint + ').'", + ");", + "", + "// A picture of what the game looks like once this has happened, kept with", + "// the test results.", + "await harness.takeScreenshot('the car just past the finish line');" + ] + }, + { + "name": "Passing the checkpoints in order counts a lap", + "description": "Moves the car over each checkpoint of the track in order, then over the finish line: a lap is counted.", + "type": "gameplay", + "source": [ + "await harness.goToScene('Game Scene');", + "// Faster in the software renderer used to run tests (grass is thousands of blades, trees are drawn in batches it is slow with).", + "harness.setGameResolutionSize(160, 90);", + "harness.getCurrentRuntimeScene().getLayer('').setEffectBooleanParameter('3D Sun Light', 'isCastingShadow', false);", + "for (const grass of harness.getCurrentRuntimeScene().getObjects('Grass')) grass.hide(true);", + "for (const object of harness.getCurrentRuntimeScene().getAdhocListOfAllInstances()) if (object.type === 'Terrain3D::ScatteredModels') object.hide(true);", + "harness.setTimeoutMs(60000);", + "const DT = { dtMs: 50 };", + "const terrain = harness.getRuntimeObject(harness.getObjects('Terrain')[0].id).__terrain3D;", + "const getCar = () => harness.getObjects('PlayerCar')[0];", + "const readLap = () => Number(harness.getObjectVariable('PlayerCar', 'LapNumber').value);", + "const readCheckpoint = () => Number(harness.getObjectVariable('PlayerCar', 'LastCheckPoint').value);", + "await harness.stepFrames(5, DT);", + "// Cross the finish line a first time: the race starts.", + "const arrows = harness.getObjects('CheckpointArrow');", + "const arrowOf = (n) => arrows.find((arrow) => arrow.variables.some((v) => v.name === 'Checkpoint' && v.value === n));", + "/** Put the car just before an arrow, pointing the way it points, then just after it. */", + "const crossArrow = async (arrow) => {", + " const radians = (arrow.angle * Math.PI) / 180;", + " for (const offset of [-60, 60]) {", + " const x = arrow.centerX + Math.cos(radians) * offset;", + " const y = arrow.centerY + Math.sin(radians) * offset;", + " harness.setObjectPosition(getCar().id, x - getCar().width / 2, y - getCar().height / 2, terrain.getHeightAt(x, y) + 5);", + " harness.getRuntimeObject(getCar().id).setAngle(arrow.angle);", + " await harness.stepFrames(3, DT);", + " }", + "};", + "await crossArrow(arrowOf(1));", + "const startLap = readLap();", + "for (let n = 2; n <= 5; n++) {", + " await crossArrow(arrowOf(n));", + " harness.assert(readCheckpoint() === n, 'Checkpoint ' + n + ' is reached (the last checkpoint is ' + readCheckpoint() + ').');", + "}", + "await crossArrow(arrowOf(1));", + "console.log('laps: ' + startLap + ' -> ' + readLap());", + "harness.assert(readLap() === startLap + 1, 'Crossing the finish line after all the checkpoints counts a lap (' + startLap + ' -> ' + readLap() + ').');" + ] + } + ], + "eventsFunctionsExtensions": [ + { + "author": "", + "category": "Camera", + "dimension": "3D", + "extensionNamespace": "", + "fullName": "Third person camera", + "gdevelopVersion": ">=5.5.269", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSI3LDEwIDEsMTMgNywxNiAxMywxMyAiLz4NCjxwb2x5bGluZSBjbGFzcz0ic3QwIiBwb2ludHM9IjEsMTMgMSwyMCA3LDIzIDEzLDIwIDEzLDEzICIvPg0KPGxpbmUgY2xhc3M9InN0MCIgeDE9IjciIHkxPSIxNiIgeDI9IjciIHkyPSIyMyIvPg0KPGxpbmUgY2xhc3M9InN0MCIgeDE9IjMxIiB5MT0iMTYiIHgyPSIyMSIgeTI9IjgiLz4NCjxsaW5lIGNsYXNzPSJzdDAiIHgxPSIyMSIgeTE9IjI0IiB4Mj0iMzEiIHkyPSIxNiIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTIyLjcsMjIuNkMyMCwyMS43LDE4LDE5LjEsMTgsMTZjMC0zLjEsMi01LjcsNC43LTYuNiIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTE5LjgsMTEuM2MxLjQsMS4xLDIuMiwyLjgsMi4yLDQuN2MwLDEuOS0wLjksMy42LTIuMiw0LjciLz4NCjwvc3ZnPg0K", + "name": "ThirdPersonCamera", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/Line Hero Pack/Master/SVG/Virtual Reality/94e95d2c318e1f3dc7151a351024e13c574e1e44669c6696aa107d60230073f6_Virtual Reality_3d_vision_eye_vr.svg", + "shortDescription": "Third-person camera orbiting an object at configurable distance, elevation, and rotation.", + "version": "1.9.0", + "description": [ + "Move the camera to look at an object from a given distance with a rotation and an elevation angles.", + "", + "It can be useful for:", + "- 3D platformer games ([open the project online](https://editor.gdevelop.io/?project=example://3d-platformer))", + "- 3D racing games ([open the project online](https://editor.gdevelop.io/?project=example://3d-car-coin-hunt))", + "- Orbit camera controls ([open the project online](https://editor.gdevelop.io/?project=example://3d-tile-based-city-builder))", + "- Isometric-like point of view ([open the project online](https://editor.gdevelop.io/?project=example://3d-road-crosser))" + ], + "origin": { + "identifier": "ThirdPersonCamera", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d", + "camera" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "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": [ + "//@ts-ignore", + "if (gdjs.__thirdPersonCameraExtension) {", + " //@ts-ignore", + " return;", + "}", + "", + "/**", + " * It can be built the follow:", + " * ", + " * `objectDefaultRotation.setFromAxisAngle(new THREE.Vector3(0, 0, 1), -Math.PI/2);`", + " * ", + " * `objectDefaultRotation.multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), Math.PI/2));`", + " * ", + " */", + "const objectDefaultRotation = new THREE.Quaternion(0.5, -0.5, -0.5, 0.5);", + "const targetQuaternion = new THREE.Quaternion();", + "const yAxis = new THREE.Vector3(0, 1, 0);", + "const zAxis = new THREE.Vector3(0, 0, 1);", + "const quaternion = new THREE.Quaternion();", + "const objectRotation = new THREE.Euler();", + "", + "/**", + " * @param object {gdjs.RuntimeObject}", + " * @param ratio {number}", + " * @param elevationAngleOffset {number}", + " */", + "function slerpCamera(object, ratio, rotationAngleOffset, elevationAngleOffset) {", + " const layer = object.getInstanceContainer().getLayer(object.getLayer());", + " const threeCamera = layer.getRenderer().getThreeCamera();", + " const threeObject = object.get3DRendererObject();", + "", + " objectRotation.copy(threeObject.rotation);", + " objectRotation.z = -objectRotation.z;", + " objectRotation.x = -objectRotation.x;", + " targetQuaternion.setFromEuler(objectRotation);", + " targetQuaternion.multiply(quaternion.setFromAxisAngle(zAxis, -gdjs.toRad(rotationAngleOffset)));", + " targetQuaternion.multiply(quaternion.setFromAxisAngle(yAxis, gdjs.toRad(elevationAngleOffset)));", + " targetQuaternion.multiply(objectDefaultRotation);", + "", + " threeCamera.quaternion.slerp(targetQuaternion, ratio);", + "", + " // The layer angle takes over the 3D camera Z rotation.", + " layer.setCameraRotation(gdjs.toDegrees(-threeCamera.rotation.z));", + "}", + "", + "//@ts-ignore", + "gdjs.__thirdPersonCameraExtension = {", + " slerpCamera,", + "};", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Move the camera to look at a position from a distance.", + "fullName": "Look at a position from a distance (deprecated)", + "functionType": "Action", + "name": "LookFromDistanceAtPosition", + "private": true, + "sentence": "Move the camera of _PARAM6_ to look at _PARAM1_; _PARAM2_ from _PARAM3_ pixels with a rotation of _PARAM4_° and an elevation of _PARAM5_°", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "=", + "PositionX + Distance * cos(ToRad(RotationAngle + 90)) * cos(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "=", + "PositionY + Distance * sin(ToRad(RotationAngle + 90)) * cos(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "Distance * sin(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::TurnCameraTowardPosition" + }, + "parameters": [ + "", + "PositionX", + "PositionY", + "0", + "Layer", + "", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Position on X axis", + "name": "PositionX", + "type": "expression" + }, + { + "description": "Position on Y axis", + "name": "PositionY", + "type": "expression" + }, + { + "description": "Distance", + "name": "Distance", + "type": "expression" + }, + { + "description": "Rotation angle (around Z axis)", + "name": "RotationAngle", + "type": "expression" + }, + { + "description": "Elevation angle (around Y axis)", + "name": "ElevationAngle", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera to look at an object from a distance.", + "fullName": "Look at an object from a distance (deprecated)", + "functionType": "Action", + "name": "LookFromDistanceAtObject", + "private": true, + "sentence": "Move the camera of _PARAM5_ to look at _PARAM1_ from _PARAM2_ pixels with a rotation of _PARAM3_° and an elevation of _PARAM4_°", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition" + }, + "parameters": [ + "", + "Object.CenterX()", + "Object.CenterY()", + "Distance", + "RotationAngle", + "ElevationAngle", + "Layer", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "Distance", + "name": "Distance", + "type": "expression" + }, + { + "description": "Rotation angle (around Z axis)", + "name": "RotationAngle", + "type": "expression" + }, + { + "description": "Elevation angle (around Y axis)", + "name": "ElevationAngle", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera to look at a position from a distance.", + "fullName": "Look at a position from a distance", + "functionType": "Action", + "name": "LookFromDistanceAtPosition3D", + "sentence": "Move the camera of _PARAM7_ to look at _PARAM1_; _PARAM2_; _PARAM3_ from _PARAM4_ pixels with a rotation of _PARAM5_° and an elevation of _PARAM6_°", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "=", + "PositionX + Distance * cos(ToRad(RotationAngle + 90)) * cos(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "=", + "PositionY + Distance * sin(ToRad(RotationAngle + 90)) * cos(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "PositionZ + Distance * sin(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationX" + }, + "parameters": [ + "", + "=", + "90 - ElevationAngle", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationY" + }, + "parameters": [ + "", + "=", + "0", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraAngle" + }, + "parameters": [ + "", + "=", + "RotationAngle", + "Layer", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Position on X axis", + "name": "PositionX", + "type": "expression" + }, + { + "description": "Position on Y axis", + "name": "PositionY", + "type": "expression" + }, + { + "description": "Position on Z axis", + "name": "PositionZ", + "type": "expression" + }, + { + "description": "Distance", + "name": "Distance", + "type": "expression" + }, + { + "description": "Rotation angle (around Z axis)", + "name": "RotationAngle", + "type": "expression" + }, + { + "description": "Elevation angle (around Y axis)", + "name": "ElevationAngle", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera to look at an object from a distance.", + "fullName": "Look at an object from a distance", + "functionType": "Action", + "group": "Layers and cameras", + "name": "LookFromDistanceAtObject3D", + "sentence": "Move the camera of _PARAM6_ to look at _PARAM1_ from _PARAM3_ pixels with a rotation of _PARAM4_° and an elevation of _PARAM5_°", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition3D" + }, + "parameters": [ + "", + "Object.CenterX()", + "Object.CenterY()", + "Object.Object3D::CenterZ()", + "Distance", + "RotationAngle", + "ElevationAngle", + "Layer", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Distance", + "name": "Distance", + "type": "expression" + }, + { + "description": "Rotation angle (around Z axis)", + "name": "RotationAngle", + "type": "expression" + }, + { + "description": "Elevation angle (around Y axis)", + "name": "ElevationAngle", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Expression", + "name": "RotatedX", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Cos", + "=", + "cos(ToRad(RotationAngle))" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Sin", + "=", + "sin(ToRad(RotationAngle))" + ] + }, + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Cos * X - Sin * Y" + ] + } + ], + "variables": [ + { + "folded": true, + "name": "Cos", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "Sin", + "type": "number", + "value": 0 + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Rotation angle", + "name": "RotationAngle", + "supplementaryInformation": "Tank::Tank", + "type": "expression" + }, + { + "description": "", + "name": "X", + "type": "expression" + }, + { + "description": "", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Expression", + "name": "RotatedY", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Cos", + "=", + "cos(ToRad(RotationAngle))" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Sin", + "=", + "sin(ToRad(RotationAngle))" + ] + }, + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Sin * X + Cos * Y" + ] + } + ], + "variables": [ + { + "folded": true, + "name": "Cos", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "Sin", + "type": "number", + "value": 0 + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Rotation angle", + "name": "RotationAngle", + "supplementaryInformation": "Tank::Tank", + "type": "expression" + }, + { + "description": "", + "name": "X", + "type": "expression" + }, + { + "description": "", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "DefineHelperClasses" + }, + { + "functionName": "LookFromDistanceAtPosition" + }, + { + "functionName": "LookFromDistanceAtObject" + }, + { + "functionName": "LookFromDistanceAtPosition3D" + }, + { + "folderName": "Layers and cameras", + "children": [ + { + "functionName": "LookFromDistanceAtObject3D" + } + ] + }, + { + "functionName": "RotatedX" + }, + { + "functionName": "RotatedY" + } + ] + }, + "eventsBasedBehaviors": [ + { + "description": "Smoothly follow an object at a distance.", + "fullName": "Third person camera", + "helpPath": "", + "iconUrl": "", + "name": "ThirdPersonCamera", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Update private properties through setters to check their values and initialize state." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetRotationHalfwayDuration" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "RotationHalfwayDuration", + "" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetElevationHalfwayDuration" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "ElevationHalfwayDuration", + "" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetTranslationZHalfwayDuration" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "TranslationZHalfwayDuration", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "HasJustBeenCreated", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "HasJustBeenCreated", + "False", + "" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraZ", + "=", + "Object.Object3D::CenterZ()" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::JumpToTargetedRotation" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "IsCalledManually", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::DoMoveCameraCloser" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera closer to the object. This action must be called after the object has moved for the frame.", + "fullName": "Move the camera closer", + "functionType": "Action", + "name": "MoveCameraCloser", + "sentence": "Move the camera closer to _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "The camera following is called with an action, the call from doStepPreEvents must be disabled to avoid to do it twice." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BehaviorActivated" + }, + "parameters": [ + "Object", + "Behavior" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsCalledManually", + "False", + "" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::DoMoveCameraCloser" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera closer to the object.", + "fullName": "Do move the camera closer", + "functionType": "Action", + "name": "DoMoveCameraCloser", + "private": true, + "sentence": "Do move the camera closer _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "At each frame, the camera must catchup the target by a given ratio (speed)\ncameraX(t) - targetX = (cameraX(t - 1) - targetX) * (1 - speed)\n\nThe frame rate must not impact on the catch-up speed, we don't want a speed in ratio per frame but a speed ratio per second, like this:\ncameraX(t) - targetX = (cameraX(t - 1s) - targetX) * (1 - speed)\n\nOk, but we still need to process each frame, we can use a exponent for this:\ncameraX(t) - targetX = (cameraX(t - timeDelta) - targetX) * (1 - speed)^timeDelta\ncameraX(t) = targetX + (cameraX(t - timeDelta) - targetX) * exp(timeDelta * ln((1 - speed)))\n\npow is probably more efficient than precalculated log if the speed is changed continuously but this might be rare enough." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "RotationMode", + "=", + "\"ZYX\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::Slerp" + }, + "parameters": [ + "Object", + "Behavior", + "1 - exp(TimeDelta() * RotationLogSpeed)", + "RotationAngleOffset", + "ElevationAngleOffset", + "" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::MoveToObject" + }, + "parameters": [ + "Object", + "Behavior", + "OffsetY", + "OffsetX", + "OffsetZ", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "-", + "Distance * Scene3D::CameraForwardX(Object.Layer())", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "-", + "Distance * Scene3D::CameraForwardY(Object.Layer())", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "-", + "Distance * Scene3D::CameraForwardZ(Object.Layer())", + "Object.Layer()", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "RotationMode", + "!=", + "\"ZYX\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "False", + "" + ] + } + ] + } + ], + "actions": [], + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Z translation", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CameraZ", + "<", + "Object.Behavior::FreeAreaZMin()" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraZ", + "=", + "Object.Behavior::FreeAreaZMin() + (CameraZ - Object.Behavior::FreeAreaZMin()) * exp(TimeDelta() * TranslationZLogSpeed)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CameraZ", + ">", + "Object.Behavior::FreeAreaZMax()" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraZ", + "=", + "Object.Behavior::FreeAreaZMax() + (CameraZ - Object.Behavior::FreeAreaZMax()) * exp(TimeDelta() * TranslationZLogSpeed)" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Z rotation", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedRotationAngle", + "=", + "Object.Angle()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "RotationMode", + "!=", + "\"Z\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsElevatingWithObject", + "True", + "" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedElevationAngle", + "=", + "Object.Object3D::RotationY()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraRotationAngle", + "=", + "TargetedRotationAngle + 90 + RotationAngleOffset + AngleDifference(CameraAngle(Object.Layer()), TargetedRotationAngle + 90 + RotationAngleOffset) * exp(TimeDelta() * RotationLogSpeed)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraElevationAngle", + "=", + "TargetedElevationAngle + ElevationAngleOffset + AngleDifference(90 - Scene3D::CameraRotationX(Object.Layer()), TargetedElevationAngle + ElevationAngleOffset) * exp(TimeDelta() * ElevationLogSpeed)" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition3D" + }, + "parameters": [ + "", + "Object.CenterX() + ThirdPersonCamera::RotatedX(CameraRotationAngle, OffsetX, -OffsetY)", + "Object.CenterY() + ThirdPersonCamera::RotatedY(CameraRotationAngle, OffsetX, -OffsetY)", + "CameraZ + OffsetZ", + "Distance", + "CameraRotationAngle", + "CameraElevationAngle", + "Object.Layer()", + "" + ] + } + ], + "variables": [ + { + "folded": true, + "name": "CameraRotationAngle", + "type": "number", + "value": 0 + }, + { + "name": "CameraElevationAngle", + "type": "number", + "value": 0 + } + ] + } + ], + "parameters": [] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Lerp camera", + "functionType": "Action", + "name": "Slerp", + "private": true, + "sentence": "Lerp the camera rotation to _PARAM0_ with a ratio of _PARAM2_ with rotation offset _PARAM3_° and elevation offset _PARAM4_°", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.RuntimeObject3D} */", + "const object = objects[0];", + "const ratio = eventsFunctionContext.getArgument(\"Ratio\");", + "const rotationAngleOffset = eventsFunctionContext.getArgument(\"NewRotationAngleOffset\");", + "const elevationAngleOffset = eventsFunctionContext.getArgument(\"NewElevationAngleOffset\");", + "", + "gdjs.__thirdPersonCameraExtension.slerpCamera(object, ratio, rotationAngleOffset, elevationAngleOffset);", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + }, + { + "description": "Ratio", + "name": "Ratio", + "type": "expression" + }, + { + "description": "Rotation angle offset", + "name": "NewRotationAngleOffset", + "type": "expression" + }, + { + "description": "Elevation angle offset", + "name": "NewElevationAngleOffset", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "fullName": "Move to object", + "functionType": "Action", + "name": "MoveToObject", + "private": true, + "sentence": "Change the camera position to _PARAM0_ with offset _PARAM2_, _PARAM3_, _PARAM4_ ", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "=", + "Object.CenterX() + LocalOffsetX * Object.Object3D::ForwardX() + LocalOffsetY * Object.Object3D::RightX() + LocalOffsetZ * Object.Object3D::UpX()", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "=", + "Object.CenterY() + LocalOffsetX * Object.Object3D::ForwardY() + LocalOffsetY * Object.Object3D::RightY() + LocalOffsetZ * Object.Object3D::UpY()", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "Object.Object3D::CenterZ() + LocalOffsetX * Object.Object3D::ForwardZ() + LocalOffsetY * Object.Object3D::RightZ() + LocalOffsetZ * Object.Object3D::UpZ()", + "Object.Layer()", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + }, + { + "description": "X offset", + "name": "LocalOffsetX", + "type": "expression" + }, + { + "description": "Y offset", + "name": "LocalOffsetY", + "type": "expression" + }, + { + "description": "Z offset", + "name": "LocalOffsetZ", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Rotate the camera all the way to the targeted angle.", + "fullName": "Rotate the camera all the way", + "functionType": "Action", + "name": "JumpToTargetedRotation", + "sentence": "Rotate the camera all the way to the targeted angle of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedRotationAngle", + "=", + "Object.Angle()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsElevatingWithObject", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedElevationAngle", + "=", + "Object.Object3D::RotationY()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraAngle", + "=", + "TargetedRotationAngle + 90 + RotationAngleOffset" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition3D" + }, + "parameters": [ + "", + "Object.CenterX() + ThirdPersonCamera::RotatedX(CameraAngle, OffsetX, -OffsetY)", + "Object.CenterY() + ThirdPersonCamera::RotatedY(CameraAngle, OffsetX, -OffsetY)", + "CameraZ + OffsetZ", + "Distance", + "CameraAngle", + "Object.Object3D::RotationY() + ElevationAngleOffset", + "", + "" + ] + } + ], + "variables": [ + { + "folded": true, + "name": "CameraAngle", + "type": "number", + "value": 0 + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the camera rotation.", + "fullName": "Camera rotation", + "functionType": "ExpressionAndCondition", + "name": "RotationAngle", + "private": true, + "sentence": "the camera rotation", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "CameraAngle(Object.Layer())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Rotate the camera all the way to the targeted angle.", + "fullName": "Rotate the camera all the way", + "functionType": "ActionWithOperator", + "getterName": "RotationAngle", + "name": "SetCameraRotation", + "sentence": "Rotate the camera all the way to the targeted angle of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition3D" + }, + "parameters": [ + "", + "Object.CenterX() + ThirdPersonCamera::RotatedX(Value, OffsetX, -OffsetY)", + "Object.CenterY() + ThirdPersonCamera::RotatedY(Value, OffsetX, -OffsetY)", + "CameraZ + OffsetZ", + "Distance", + "Value", + "Object.Object3D::RotationY() + ElevationAngleOffset", + "", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the halfway time for rotation of the object.", + "fullName": "Halfway time for rotation", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "RotationHalfwayDuration", + "sentence": "the halfway time for rotation", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "RotationHalfwayDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "RotationHalfwayDuration", + "group": "Third person camera configuration", + "name": "SetRotationHalfwayDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "ln((1 - speed)) = ln(1 / 2) / halfwatTime" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "RotationHalfwayDuration", + "=", + "Value" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "RotationLogSpeed", + "=", + "log(0.5) / Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the halfway time for elevation rotation of the object.", + "fullName": "Halfway time for elevation rotation", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "ElevationHalfwayDuration", + "sentence": "the halfway time for elevation rotation", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ElevationHalfwayDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ElevationHalfwayDuration", + "group": "Third person camera configuration", + "name": "SetElevationHalfwayDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "ln((1 - speed)) = ln(1 / 2) / halfwatTime" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ElevationHalfwayDuration", + "=", + "Value" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ElevationLogSpeed", + "=", + "log(0.5) / Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the halfway time on Z axis of the object.", + "fullName": "Halfway time on Z axis", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "TranslationZHalfwayDuration", + "sentence": "the halfway time on Z axis", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "TranslationZHalfwayDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "TranslationZHalfwayDuration", + "group": "Third person camera configuration", + "name": "SetTranslationZHalfwayDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "ln((1 - speed)) = ln(1 / 2) / halfwatTime" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TranslationZHalfwayDuration", + "=", + "Value" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TranslationZLogSpeed", + "=", + "log(0.5) / Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Return follow free area bottom border Z.", + "fullName": "Free area Z min", + "functionType": "Expression", + "group": "Private", + "name": "FreeAreaZMin", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Object3D::CenterZ() + OffsetZ - FollowFreeAreaZMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Return follow free area top border Z.", + "fullName": "Free area Z max", + "functionType": "Expression", + "group": "Private", + "name": "FreeAreaZMax", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Object3D::CenterZ() + OffsetZ + FollowFreeAreaZMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the follow free area top border on Z axis of the object.", + "fullName": "Follow free area top border on Z axis", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "FollowFreeAreaZMax", + "sentence": "the follow free area top border on Z axis", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FollowFreeAreaZMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FollowFreeAreaZMax", + "group": "Third person camera configuration", + "name": "SetFollowFreeAreaZMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FollowFreeAreaZMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the follow free area bottom border on Z axis of the object.", + "fullName": "Follow free area bottom border on Z axis", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "FollowFreeAreaZMin", + "sentence": "the follow free area bottom border on Z axis", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FollowFreeAreaZMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FollowFreeAreaZMin", + "group": "Third person camera configuration", + "name": "SetFollowFreeAreaZMin", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FollowFreeAreaZMin", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the camera distance of the object.", + "fullName": "Camera distance", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "Distance", + "sentence": "the camera distance", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Distance" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Distance", + "group": "Third person camera configuration", + "name": "SetDistance", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Distance", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the lateral distance offset of the object.", + "fullName": "Lateral distance offset", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "OffsetX", + "sentence": "the lateral distance offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "OffsetX" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "OffsetX", + "group": "Third person camera configuration", + "name": "SetOffsetX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OffsetX", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the ahead distance offset of the object.", + "fullName": "Ahead distance offset", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "OffsetY", + "sentence": "the ahead distance offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "OffsetY" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "OffsetY", + "group": "Third person camera configuration", + "name": "SetOffsetY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OffsetY", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the z offset of the object.", + "fullName": "Z offset", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "OffsetZ", + "sentence": "the z offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "OffsetZ" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "OffsetZ", + "group": "Third person camera configuration", + "name": "SetOffsetZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OffsetZ", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the rotation angle offset of the object.", + "fullName": "Rotation angle offset", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "RotationAngleOffset", + "sentence": "the rotation angle offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "RotationAngleOffset" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "RotationAngleOffset", + "group": "Third person camera configuration", + "name": "SetRotationAngleOffset", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "RotationAngleOffset", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the elevation angle offset of the object.", + "fullName": "Elevation angle offset", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "ElevationAngleOffset", + "sentence": "the elevation angle offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ElevationAngleOffset" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ElevationAngleOffset", + "group": "Third person camera configuration", + "name": "SetElevationAngleOffset", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ElevationAngleOffset", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the targeted camera rotation angle of the object. When this angle is set, the camera follow this value instead of the object angle.", + "fullName": "Targeted rotation angle", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "TargetedRotationAngle", + "sentence": "the targeted camera rotation angle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "TargetedRotationAngle" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "TargetedRotationAngle", + "group": "Third person camera configuration", + "name": "SetTargetedRotationAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedRotationAngle", + "=", + "Value" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "False", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "MoveCameraCloser" + }, + { + "functionName": "DoMoveCameraCloser" + }, + { + "functionName": "Slerp" + }, + { + "functionName": "MoveToObject" + }, + { + "functionName": "JumpToTargetedRotation" + }, + { + "functionName": "RotationAngle" + }, + { + "functionName": "SetCameraRotation" + }, + { + "folderName": "Third person camera configuration", + "children": [ + { + "functionName": "RotationHalfwayDuration" + }, + { + "functionName": "SetRotationHalfwayDuration" + }, + { + "functionName": "ElevationHalfwayDuration" + }, + { + "functionName": "SetElevationHalfwayDuration" + }, + { + "functionName": "TranslationZHalfwayDuration" + }, + { + "functionName": "SetTranslationZHalfwayDuration" + }, + { + "functionName": "FollowFreeAreaZMax" + }, + { + "functionName": "SetFollowFreeAreaZMax" + }, + { + "functionName": "FollowFreeAreaZMin" + }, + { + "functionName": "SetFollowFreeAreaZMin" + }, + { + "functionName": "Distance" + }, + { + "functionName": "SetDistance" + }, + { + "functionName": "OffsetX" + }, + { + "functionName": "SetOffsetX" + }, + { + "functionName": "OffsetY" + }, + { + "functionName": "SetOffsetY" + }, + { + "functionName": "OffsetZ" + }, + { + "functionName": "SetOffsetZ" + }, + { + "functionName": "RotationAngleOffset" + }, + { + "functionName": "SetRotationAngleOffset" + }, + { + "functionName": "ElevationAngleOffset" + }, + { + "functionName": "SetElevationAngleOffset" + }, + { + "functionName": "TargetedRotationAngle" + }, + { + "functionName": "SetTargetedRotationAngle" + } + ] + }, + { + "folderName": "Private", + "children": [ + { + "functionName": "FreeAreaZMin" + }, + { + "functionName": "FreeAreaZMax" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D capability", + "extraInformation": [ + "Scene3D::Base3DBehavior" + ], + "choices": [], + "name": "Object3D" + }, + { + "value": "0.125", + "type": "Number", + "unit": "Second", + "label": "Halfway time for rotation", + "group": "Catch-up speed", + "quickCustomizationVisibility": "hidden", + "name": "RotationHalfwayDuration" + }, + { + "value": "0", + "type": "Number", + "unit": "Second", + "label": "Halfway time for elevation rotation", + "description": "Only used by Z and ZY rotation modes", + "group": "Catch-up speed", + "quickCustomizationVisibility": "hidden", + "name": "ElevationHalfwayDuration" + }, + { + "value": "0.125", + "type": "Number", + "unit": "Second", + "label": "Halfway time on Z axis", + "description": "Only used by Z and ZY rotation modes", + "group": "Catch-up speed", + "quickCustomizationVisibility": "hidden", + "name": "TranslationZHalfwayDuration" + }, + { + "value": "500", + "type": "Number", + "unit": "Pixel", + "label": "Camera distance", + "group": "Position", + "name": "Distance" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Lateral distance offset", + "group": "Position", + "name": "OffsetX" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Ahead distance offset", + "group": "Position", + "name": "OffsetY" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Z offset", + "group": "Position", + "name": "OffsetZ" + }, + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "Rotation angle offset", + "group": "Position", + "advanced": true, + "name": "RotationAngleOffset" + }, + { + "value": "20", + "type": "Number", + "unit": "DegreeAngle", + "label": "Elevation angle offset", + "group": "Position", + "name": "ElevationAngleOffset" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Follow free area top border on Z axis", + "group": "Position", + "advanced": true, + "quickCustomizationVisibility": "hidden", + "name": "FollowFreeAreaZMax" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Follow free area bottom border on Z axis", + "group": "Position", + "advanced": true, + "quickCustomizationVisibility": "hidden", + "name": "FollowFreeAreaZMin" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "RotationLogSpeed" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "ElevationLogSpeed" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "TranslationZLogSpeed" + }, + { + "value": "", + "type": "Boolean", + "label": "", + "hidden": true, + "name": "IsCalledManually" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "", + "hidden": true, + "name": "CameraZ" + }, + { + "value": "true", + "type": "Boolean", + "label": "", + "hidden": true, + "name": "HasJustBeenCreated" + }, + { + "value": "true", + "type": "Boolean", + "label": "Automatically rotate the camera with the object", + "quickCustomizationVisibility": "hidden", + "name": "IsRotatingWithObject" + }, + { + "value": "false", + "type": "Boolean", + "label": "Automatically rotate the camera with the object (elevation)", + "description": "Best left unchecked, use the rotation mode instead", + "deprecated": true, + "quickCustomizationVisibility": "hidden", + "name": "IsElevatingWithObject" + }, + { + "value": "Z", + "type": "Choice", + "label": "Rotation mode", + "choices": [ + { + "label": "Horizontal rotation (good for characters)", + "value": "Z" + }, + { + "label": "Vertical and horizontal rotations (good for cars)", + "value": "ZY" + }, + { + "label": "All rotations (good for spaceships)", + "value": "ZYX" + } + ], + "name": "RotationMode" + }, + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "Targeted camera rotation angle", + "hidden": true, + "name": "TargetedRotationAngle" + }, + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "Targeted camera rotation angle", + "hidden": true, + "name": "TargetedElevationAngle" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Forward X", + "hidden": true, + "name": "ForwardX" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Forward Y", + "hidden": true, + "name": "ForwardY" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Forward Z", + "hidden": true, + "name": "ForwardZ" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Object3D" + }, + { + "folderName": "Catch-up speed", + "children": [ + { + "propertyName": "RotationHalfwayDuration" + }, + { + "propertyName": "ElevationHalfwayDuration" + }, + { + "propertyName": "TranslationZHalfwayDuration" + } + ] + }, + { + "folderName": "Position", + "children": [ + { + "propertyName": "Distance" + }, + { + "propertyName": "OffsetX" + }, + { + "propertyName": "OffsetY" + }, + { + "propertyName": "OffsetZ" + }, + { + "propertyName": "RotationAngleOffset" + }, + { + "propertyName": "ElevationAngleOffset" + }, + { + "propertyName": "FollowFreeAreaZMax" + }, + { + "propertyName": "FollowFreeAreaZMin" + } + ] + }, + { + "propertyName": "RotationLogSpeed" + }, + { + "propertyName": "ElevationLogSpeed" + }, + { + "propertyName": "TranslationZLogSpeed" + }, + { + "propertyName": "IsCalledManually" + }, + { + "propertyName": "CameraZ" + }, + { + "propertyName": "HasJustBeenCreated" + }, + { + "propertyName": "IsRotatingWithObject" + }, + { + "propertyName": "IsElevatingWithObject" + }, + { + "propertyName": "RotationMode" + }, + { + "propertyName": "TargetedRotationAngle" + }, + { + "propertyName": "TargetedElevationAngle" + }, + { + "propertyName": "ForwardX" + }, + { + "propertyName": "ForwardY" + }, + { + "propertyName": "ForwardZ" + } + ] + } + } + ], + "eventsBasedObjects": [] + }, + { + "author": "", + "category": "Input", + "dimension": "", + "extensionNamespace": "", + "fullName": "Multitouch joystick and buttons (sprite)", + "gdevelopVersion": ">=5.5.222", + "helpPath": "/objects/multitouch-joystick", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPGNpcmNsZSBjbGFzcz0ic3QwIiBjeD0iMTYiIGN5PSIxNiIgcj0iMTMiLz4NCjxwb2x5bGluZSBjbGFzcz0ic3QwIiBwb2ludHM9IjI4LjQsMTIgMjAsMTIgMjAsMy42ICIvPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMjAsMjguNCAyMCwyMCAyOC40LDIwICIvPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMy42LDIwIDEyLDIwIDEyLDI4LjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSIxMiwzLjYgMTIsMTIgMy42LDEyICIvPg0KPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSIxNiw2IDE2LjcsNyAxNS4zLDcgIi8+DQo8cG9seWdvbiBjbGFzcz0ic3QwIiBwb2ludHM9IjE2LDI2IDE1LjMsMjUgMTYuNywyNSAiLz4NCjxwb2x5Z29uIGNsYXNzPSJzdDAiIHBvaW50cz0iNiwxNiA3LDE1LjMgNywxNi43ICIvPg0KPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSIyNiwxNiAyNSwxNi43IDI1LDE1LjMgIi8+DQo8L3N2Zz4NCg==", + "name": "SpriteMultitouchJoystick", + "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Videogames/Videogames_controller_joystick_arrows_direction.svg", + "shortDescription": "On-screen multitouch joystick and buttons.", + "version": "1.9.2", + "description": [ + "Multitouch joysticks are objects showing a joystick on the screen, useful for mobile. They work like a physical gamepad:", + "- 4 or 8 directions", + "- Analogus pads", + "- Player selection", + "- Automatic \"mapper\" behaviors for 2D and 3D movement behaviors (platformer characters, top-down movement, 3D character etc...). Add one of these to the object with the movement behavior (i.e: the player most of the time) and the behavior will then be controlled automatically by the virtual joystick (it works by reading the multitouch joystick state and simulating controls). No need for additional events to make it work once the behaviors are set up.", + "", + "There are ready-to-use joysticks in the asset-store [multitouch joysticks pack](https://editor.gdevelop.io/?initial-dialog=asset-store&asset-pack=multitouch-joysticks-multitouch-joysticks)." + ], + "origin": { + "identifier": "SpriteMultitouchJoystick", + "name": "gdevelop-extension-store" + }, + "tags": [ + "multitouch", + "joystick", + "thumbstick", + "controller", + "touchscreen", + "twin stick", + "shooter", + "virtual", + "platformer", + "platform", + "top-down" + ], + "authorIds": [ + "gqDaZjCfevOOxBYkK6zlhtZnXCg1", + "1OgYzWp5UeVPbiWGJwI6vqfgZLC3", + "v0YRpdAnIucZFgiRCCecqVnGKno2", + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [ + { + "name": "Controllers", + "type": "array", + "children": [ + { + "type": "structure", + "children": [ + { + "name": "Buttons", + "type": "array", + "children": [ + { + "type": "structure", + "children": [ + { + "name": "State", + "type": "string", + "value": "Idle" + } + ] + } + ] + }, + { + "name": "Joystick", + "type": "structure", + "children": [] + } + ] + } + ] + } + ], + "eventsFunctions": [ + { + "description": "Check if the events are running for the editor.", + "fullName": "Editor is running", + "functionType": "Condition", + "name": "IsInGameEdition", + "private": true, + "sentence": "Events are running for the editor", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const game = runtimeScene.getGame();\r", + "eventsFunctionContext.returnValue = game.isInGameEdition && game.isInGameEdition();" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "fullName": "Accelerated speed", + "functionType": "Expression", + "name": "AcceleratedSpeed", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "CurrentSpeed" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TargetedSpeed", + "<", + "0" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Reduce the speed to match the stick force." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + "<", + "TargetedSpeed" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "min(TargetedSpeed, CurrentSpeed + Acceleration * TimeDelta())" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + ">", + "TargetedSpeed" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + "<", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "-", + "Acceleration * TimeDelta()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Turn back at least as fast as it would stop." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + ">=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "max(TargetedSpeed, CurrentSpeed - max(Acceleration , Deceleration) * TimeDelta())" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TargetedSpeed", + ">", + "0" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Reduce the speed to match the stick force." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + ">", + "TargetedSpeed" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "max(TargetedSpeed, CurrentSpeed - Acceleration * TimeDelta())" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + "<", + "TargetedSpeed" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "+", + "Acceleration * TimeDelta()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Turn back at least as fast as it would stop." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + "<=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "min(TargetedSpeed, CurrentSpeed + max(Acceleration , Deceleration) * TimeDelta())" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TargetedSpeed", + "=", + "0" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + "<", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "min(0, CurrentSpeed + Acceleration * TimeDelta())" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "max(0, CurrentSpeed - Acceleration * TimeDelta())" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "clamp(AcceleratedSpeed, -SpeedMax, SpeedMax)" + ] + } + ] + } + ], + "variables": [ + { + "name": "AcceleratedSpeed", + "type": "number", + "value": 0 + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Current speed", + "name": "CurrentSpeed", + "type": "expression" + }, + { + "description": "Targeted speed", + "name": "TargetedSpeed", + "type": "expression" + }, + { + "description": "Max speed", + "name": "SpeedMax", + "type": "expression" + }, + { + "description": "Acceleration", + "name": "Acceleration", + "type": "expression" + }, + { + "description": "Deceleration", + "name": "Deceleration", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a button was just pressed on a multitouch controller.", + "fullName": "Multitouch controller button just pressed", + "functionType": "Condition", + "name": "IsButtonJustPressed", + "sentence": "Button _PARAM2_ of multitouch controller _PARAM1_ was just pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Buttons[Button].State", + "=", + "\"JustPressed\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Button name", + "name": "Button", + "supplementaryInformation": "[\"A\",\"CROSS\",\"B\",\"CIRCLE\",\"X\",\"SQUARE\",\"Y\",\"TRIANGLE\",\"LB\",\"L1\",\"RB\",\"R1\",\"LT\",\"L2\",\"RT\",\"R2\",\"UP\",\"DOWN\",\"LEFT\",\"RIGHT\",\"BACK\",\"SHARE\",\"START\",\"OPTIONS\",\"CLICK_STICK_LEFT\",\"CLICK_STICK_RIGHT\",\"PS_BUTTON\",\"CLICK_TOUCHPAD\"]", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a button is pressed on a multitouch controller.", + "fullName": "Multitouch controller button pressed", + "functionType": "Condition", + "name": "IsButtonPressed", + "sentence": "Button _PARAM2_ of multitouch controller _PARAM1_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Buttons[Button].State", + "=", + "\"Pressed\"" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Buttons[Button].State", + "=", + "\"JustPressed\"" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Button name", + "name": "Button", + "supplementaryInformation": "[\"A\",\"CROSS\",\"B\",\"CIRCLE\",\"X\",\"SQUARE\",\"Y\",\"TRIANGLE\",\"LB\",\"L1\",\"RB\",\"R1\",\"LT\",\"L2\",\"RT\",\"R2\",\"UP\",\"DOWN\",\"LEFT\",\"RIGHT\",\"BACK\",\"SHARE\",\"START\",\"OPTIONS\",\"CLICK_STICK_LEFT\",\"CLICK_STICK_RIGHT\",\"PS_BUTTON\",\"CLICK_TOUCHPAD\"]", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a button is released on a multitouch controller.", + "fullName": "Multitouch controller button released", + "functionType": "Condition", + "name": "IsButtonReleased", + "sentence": "Button _PARAM2_ of multitouch controller _PARAM1_ is released", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Buttons[Button].State", + "=", + "\"Released\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Button name", + "name": "Button", + "supplementaryInformation": "[\"A\",\"CROSS\",\"B\",\"CIRCLE\",\"X\",\"SQUARE\",\"Y\",\"TRIANGLE\",\"LB\",\"L1\",\"RB\",\"R1\",\"LT\",\"L2\",\"RT\",\"R2\",\"UP\",\"DOWN\",\"LEFT\",\"RIGHT\",\"BACK\",\"SHARE\",\"START\",\"OPTIONS\",\"CLICK_STICK_LEFT\",\"CLICK_STICK_RIGHT\",\"PS_BUTTON\",\"CLICK_TOUCHPAD\"]", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Change a button state for a multitouch controller.", + "fullName": "Button state", + "functionType": "Action", + "name": "SetButtonState", + "private": true, + "sentence": "Mark _PARAM2_ button as _PARAM3_ for multitouch controller _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Buttons[Button].State", + "=", + "ButtonState" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Button name", + "name": "Button", + "type": "string" + }, + { + "description": "Button state", + "name": "ButtonState", + "supplementaryInformation": "[\"Idle\",\"Pressed\",\"JustPressed\",\"Released\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Change the dead zone radius of a joystick. The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved).", + "fullName": "Dead zone radius", + "functionType": "Action", + "name": "SetDeadZone", + "private": true, + "sentence": "Change the dead zone of multitouch joystick _PARAM2_ of multitouch controller _PARAM1_ to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].DeadZone", + "=", + "DeadZoneRadius" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "type": "string" + }, + { + "description": "Dead zone radius", + "name": "DeadZoneRadius", + "supplementaryInformation": "[]", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the dead zone radius of a joystick. The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved).", + "fullName": "Dead zone radius", + "functionType": "Expression", + "name": "DeadZone", + "private": true, + "sentence": "Change multitouch joystick _PARAM2_ of multitouch controller _PARAM1_ dead zone to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].DeadZone" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "the direction index (right = 0, bottom = 1, left = 2, top = 3) for an angle (in degrees).", + "fullName": "Angle to 4-way index", + "functionType": "ExpressionAndCondition", + "name": "AngleTo4Way", + "private": true, + "sentence": "The angle _PARAM1_ 4-way index", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "mod(round(Angle * 4 / 360), 4)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Angle", + "name": "Angle", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the direction index (right = 0, bottom-right = 1... top-right = 7) for an angle (in degrees).", + "fullName": "Angle to 8-way index", + "functionType": "ExpressionAndCondition", + "name": "AngleTo8Way", + "private": true, + "sentence": "The angle _PARAM1_ 8-way index", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "mod(round(Angle * 8 / 360), 8)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Angle", + "name": "Angle", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Check if angle is in a given direction.", + "fullName": "Angle 4-way direction", + "functionType": "Condition", + "name": "IsAngleIn4WayDirection", + "private": true, + "sentence": "The angle _PARAM1_ is the 4-way direction _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Right\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo4Way" + }, + "parameters": [ + "", + "=", + "0", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Down\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo4Way" + }, + "parameters": [ + "", + "=", + "1", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Left\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo4Way" + }, + "parameters": [ + "", + "=", + "2", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Up\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo4Way" + }, + "parameters": [ + "", + "=", + "3", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Angle", + "name": "Angle", + "type": "expression" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if angle is in a given direction.", + "fullName": "Angle 8-way direction", + "functionType": "Condition", + "name": "IsAngleIn8WayDirection", + "private": true, + "sentence": "The angle _PARAM1_ is the 8-way direction _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Right\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "0", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"DownRight\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "1", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Down\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "2", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"DownLeft\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "3", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Left\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "4", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"UpLeft\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "5", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Up\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "6", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"UpRight\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "7", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Angle", + "name": "Angle", + "type": "expression" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\",\"UpLeft\",\"UpRight\",\"DownLeft\",\"DownRight\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (4-way)", + "functionType": "Condition", + "name": "IsDirectionPushed4Way", + "sentence": "Joystick _PARAM2_ of multitouch controller _PARAM1_ is pushed in direction _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Make sure the joystick has moved from center" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::JoystickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "JoystickIdentifier", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::IsAngleIn4WayDirection" + }, + "parameters": [ + "", + "SpriteMultitouchJoystick::JoystickAngle(ControllerIdentifier, JoystickIdentifier)", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (8-way)", + "functionType": "Condition", + "name": "IsDirectionPushed8Way", + "sentence": "Joystick _PARAM2_ of multitouch controller _PARAM1_ is pushed in direction _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Make sure the joystick has moved from center" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::JoystickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "JoystickIdentifier", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::IsAngleIn8WayDirection" + }, + "parameters": [ + "", + "SpriteMultitouchJoystick::JoystickAngle(ControllerIdentifier, JoystickIdentifier)", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\",\"UpLeft\",\"UpRight\",\"DownLeft\",\"DownRight\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "the percentage the thumb has been pulled away from the joystick center (Range: 0 to 1).", + "fullName": "Joystick force (deprecated)", + "functionType": "ExpressionAndCondition", + "name": "JoystickForce", + "private": true, + "sentence": "Joystick _PARAM2_ of multitouch controller _PARAM1_ force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + }, + { + "description": "", + "name": "Coucou", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the force of multitouch contoller stick (from 0 to 1).", + "fullName": "Stick force", + "functionType": "ExpressionAndCondition", + "name": "StickForce", + "sentence": "multitouch controller _PARAM1_ _PARAM2_ stick force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "max(0, Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].Force - SpriteMultitouchJoystick::DeadZone(ControllerIdentifier, JoystickIdentifier)) / (1 - SpriteMultitouchJoystick::DeadZone(ControllerIdentifier, JoystickIdentifier))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Stick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Change the percentage the thumb has been pulled away from the joystick center (Range: 0 to 1).", + "fullName": "Joystick force", + "functionType": "Action", + "name": "SetJoystickForce", + "private": true, + "sentence": "Change the force of the joystick _PARAM2_ of multitouch controller _PARAM1_ to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].Force", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "type": "string" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle the joystick is pointing towards (Range: -180 to 180).", + "fullName": "Joystick angle (deprecated)", + "functionType": "Expression", + "name": "JoystickAngle", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle the multitouch controller stick is pointing towards (Range: -180 to 180).", + "fullName": "Stick angle", + "functionType": "Expression", + "name": "StickAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].Angle" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Change the angle the joystick is pointing towards (Range: -180 to 180).", + "fullName": "Joystick angle", + "functionType": "Action", + "name": "SetJoystickAngle", + "private": true, + "sentence": "Change the angle of the joystick _PARAM2_ of multitouch controller _PARAM1_ to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].Angle", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "type": "string" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the multitouch contoller stick force on X axis (from -1 at the left to 1 at the right).", + "fullName": "Stick X force", + "functionType": "Expression", + "name": "StickForceX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "XFromAngleAndDistance(SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier), SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Return the multitouch contoller stick force on Y axis (from -1 at the top to 1 at the bottom).", + "fullName": "Stick Y force", + "functionType": "Expression", + "name": "StickForceY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "YFromAngleAndDistance(SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier), SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a new touch has started on the right or left side of the screen.", + "fullName": "New touch on a screen side", + "functionType": "Condition", + "group": "Multitouch Joystick", + "name": "HasTouchStartedOnScreenSide", + "sentence": "A new touch has started on the _PARAM2_ side of the screen on _PARAM1_'s layer", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Side", + "=", + "\"Left\"" + ] + }, + { + "type": { + "value": "TouchX" + }, + "parameters": [ + "", + "StartedTouchOrMouseId(0)", + "<", + "CameraCenterX(Object.Layer())", + "Object.Layer()", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Side", + "=", + "\"Right\"" + ] + }, + { + "type": { + "value": "TouchX" + }, + "parameters": [ + "", + "StartedTouchOrMouseId(0)", + ">=", + "CameraCenterX(Object.Layer())", + "Object.Layer()", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch joystick", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "objectList" + }, + { + "description": "Screen side", + "name": "Side", + "supplementaryInformation": "[\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "IsInGameEdition" + }, + { + "functionName": "AcceleratedSpeed" + }, + { + "functionName": "IsButtonJustPressed" + }, + { + "functionName": "IsButtonPressed" + }, + { + "functionName": "IsButtonReleased" + }, + { + "functionName": "SetButtonState" + }, + { + "functionName": "SetDeadZone" + }, + { + "functionName": "DeadZone" + }, + { + "functionName": "AngleTo4Way" + }, + { + "functionName": "AngleTo8Way" + }, + { + "functionName": "IsAngleIn4WayDirection" + }, + { + "functionName": "IsAngleIn8WayDirection" + }, + { + "functionName": "IsDirectionPushed4Way" + }, + { + "functionName": "IsDirectionPushed8Way" + }, + { + "functionName": "JoystickForce" + }, + { + "functionName": "StickForce" + }, + { + "functionName": "SetJoystickForce" + }, + { + "functionName": "JoystickAngle" + }, + { + "functionName": "StickAngle" + }, + { + "functionName": "SetJoystickAngle" + }, + { + "functionName": "StickForceX" + }, + { + "functionName": "StickForceY" + }, + { + "folderName": "Multitouch Joystick", + "children": [ + { + "functionName": "HasTouchStartedOnScreenSide" + } + ] + } + ] + }, + "eventsBasedBehaviors": [ + { + "description": "Joystick that can be controlled by interacting with a touchscreen.", + "fullName": "Multitouch Joystick", + "helpPath": "", + "iconUrl": "", + "name": "MultitouchJoystick", + "objectType": "", + "previewIconUrl": "", + "private": true, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::SetDeadZone" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "DeadZoneRadius", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::Reset" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDeActivate", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::Reset" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::IsInGameEdition" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasGameJustResumed" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::Reset" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Manage touches", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchIndex", + "=", + "0" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "StartedTouchOrMouseCount()", + "conditions": [], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "0" + ] + }, + { + "type": { + "value": "CollisionPoint" + }, + "parameters": [ + "Object", + "TouchX(StartedTouchOrMouseId(TouchIndex), Object.Layer(), 0)", + "TouchY(StartedTouchOrMouseId(TouchIndex), Object.Layer(), 0)" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "StartedTouchOrMouseId(TouchIndex)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchIndex", + "+", + "1" + ] + } + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Move thumb back to center when not being pressed (acts like a spring on a real controller)" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasTouchEnded" + }, + "parameters": [ + "", + "TouchId" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::Reset" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Update joystick position", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetJoystickAngle" + }, + "parameters": [ + "Object", + "Behavior", + "AngleBetweenPositions(Object.CenterX(), Object.CenterY(), TouchX(TouchId, Object.Layer(), 0), TouchY(TouchId, Object.Layer(), 0))", + "AngleBetweenPositions(Object.CenterX(), Object.CenterY(), TouchX(TouchId, Object.Layer(), 0), TouchY(TouchId, Object.Layer(), 0))" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetJoystickForce" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "clamp(2 * DistanceBetweenPositions(Object.CenterX(), Object.CenterY(), TouchX(TouchId, Object.Layer(), 0), TouchY(TouchId, Object.Layer(), 0)) / Object.Width(), 0, 1)", + "" + ] + } + ] + } + ], + "parameters": [] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the joystick force (from 0 to 1).", + "fullName": "Joystick force", + "functionType": "ExpressionAndCondition", + "name": "JoystickForce", + "sentence": "the joystick force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "max(0, JoystickForce - DeadZoneRadius) / (1 - DeadZoneRadius)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "JoystickForce", + "name": "SetJoystickForce", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "JoystickForce", + "=", + "Value" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SetJoystickForce" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "JoystickForce", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + }, + { + "description": "", + "name": "Parameter", + "type": "objectList" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle the joystick is pointing towards (Range: -180 to 180).", + "fullName": "Joystick angle", + "functionType": "Expression", + "name": "JoystickAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "JoystickAngle" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Change the angle the joystick is pointing towards (Range: -180 to 180).", + "fullName": "Joystick angle", + "functionType": "Action", + "name": "SetJoystickAngle", + "private": true, + "sentence": "Change the joystick angle of _PARAM0_ to _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "JoystickAngle", + "=", + "Value" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SetJoystickAngle" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "JoystickAngle", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + }, + { + "description": "Angle", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the stick force on X axis (from -1 at the left to 1 at the right).", + "fullName": "Stick X force", + "functionType": "Expression", + "name": "StickForceX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Behavior::JoystickForce() * cos(ToRad(Object.Behavior::JoystickAngle()))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Return the stick force on Y axis (from -1 at the top to 1 at the bottom).", + "fullName": "Stick Y force", + "functionType": "Expression", + "name": "StickForceY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Behavior::JoystickForce() * sin(ToRad(Object.Behavior::JoystickAngle()))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (4-way movement)", + "functionType": "Condition", + "name": "IsDirectionPushed4Way", + "sentence": "_PARAM0_ is pushed in direction _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::JoystickForce" + }, + "parameters": [ + "Object", + "Behavior", + ">", + "0", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::IsAngleIn4WayDirection" + }, + "parameters": [ + "", + "JoystickAngle", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (8-way movement)", + "functionType": "Condition", + "name": "IsDirectionPushed8Way", + "sentence": "_PARAM0_ is pushed in direction _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::JoystickForce" + }, + "parameters": [ + "Object", + "Behavior", + ">", + "0", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::IsAngleIn8WayDirection" + }, + "parameters": [ + "", + "JoystickAngle", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\",\"UpLeft\",\"UpRight\",\"DownLeft\",\"DownRight\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a joystick is pressed.", + "fullName": "Joystick pressed", + "functionType": "Condition", + "name": "IsPressed", + "sentence": "Joystick _PARAM0_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Reset the joystick values (except for angle, which stays the same)", + "fullName": "Reset", + "functionType": "Action", + "name": "Reset", + "private": true, + "sentence": "Reset the joystick of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetJoystickForce" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "0", + "" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "0" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the multitouch controller identifier.", + "fullName": "Multitouch controller identifier", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "ControllerIdentifier", + "sentence": "the multitouch controller identifier", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ControllerIdentifier" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ControllerIdentifier", + "group": "Multitouch Joystick configuration", + "name": "SetControllerIdentifier", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ControllerIdentifier", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the joystick name.", + "fullName": "Joystick name", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "JoystickIdentifier", + "sentence": "the joystick name", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "JoystickIdentifier" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "JoystickIdentifier", + "group": "Multitouch Joystick configuration", + "name": "SetJoystickIdentifier", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "JoystickIdentifier", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the dead zone radius (range: 0 to 1) of the joystick. The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved).", + "fullName": "Dead zone radius", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "DeadZoneRadius", + "sentence": "the dead zone radius", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "DeadZoneRadius" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "DeadZoneRadius", + "group": "Multitouch Joystick configuration", + "name": "SetDeadZoneRadius", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DeadZoneRadius", + "=", + "Value" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SetDeadZone" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "Value", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Force the joystick into the pressing state.", + "fullName": "Force start pressing", + "functionType": "Action", + "name": "ForceStartPressing", + "sentence": "Force start pressing _PARAM0_ with touch identifier: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + }, + { + "description": "Touch identifier", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "onDeActivate" + }, + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "JoystickForce" + }, + { + "functionName": "SetJoystickForce" + }, + { + "functionName": "JoystickAngle" + }, + { + "functionName": "SetJoystickAngle" + }, + { + "functionName": "StickForceX" + }, + { + "functionName": "StickForceY" + }, + { + "functionName": "IsDirectionPushed4Way" + }, + { + "functionName": "IsDirectionPushed8Way" + }, + { + "functionName": "IsPressed" + }, + { + "functionName": "Reset" + }, + { + "folderName": "Multitouch Joystick configuration", + "children": [ + { + "functionName": "ControllerIdentifier" + }, + { + "functionName": "SetControllerIdentifier" + }, + { + "functionName": "JoystickIdentifier" + }, + { + "functionName": "SetJoystickIdentifier" + }, + { + "functionName": "DeadZoneRadius" + }, + { + "functionName": "SetDeadZoneRadius" + } + ] + }, + { + "functionName": "ForceStartPressing" + } + ] + }, + "propertyDescriptors": [ + { + "value": "1", + "type": "Number", + "label": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "String", + "label": "Joystick name", + "name": "JoystickIdentifier" + }, + { + "value": "0.4", + "type": "Number", + "label": "Dead zone radius (range: 0 to 1)", + "description": "The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved)", + "name": "DeadZoneRadius" + }, + { + "value": "0", + "type": "Number", + "label": "Joystick angle (range: -180 to 180)", + "hidden": true, + "name": "JoystickAngle" + }, + { + "value": "0", + "type": "Number", + "label": "Joystick force (range: 0 to 1)", + "hidden": true, + "name": "JoystickForce" + }, + { + "value": "0", + "type": "Number", + "label": "", + "hidden": true, + "name": "TouchId" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "TouchIndex" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ControllerIdentifier" + }, + { + "propertyName": "JoystickIdentifier" + }, + { + "propertyName": "DeadZoneRadius" + }, + { + "propertyName": "JoystickAngle" + }, + { + "propertyName": "JoystickForce" + }, + { + "propertyName": "TouchId" + }, + { + "propertyName": "TouchIndex" + } + ] + } + }, + { + "description": "Detect presses made on a touchscreen on the object so it acts like a button and automatically trigger the button having the same identifier for the mapper behaviors.", + "fullName": "Multitouch button", + "helpPath": "", + "iconUrl": "", + "name": "MultitouchButton", + "objectType": "", + "previewIconUrl": "", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsReleased", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsReleased", + "False", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchButton::SetButtonState" + }, + "parameters": [ + "Object", + "Behavior", + "\"Idle\"", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsJustPressed", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsJustPressed", + "False", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchButton::SetButtonState" + }, + "parameters": [ + "Object", + "Behavior", + "\"Pressed\"", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + }, + { + "type": { + "value": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchIndex", + "=", + "0" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "StartedTouchOrMouseCount()", + "conditions": [], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "CollisionPoint" + }, + "parameters": [ + "Object", + "TouchX(StartedTouchOrMouseId(TouchIndex), Object.Layer())", + "TouchY(StartedTouchOrMouseId(TouchIndex), Object.Layer())" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "Radius", + ">", + "DistanceBetweenPositions(Object.CenterX(), Object.CenterY(), TouchX(StartedTouchOrMouseId(TouchIndex), Object.Layer()), TouchY(StartedTouchOrMouseId(TouchIndex), Object.Layer()))" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "StartedTouchOrMouseId(TouchIndex)" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchButton::SetButtonState" + }, + "parameters": [ + "Object", + "Behavior", + "\"JustPressed\"", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsJustPressed", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchIndex", + "+", + "1" + ] + } + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + }, + { + "type": { + "value": "HasTouchEnded" + }, + "parameters": [ + "", + "TouchId" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchButton::SetButtonState" + }, + "parameters": [ + "Object", + "Behavior", + "\"Released\"", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsReleased", + "True", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsJustPressed", + "False", + "" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "0" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchButton", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button was just pressed.", + "fullName": "Button just pressed", + "functionType": "Condition", + "name": "IsJustPressed", + "sentence": "Button _PARAM0_ was just pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsJustPressed", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchButton", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is pressed.", + "fullName": "Button pressed", + "functionType": "Condition", + "name": "IsPressed", + "sentence": "Button _PARAM0_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchButton", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is released.", + "fullName": "Button released", + "functionType": "Condition", + "name": "IsReleased", + "sentence": "Button _PARAM0_ is released", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsReleased", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchButton", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Button state", + "functionType": "Action", + "name": "SetButtonState", + "private": true, + "sentence": "Mark the button _PARAM0_ as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::SetButtonState" + }, + "parameters": [ + "", + "ControllerIdentifier", + "ButtonIdentifier", + "ButtonState", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchButton", + "type": "behavior" + }, + { + "description": "Button state", + "name": "ButtonState", + "supplementaryInformation": "[\"Idle\",\"JustPressed\",\"Pressed\",\"Released\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "IsJustPressed" + }, + { + "functionName": "IsPressed" + }, + { + "functionName": "IsReleased" + }, + { + "functionName": "SetButtonState" + } + ] + }, + "propertyDescriptors": [ + { + "value": "1", + "type": "Number", + "label": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "A", + "type": "String", + "label": "Button identifier", + "name": "ButtonIdentifier" + }, + { + "value": "0", + "type": "Number", + "label": "TouchID", + "hidden": true, + "name": "TouchId" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "TouchIndex" + }, + { + "value": "", + "type": "Boolean", + "label": "Button released", + "hidden": true, + "name": "IsReleased" + }, + { + "value": "", + "type": "Boolean", + "label": "Button just pressed", + "hidden": true, + "name": "IsJustPressed" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Triggering circle radius", + "description": "This circle adds up to the object collision mask.", + "name": "Radius" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ControllerIdentifier" + }, + { + "propertyName": "ButtonIdentifier" + }, + { + "propertyName": "TouchId" + }, + { + "propertyName": "TouchIndex" + }, + { + "propertyName": "IsReleased" + }, + { + "propertyName": "IsJustPressed" + }, + { + "propertyName": "Radius" + } + ] + } + }, + { + "description": "Control a platformer character with a multitouch controller.", + "fullName": "Platformer multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "PlatformerMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Left\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "Property" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Right\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "Property" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Up\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "Property" + ] + }, + { + "type": { + "value": "PlatformBehavior::SimulateLadderKey" + }, + "parameters": [ + "Object", + "Property" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Down\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "Property" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsButtonPressed" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JumpButton", + "\"Down\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateJumpKey" + }, + "parameters": [ + "Object", + "Property" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::PlatformerMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Platform character behavior", + "extraInformation": [ + "PlatformBehavior::PlatformerObjectBehavior" + ], + "choices": [], + "name": "Property" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Joystick name", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "A", + "type": "String", + "label": "Jump button name", + "group": "Controls", + "name": "JumpButton" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Property" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "JoystickIdentifier" + }, + { + "propertyName": "JumpButton" + } + ] + } + ] + } + }, + { + "description": "Control a 3D physics character with a multitouch controller.", + "fullName": "3D platformer multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "Platformer3DMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::StickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "JoystickIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SetForwardAngle" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D", + "=", + "SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier) + CameraAngle(Object.Layer())" + ] + }, + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateStick" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D", + "-90", + "SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsButtonPressed" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JumpButton", + "\"Down\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateJumpKey" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::Platformer3DMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics character", + "extraInformation": [ + "Physics3D::PhysicsCharacter3D" + ], + "choices": [], + "name": "PhysicsCharacter3D" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Walk joystick", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "A", + "type": "String", + "label": "Jump button name", + "group": "Controls", + "name": "JumpButton" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "PhysicsCharacter3D" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "JoystickIdentifier" + }, + { + "propertyName": "JumpButton" + } + ] + } + ] + } + }, + { + "description": "Control a 3D physics character with a multitouch controller.", + "fullName": "3D shooter multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "Shooter3DMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::StickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "JoystickIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateStick" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D", + "SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier)", + "SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsButtonPressed" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JumpButton", + "\"Down\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateJumpKey" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::Shooter3DMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics character", + "extraInformation": [ + "Physics3D::PhysicsCharacter3D" + ], + "choices": [], + "name": "PhysicsCharacter3D" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Walk joystick", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "A", + "type": "String", + "label": "Jump button name", + "group": "Controls", + "name": "JumpButton" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "PhysicsCharacter3D" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "JoystickIdentifier" + }, + { + "propertyName": "JumpButton" + } + ] + } + ] + } + }, + { + "description": "Control camera rotations with a multitouch controller.", + "fullName": "First person camera multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "FirstPersonMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "TODO It's probably a bad idea to rotate the object around Y." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CurrentRotationSpeedZ", + "=", + "SpriteMultitouchJoystick::AcceleratedSpeed(CurrentRotationSpeedZ, SpriteMultitouchJoystick::StickForceX(ControllerIdentifier, CameraStick) * HorizontalRotationSpeedMax, HorizontalRotationSpeedMax, HorizontalRotationAcceleration, HorizontalRotationDeceleration)" + ] + }, + { + "type": { + "value": "SetAngle" + }, + "parameters": [ + "Object", + "+", + "CurrentRotationSpeedZ * TimeDelta()" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CurrentRotationSpeedY", + "=", + "SpriteMultitouchJoystick::AcceleratedSpeed(CurrentRotationSpeedY, SpriteMultitouchJoystick::StickForceY(ControllerIdentifier, CameraStick) * VerticalRotationSpeedMax, VerticalRotationSpeedMax, VerticalRotationAcceleration, VerticalRotationDeceleration)" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationY" + }, + "parameters": [ + "Object", + "Object3D", + "+", + "CurrentRotationSpeedY * TimeDelta()" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationY" + }, + "parameters": [ + "Object", + "Object3D", + "=", + "clamp(Object.Object3D::RotationY(), VerticalAngleMin, VerticalAngleMax)" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper::LookFromObjectEyes" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera to look though _PARAM1_ eyes. The object must look to the right when all its angles are 0 and the top of its head be toward Z+.", + "fullName": "Look through object eyes", + "functionType": "Action", + "group": "Layers and cameras", + "name": "LookFromObjectEyes", + "private": true, + "sentence": "Move the camera to look though _PARAM0_ eyes", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "CenterCameraOnObject" + }, + "parameters": [ + "", + "Object", + "", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "Object.Object3D::Z() + Object.Object3D::Depth() + OffsetZ", + "", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationX" + }, + "parameters": [ + "", + "=", + "- Object.Object3D::RotationY() + 90", + "GetArgumentAsString(\"Layer\")", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationY" + }, + "parameters": [ + "", + "=", + "Object.Object3D::RotationX()", + "GetArgumentAsString(\"Layer\")", + "" + ] + }, + { + "type": { + "value": "SetCameraAngle" + }, + "parameters": [ + "", + "=", + "Object.Angle() + 90", + "Object.Layer()", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the maximum horizontal rotation speed of the object.", + "fullName": "Maximum horizontal rotation speed", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "HorizontalRotationSpeedMax", + "sentence": "the maximum horizontal rotation speed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "HorizontalRotationSpeedMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "HorizontalRotationSpeedMax", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "SetHorizontalRotationSpeedMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "HorizontalRotationSpeedMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the horizontal rotation acceleration of the object.", + "fullName": "Horizontal rotation acceleration", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "HorizontalRotationAcceleration", + "sentence": "the horizontal rotation acceleration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "HorizontalRotationAcceleration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "HorizontalRotationAcceleration", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "SetHorizontalRotationAcceleration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "HorizontalRotationAcceleration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the horizontal rotation deceleration of the object.", + "fullName": "Horizontal rotation deceleration", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "HorizontalRotationDeceleration", + "sentence": "the horizontal rotation deceleration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "HorizontalRotationDeceleration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "HorizontalRotationDeceleration", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "SetHorizontalRotationDeceleration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "HorizontalRotationDeceleration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the maximum vertical rotation speed of the object.", + "fullName": "Maximum vertical rotation speed", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "VerticalRotationSpeedMax", + "sentence": "the maximum vertical rotation speed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalRotationSpeedMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalRotationSpeedMax", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "SetVerticalRotationSpeedMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalRotationSpeedMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the vertical rotation acceleration of the object.", + "fullName": "Vertical rotation acceleration", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "VerticalRotationAcceleration", + "sentence": "the vertical rotation acceleration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalRotationAcceleration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalRotationAcceleration", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "SetVerticalRotationAcceleration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalRotationAcceleration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the vertical rotation deceleration of the object.", + "fullName": "Vertical rotation deceleration", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "VerticalRotationDeceleration", + "sentence": "the vertical rotation deceleration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalRotationDeceleration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalRotationDeceleration", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "SetVerticalRotationDeceleration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalRotationDeceleration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the minimum vertical camera angle of the object.", + "fullName": "Minimum vertical camera angle", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "VerticalAngleMin", + "sentence": "the minimum vertical camera angle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalAngleMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalAngleMin", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "SetVerticalAngleMin", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalAngleMin", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the maximum vertical camera angle of the object.", + "fullName": "Maximum vertical camera angle", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "VerticalAngleMax", + "sentence": "the maximum vertical camera angle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalAngleMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalAngleMax", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "SetVerticalAngleMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalAngleMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the z position offset of the object.", + "fullName": "Z position offset", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper position configuration", + "name": "OffsetZ", + "sentence": "the z position offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "OffsetZ" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "OffsetZ", + "group": "First person camera multitouch controller mapper position configuration", + "name": "SetOffsetZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OffsetZ", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + }, + { + "folderName": "Layers and cameras", + "children": [ + { + "functionName": "LookFromObjectEyes" + } + ] + }, + { + "folderName": "First person camera multitouch controller mapper horizontal rotation configuration", + "children": [ + { + "functionName": "HorizontalRotationSpeedMax" + }, + { + "functionName": "SetHorizontalRotationSpeedMax" + }, + { + "functionName": "HorizontalRotationAcceleration" + }, + { + "functionName": "SetHorizontalRotationAcceleration" + }, + { + "functionName": "HorizontalRotationDeceleration" + }, + { + "functionName": "SetHorizontalRotationDeceleration" + } + ] + }, + { + "folderName": "First person camera multitouch controller mapper vertical rotation configuration", + "children": [ + { + "functionName": "VerticalRotationSpeedMax" + }, + { + "functionName": "SetVerticalRotationSpeedMax" + }, + { + "functionName": "VerticalRotationAcceleration" + }, + { + "functionName": "SetVerticalRotationAcceleration" + }, + { + "functionName": "VerticalRotationDeceleration" + }, + { + "functionName": "SetVerticalRotationDeceleration" + }, + { + "functionName": "VerticalAngleMin" + }, + { + "functionName": "SetVerticalAngleMin" + }, + { + "functionName": "VerticalAngleMax" + }, + { + "functionName": "SetVerticalAngleMax" + } + ] + }, + { + "folderName": "First person camera multitouch controller mapper position configuration", + "children": [ + { + "functionName": "OffsetZ" + }, + { + "functionName": "SetOffsetZ" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D capability", + "extraInformation": [ + "Scene3D::Base3DBehavior" + ], + "choices": [], + "name": "Object3D" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Secondary", + "type": "Choice", + "label": "Camera joystick", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "CameraStick" + }, + { + "value": "180", + "type": "Number", + "unit": "AngularSpeed", + "label": "Maximum rotation speed", + "group": "Horizontal rotation", + "name": "HorizontalRotationSpeedMax" + }, + { + "value": "360", + "type": "Number", + "label": "Rotation acceleration", + "group": "Horizontal rotation", + "name": "HorizontalRotationAcceleration" + }, + { + "value": "720", + "type": "Number", + "label": "Rotation deceleration", + "group": "Horizontal rotation", + "name": "HorizontalRotationDeceleration" + }, + { + "value": "120", + "type": "Number", + "unit": "AngularSpeed", + "label": "Maximum rotation speed", + "group": "Vertical rotation", + "name": "VerticalRotationSpeedMax" + }, + { + "value": "240", + "type": "Number", + "label": "Rotation acceleration", + "group": "Vertical rotation", + "name": "VerticalRotationAcceleration" + }, + { + "value": "480", + "type": "Number", + "label": "Rotation deceleration", + "group": "Vertical rotation", + "name": "VerticalRotationDeceleration" + }, + { + "value": "-90", + "type": "Number", + "unit": "DegreeAngle", + "label": "Minimum angle", + "group": "Vertical rotation", + "name": "VerticalAngleMin" + }, + { + "value": "90", + "type": "Number", + "unit": "DegreeAngle", + "label": "Maximum angle", + "group": "Vertical rotation", + "name": "VerticalAngleMax" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Z position offset", + "group": "Position", + "name": "OffsetZ" + }, + { + "value": "0", + "type": "Number", + "unit": "AngularSpeed", + "label": "Current rotation speed Z", + "hidden": true, + "name": "CurrentRotationSpeedZ" + }, + { + "value": "0", + "type": "Number", + "unit": "AngularSpeed", + "label": "Current rotation speed Y", + "hidden": true, + "name": "CurrentRotationSpeedY" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Object3D" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "CameraStick" + } + ] + }, + { + "folderName": "Horizontal rotation", + "children": [ + { + "propertyName": "HorizontalRotationSpeedMax" + }, + { + "propertyName": "HorizontalRotationAcceleration" + }, + { + "propertyName": "HorizontalRotationDeceleration" + } + ] + }, + { + "folderName": "Vertical rotation", + "children": [ + { + "propertyName": "VerticalRotationSpeedMax" + }, + { + "propertyName": "VerticalRotationAcceleration" + }, + { + "propertyName": "VerticalRotationDeceleration" + }, + { + "propertyName": "VerticalAngleMin" + }, + { + "propertyName": "VerticalAngleMax" + } + ] + }, + { + "folderName": "Position", + "children": [ + { + "propertyName": "OffsetZ" + } + ] + }, + { + "propertyName": "CurrentRotationSpeedZ" + }, + { + "propertyName": "CurrentRotationSpeedY" + } + ] + } + }, + { + "description": "Control a 3D physics car with a multitouch controller.", + "fullName": "3D car multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "PhysicsCar3DMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::StickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "SteerJoystickIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateSteeringStick" + }, + "parameters": [ + "Object", + "PhysicsCar3D", + "SpriteMultitouchJoystick::StickForceX(ControllerIdentifier, SteerJoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::StickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "SpeedJoystickIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateAcceleratorStick" + }, + "parameters": [ + "Object", + "PhysicsCar3D", + "-SpriteMultitouchJoystick::StickForceY(ControllerIdentifier, SpeedJoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsButtonPressed" + }, + "parameters": [ + "", + "ControllerIdentifier", + "HandBrakeButton", + "\"Down\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateHandBrakeKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::PhysicsCar3DMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics car", + "extraInformation": [ + "Physics3D::PhysicsCar3D" + ], + "choices": [], + "name": "PhysicsCar3D" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Steer joystick", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "SteerJoystickIdentifier" + }, + { + "value": "Secondary", + "type": "Choice", + "label": "Speed joystick", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "SpeedJoystickIdentifier" + }, + { + "value": "B", + "type": "String", + "label": "Hand brake button name", + "group": "Controls", + "name": "HandBrakeButton" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "PhysicsCar3D" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "SteerJoystickIdentifier" + }, + { + "propertyName": "SpeedJoystickIdentifier" + }, + { + "propertyName": "HandBrakeButton" + } + ] + } + ] + } + }, + { + "description": "Control a top-down character with a multitouch controller.", + "fullName": "Top-down multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "TopDownMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "StickMode", + "=", + "\"Analog\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateStick" + }, + "parameters": [ + "Object", + "TopDownMovement", + "SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier)", + "SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "StickMode", + "=", + "\"360°\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateStick" + }, + "parameters": [ + "Object", + "TopDownMovement", + "SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier)", + "sign(SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier))" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "StickMode", + "=", + "\"8 Directions\"" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "TopDownMovementBehavior::DiagonalsAllowed" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Left\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Right\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Up\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Down\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "TopDownMovementBehavior::DiagonalsAllowed" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Left\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Right\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Up\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Down\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"UpLeft\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + }, + { + "type": { + "value": "TopDownMovementBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"UpRight\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + }, + { + "type": { + "value": "TopDownMovementBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"DownLeft\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + }, + { + "type": { + "value": "TopDownMovementBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"DownRight\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + }, + { + "type": { + "value": "TopDownMovementBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::TopDownMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Top-down movement behavior", + "extraInformation": [ + "TopDownMovementBehavior::TopDownMovementBehavior" + ], + "choices": [], + "name": "TopDownMovement" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Joystick name", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "Analog", + "type": "Choice", + "label": "Stick mode", + "group": "Controls", + "choices": [ + { + "label": "Analog", + "value": "Analog" + }, + { + "label": "360°", + "value": "360°" + }, + { + "label": "8 Directions", + "value": "8 Directions" + } + ], + "name": "StickMode" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "TopDownMovement" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "propertyName": "JoystickIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "StickMode" + } + ] + } + ] + } + } + ], + "eventsBasedObjects": [ + { + "areaMaxX": 64, + "areaMaxY": 64, + "areaMaxZ": 64, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "assetStoreTag": "multitouch joysticks", + "defaultName": "Joystick", + "description": "Joystick for touchscreens.", + "fullName": "Multitouch Joystick", + "helpPath": "", + "iconUrl": "", + "isUsingLegacyInstancesRenderer": true, + "name": "SpriteMultitouchJoystick", + "previewIconUrl": "", + "objects": [ + { + "adaptCollisionMaskAutomatically": false, + "assetStoreId": "", + "name": "Thumb", + "type": "Sprite", + "updateIfNotVisible": false, + "variables": [], + "effects": [], + "behaviors": [], + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [] + } + ] + } + ] + }, + { + "adaptCollisionMaskAutomatically": false, + "assetStoreId": "", + "name": "Border", + "type": "Sprite", + "updateIfNotVisible": false, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "MultitouchJoystick", + "type": "SpriteMultitouchJoystick::MultitouchJoystick", + "ControllerIdentifier": 1, + "JoystickIdentifier": "Primary", + "FloatingEnabled": false, + "DeadZoneRadius": 0.4, + "JoystickAngle": 0, + "JoystickForce": 0, + "TouchId": 0, + "TouchIndex": 0 + } + ], + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [] + } + ] + } + ] + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Thumb" + }, + { + "objectName": "Border" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [], + "editionSettings": [], + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Create" + }, + "parameters": [ + "", + "Border", + "0", + "0", + "" + ] + }, + { + "type": { + "value": "Create" + }, + "parameters": [ + "", + "Thumb", + "0", + "0", + "" + ] + }, + { + "type": { + "value": "SetZOrder" + }, + "parameters": [ + "Border", + "=", + "1" + ] + }, + { + "type": { + "value": "SetZOrder" + }, + "parameters": [ + "Thumb", + "=", + "2" + ] + }, + { + "type": { + "value": "SetCenter" + }, + "parameters": [ + "Border", + "=", + "0", + "=", + "0" + ] + }, + { + "type": { + "value": "SetCenter" + }, + "parameters": [ + "Thumb", + "=", + "0", + "=", + "0" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::UpdateConfiguration" + }, + "parameters": [ + "Object", + "" + ] + }, + { + "type": { + "value": "SetIncludedInParentCollisionMask" + }, + "parameters": [ + "Thumb", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::IsInGameEdition" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "PutAroundObject" + }, + "parameters": [ + "Thumb", + "Border", + "Border.MultitouchJoystick::JoystickForce() * Border.Width() / 2", + "Border.MultitouchJoystick::JoystickAngle()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldBeHiddenWhenReleased", + "True", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::IsPressed" + }, + "parameters": [ + "Object", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Hide" + }, + "parameters": [ + "Object" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::ActivateControl" + }, + "parameters": [ + "Object", + "no", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "ShouldBeHiddenWhenReleased", + "False", + "" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::UpdateConfiguration" + }, + "parameters": [ + "Object", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Pass the object property values to the behavior.", + "fullName": "Update configuration", + "functionType": "Action", + "name": "UpdateConfiguration", + "private": true, + "sentence": "Update the configuration of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetControllerIdentifier" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "ControllerIdentifier", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetJoystickIdentifier" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "JoystickIdentifier", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetDeadZoneRadius" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "DeadZoneRadius", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Show the joystick until it is released.", + "fullName": "Show and start pressing", + "functionType": "Action", + "name": "TeleportAndPress", + "sentence": "Show _PARAM0_ at the cursor position and start pressing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetXY" + }, + "parameters": [ + "Object", + "=", + "Object.ParentTouchX(StartedTouchOrMouseId(0))", + "=", + "Object.ParentTouchY(StartedTouchOrMouseId(0))" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::ActivateControl" + }, + "parameters": [ + "Object", + "yes", + "" + ] + }, + { + "type": { + "value": "Show" + }, + "parameters": [ + "Object", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "ShouldBeHiddenWhenReleased", + "True", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::ForceStartPressing" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "StartedTouchOrMouseId(0)", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the X position of a specified touch", + "fullName": "Touch X position (on parent)", + "functionType": "Expression", + "name": "ParentTouchX", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];", + "const touchId = eventsFunctionContext.getArgument(\"TouchId\");", + "eventsFunctionContext.returnValue = gdjs.evtTools.input.getTouchX(object.getInstanceContainer(), touchId, object.getLayer());" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + }, + { + "description": "Touch identifier", + "name": "TouchId", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the X position of a specified touch", + "fullName": "Touch X position (on parent)", + "functionType": "Expression", + "name": "ParentTouchY", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];", + "const touchId = eventsFunctionContext.getArgument(\"TouchId\");", + "eventsFunctionContext.returnValue = gdjs.evtTools.input.getTouchY(object.getInstanceContainer(), touchId, object.getLayer());" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + }, + { + "description": "Touch identifier", + "name": "TouchId", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "De/activate control of the joystick.", + "fullName": "De/activate control", + "functionType": "Action", + "name": "ActivateControl", + "sentence": "Activate control of _PARAM0_: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldActivate", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "no" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldActivate", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "yes" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + }, + { + "description": "Activate", + "name": "ShouldActivate", + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a stick is pressed.", + "fullName": "Stick pressed", + "functionType": "Condition", + "name": "IsPressed", + "sentence": "Stick _PARAM0_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::IsPressed" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "!=" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the joystick force (from 0 to 1).", + "fullName": "Joystick force (deprecated)", + "functionType": "ExpressionAndCondition", + "name": "JoystickForce", + "private": true, + "sentence": "the joystick force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::JoystickForce()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the strick force (from 0 to 1).", + "fullName": "Stick force", + "functionType": "ExpressionAndCondition", + "name": "StickForce", + "sentence": "the stick force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::JoystickForce()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the stick force on X axis (from -1 at the left to 1 at the right).", + "fullName": "Stick X force", + "functionType": "ExpressionAndCondition", + "name": "StickForceX", + "sentence": "the stick X force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::StickForceX()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the stick force on Y axis (from -1 at the top to 1 at the bottom).", + "fullName": "Stick Y force", + "functionType": "ExpressionAndCondition", + "name": "StickForceY", + "sentence": "the stick Y force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::StickForceY()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle the joystick is pointing towards (from -180 to 180).", + "fullName": "Joystick angle (deprecated)", + "functionType": "Expression", + "name": "JoystickAngle", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::JoystickAngle()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle the stick is pointing towards (from -180 to 180).", + "fullName": "Stick angle", + "functionType": "Expression", + "name": "StickAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::JoystickAngle()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (4-way movement)", + "functionType": "Condition", + "name": "IsDirectionPushed4Way", + "sentence": "_PARAM0_ is pushed in direction _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (8-way movement)", + "functionType": "Condition", + "name": "IsDirectionPushed8Way", + "sentence": "_PARAM0_ is pushed in direction _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\",\"UpLeft\",\"UpRight\",\"DownLeft\",\"DownRight\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "the multitouch controller identifier (1, 2, 3, 4...).", + "fullName": "Multitouch controller identifier", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "ControllerIdentifier", + "sentence": "the multitouch controller identifier", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::PropertyControllerIdentifier()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ControllerIdentifier", + "group": "Multitouch Joystick configuration", + "name": "SetControllerIdentifier", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetControllerIdentifier" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "Value", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the joystick name of the object.", + "fullName": "Joystick name", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "JoystickIdentifier", + "sentence": "the joystick name", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "Border.MultitouchJoystick::PropertyJoystickIdentifier()" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "JoystickIdentifier", + "group": "Multitouch Joystick configuration", + "name": "SetJoystickIdentifier", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetJoystickIdentifier" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "Value", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the dead zone radius of the joystick (range: 0 to 1). The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved).", + "fullName": "Dead zone radius", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "DeadZoneRadius", + "sentence": "the dead zone radius", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::PropertyDeadZoneRadius()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "DeadZoneRadius", + "group": "Multitouch Joystick configuration", + "name": "SetDeadZoneRadius", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetDeadZoneRadius" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "Value", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPostEvents" + }, + { + "functionName": "onHotReloading" + }, + { + "functionName": "UpdateConfiguration" + }, + { + "functionName": "TeleportAndPress" + }, + { + "functionName": "ParentTouchX" + }, + { + "functionName": "ParentTouchY" + }, + { + "functionName": "ActivateControl" + }, + { + "functionName": "IsPressed" + }, + { + "functionName": "JoystickForce" + }, + { + "functionName": "StickForce" + }, + { + "functionName": "StickForceX" + }, + { + "functionName": "StickForceY" + }, + { + "functionName": "JoystickAngle" + }, + { + "functionName": "StickAngle" + }, + { + "functionName": "IsDirectionPushed4Way" + }, + { + "functionName": "IsDirectionPushed8Way" + }, + { + "folderName": "Multitouch Joystick configuration", + "children": [ + { + "functionName": "ControllerIdentifier" + }, + { + "functionName": "SetControllerIdentifier" + }, + { + "functionName": "JoystickIdentifier" + }, + { + "functionName": "SetJoystickIdentifier" + }, + { + "functionName": "DeadZoneRadius" + }, + { + "functionName": "SetDeadZoneRadius" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "1", + "type": "Number", + "label": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Joystick name", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "0.4", + "type": "Number", + "label": "Dead zone radius (range: 0 to 1)", + "description": "The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved)", + "name": "DeadZoneRadius" + }, + { + "value": "Center-center", + "type": "String", + "label": "", + "description": "Only used by the scene editor.", + "extraInformation": [ + "Thumb" + ], + "choices": [], + "hidden": true, + "name": "ThumbAnchorOrigin" + }, + { + "value": "Center-center", + "type": "Number", + "label": "", + "description": "Only used by the scene editor.", + "hidden": true, + "name": "ThumbAnchorTarget" + }, + { + "value": "true", + "type": "Boolean", + "label": "", + "description": "Only used by the scene editor.", + "extraInformation": [ + "Thumb" + ], + "choices": [], + "hidden": true, + "name": "ThumbIsScaledProportionally" + }, + { + "value": "Center-center", + "type": "String", + "label": "", + "description": "Only used by the scene editor.", + "hidden": true, + "name": "ParentOrigin" + }, + { + "value": "", + "type": "Boolean", + "label": "", + "hidden": true, + "name": "ShouldBeHiddenWhenReleased" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ControllerIdentifier" + }, + { + "propertyName": "JoystickIdentifier" + }, + { + "propertyName": "DeadZoneRadius" + }, + { + "propertyName": "ThumbAnchorOrigin" + }, + { + "propertyName": "ThumbAnchorTarget" + }, + { + "propertyName": "ThumbIsScaledProportionally" + }, + { + "propertyName": "ParentOrigin" + }, + { + "propertyName": "ShouldBeHiddenWhenReleased" + } + ] + } + } + ] + }, + { + "author": "", + "category": "Input", + "dimension": "", + "extensionNamespace": "", + "fullName": "3D car keyboard mapper", + "gdevelopVersion": ">=5.5.222", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBhdGggZD0iTTI5LDE0aC00LjJjMC4xLTAuMywwLjItMC42LDAuMi0xYzAtMS43LTEuMy0zLTMtM0g4Yy0wLjYsMC0xLTAuNC0xLTFzMC40LTEsMS0xaDZjMS43LDAsMy0xLjMsMy0zVjJjMC0wLjYtMC40LTEtMS0xDQoJcy0xLDAuNC0xLDF2M2MwLDAuNi0wLjQsMS0xLDFIOEM2LjMsNiw1LDcuMyw1LDlzMS4zLDMsMywzaDE0YzAuNiwwLDEsMC40LDEsMXMtMC40LDEtMSwxSDNjLTAuNiwwLTEsMC40LTEsMXYxNGMwLDAuNiwwLjQsMSwxLDENCgloMjZjMC42LDAsMS0wLjQsMS0xVjE1QzMwLDE0LjQsMjkuNiwxNCwyOSwxNHogTTcsMThoMmMwLjYsMCwxLDAuNCwxLDFjMC0wLjYsMC40LTEsMS0xaDJjMC42LDAsMSwwLjQsMSwxYzAtMC42LDAuNC0xLDEtMWgyDQoJYzAuNiwwLDEsMC40LDEsMWMwLTAuNiwwLjQtMSwxLTFoMmMwLjYsMCwxLDAuNCwxLDFjMC0wLjYsMC40LTEsMS0xaDJjMC42LDAsMSwwLjQsMSwxcy0wLjQsMS0xLDFoLTJjLTAuNiwwLTEtMC40LTEtMQ0KCWMwLDAuNi0wLjQsMS0xLDFoLTJjLTAuNiwwLTEtMC40LTEtMWMwLDAuNi0wLjQsMS0xLDFoLTJjLTAuNiwwLTEtMC40LTEtMWMwLDAuNi0wLjQsMS0xLDFoLTJjLTAuNiwwLTEtMC40LTEtMWMwLDAuNi0wLjQsMS0xLDFINw0KCWMtMC42LDAtMS0wLjQtMS0xUzYuNCwxOCw3LDE4eiBNMTksMjJjMCwwLjEsMCwwLjMtMC4xLDAuNHMtMC4xLDAuMi0wLjIsMC4zYzAsMC0wLjEsMC4xLTAuMiwwLjFjMCwwLTAuMSwwLjEtMC4yLDAuMQ0KCWMtMC4xLDAtMC4xLDAtMC4yLDAuMWMtMC4xLDAtMC4xLDAtMC4yLDBjLTAuMSwwLTAuMSwwLTAuMiwwYy0wLjEsMC0wLjEsMC0wLjItMC4xYy0wLjEsMC0wLjEtMC4xLTAuMi0wLjFjMCwwLTAuMS0wLjEtMC4xLTAuMQ0KCWMtMC4xLTAuMS0wLjItMC4yLTAuMi0wLjNDMTcsMjIuMywxNywyMi4xLDE3LDIyYzAtMC4xLDAtMC4zLDAuMS0wLjRjMC0wLjEsMC4xLTAuMiwwLjItMC4zYzAsMCwwLjEtMC4xLDAuMS0wLjENCgljMC4xLDAsMC4xLTAuMSwwLjItMC4xYzAuMSwwLDAuMSwwLDAuMi0wLjFjMC4xLDAsMC4zLDAsMC40LDBjMC4xLDAsMC4xLDAsMC4yLDAuMWMwLjEsMCwwLjEsMC4xLDAuMiwwLjFjMC4xLDAsMC4xLDAuMSwwLjIsMC4xDQoJYzAuMSwwLjEsMC4yLDAuMiwwLjIsMC4zQzE5LDIxLjcsMTksMjEuOSwxOSwyMnogTTE1LDIyYzAsMC4xLDAsMC4zLTAuMSwwLjRzLTAuMSwwLjItMC4yLDAuM2MwLDAtMC4xLDAuMS0wLjIsMC4xDQoJYzAsMC0wLjEsMC4xLTAuMiwwLjFjLTAuMSwwLTAuMSwwLTAuMiwwLjFjLTAuMSwwLTAuMSwwLTAuMiwwYy0wLjMsMC0wLjUtMC4xLTAuNy0wLjNjLTAuMS0wLjEtMC4yLTAuMi0wLjItMC4zDQoJQzEzLDIyLjMsMTMsMjIuMSwxMywyMmMwLTAuMSwwLTAuMSwwLTAuMmMwLTAuMSwwLTAuMSwwLjEtMC4yYzAtMC4xLDAtMC4xLDAuMS0wLjJjMC0wLjEsMC4xLTAuMSwwLjEtMC4xYzAuMi0wLjIsMC42LTAuMywwLjktMC4zDQoJYzAuMSwwLDAuMSwwLDAuMiwwLjFjMC4xLDAsMC4xLDAuMSwwLjIsMC4xYzAuMSwwLDAuMSwwLjEsMC4yLDAuMWMwLDAsMC4xLDAuMSwwLjEsMC4xYzAsMC4xLDAuMSwwLjEsMC4xLDAuMmMwLDAuMSwwLDAuMSwwLjEsMC4yDQoJQzE1LDIxLjksMTUsMjEuOSwxNSwyMnogTTUsMjJjMC0wLjEsMC0wLjMsMC4xLTAuNGMwLTAuMSwwLjEtMC4yLDAuMi0wLjNjMC4yLTAuMiwwLjYtMC4zLDAuOS0wLjNjMC4xLDAsMC4xLDAsMC4yLDAuMQ0KCWMwLjEsMCwwLjEsMC4xLDAuMiwwLjFjMCwwLDAuMSwwLjEsMC4yLDAuMWMwLjEsMC4xLDAuMiwwLjIsMC4yLDAuM0M3LDIxLjcsNywyMS45LDcsMjJjMCwwLjMtMC4xLDAuNS0wLjMsMC43YzAsMC0wLjEsMC4xLTAuMiwwLjENCgljLTAuMSwwLTAuMSwwLjEtMC4yLDAuMWMtMC4xLDAtMC4xLDAtMC4yLDAuMWMtMC4xLDAtMC4xLDAtMC4yLDBjLTAuMywwLTAuNS0wLjEtMC43LTAuM0M1LjEsMjIuNSw1LDIyLjMsNSwyMnogTTgsMjZINw0KCWMtMC42LDAtMS0wLjQtMS0xczAuNC0xLDEtMWgxYzAuNiwwLDEsMC40LDEsMVM4LjYsMjYsOCwyNnogTTEwLjksMjIuNGMwLDAuMSwwLDAuMS0wLjEsMC4yYzAsMC0wLjEsMC4xLTAuMSwwLjENCglDMTAuNSwyMi45LDEwLjMsMjMsMTAsMjNjLTAuMSwwLTAuMywwLTAuNC0wLjFjLTAuMS0wLjEtMC4yLTAuMS0wLjMtMC4yYzAtMC4xLTAuMS0wLjEtMC4xLTAuMWMwLTAuMS0wLjEtMC4xLTAuMS0wLjINCgljMC0wLjEsMC0wLjEtMC4xLTAuMmMwLTAuMSwwLTAuMSwwLTAuMmMwLTAuMywwLjEtMC41LDAuMy0wLjdjMC4xLTAuMSwwLjItMC4yLDAuMy0wLjJjMC40LTAuMiwwLjgtMC4xLDEuMSwwLjINCgljMC4yLDAuMiwwLjMsMC40LDAuMywwLjdjMCwwLjEsMCwwLjEsMCwwLjJDMTEsMjIuMywxMSwyMi4zLDEwLjksMjIuNHogTTIwLDI2aC04Yy0wLjYsMC0xLTAuNC0xLTFzMC40LTEsMS0xaDhjMC42LDAsMSwwLjQsMSwxDQoJUzIwLjYsMjYsMjAsMjZ6IE0yMi45LDIyLjRjMCwwLjEsMCwwLjEtMC4xLDAuMmMwLDAtMC4xLDAuMS0wLjEsMC4xYzAsMC0wLjEsMC4xLTAuMiwwLjFjMCwwLTAuMSwwLjEtMC4yLDAuMWMtMC4xLDAtMC4xLDAtMC4yLDAuMQ0KCWMtMC4xLDAtMC4xLDAtMC4yLDBjLTAuMSwwLTAuMSwwLTAuMiwwYy0wLjEsMC0wLjEsMC0wLjItMC4xYy0wLjEsMC0wLjEtMC4xLTAuMi0wLjFjMCwwLTAuMS0wLjEtMC4xLTAuMWMwLTAuMS0wLjEtMC4xLTAuMS0wLjENCgljMC0wLjEtMC4xLTAuMS0wLjEtMC4yYzAtMC4xLDAtMC4xLTAuMS0wLjJjMC0wLjEsMC0wLjEsMC0wLjJjMC0wLjMsMC4xLTAuNSwwLjMtMC43YzAsMCwwLjEtMC4xLDAuMS0wLjFjMC4xLDAsMC4xLTAuMSwwLjItMC4xDQoJYzAuMSwwLDAuMSwwLDAuMi0wLjFjMC4xLDAsMC4zLDAsMC40LDBjMC4xLDAsMC4xLDAsMC4yLDAuMWMwLjEsMCwwLjEsMC4xLDAuMiwwLjFjMC4xLDAsMC4xLDAuMSwwLjIsMC4xYzAuMiwwLjIsMC4zLDAuNCwwLjMsMC43DQoJYzAsMC4xLDAsMC4xLDAsMC4yQzIzLDIyLjMsMjMsMjIuMywyMi45LDIyLjR6IE0yNSwyNmgtMWMtMC42LDAtMS0wLjQtMS0xczAuNC0xLDEtMWgxYzAuNiwwLDEsMC40LDEsMVMyNS42LDI2LDI1LDI2eiBNMjYuNywyMi43DQoJQzI2LjUsMjIuOSwyNi4zLDIzLDI2LDIzYy0wLjEsMC0wLjEsMC0wLjIsMGMtMC4xLDAtMC4xLDAtMC4yLTAuMWMtMC4xLDAtMC4xLTAuMS0wLjItMC4xYy0wLjEsMC0wLjEtMC4xLTAuMi0wLjENCglDMjUuMSwyMi41LDI1LDIyLjMsMjUsMjJjMC0wLjEsMC0wLjMsMC4xLTAuNGMwLTAuMSwwLjEtMC4yLDAuMi0wLjNjMCwwLDAuMS0wLjEsMC4yLTAuMWMwLDAsMC4xLTAuMSwwLjItMC4xYzAuMSwwLDAuMSwwLDAuMi0wLjENCgljMC4zLTAuMSwwLjcsMCwwLjksMC4zYzAuMiwwLjIsMC4zLDAuNCwwLjMsMC43QzI3LDIyLjMsMjYuOSwyMi41LDI2LjcsMjIuN3oiLz4NCjwvc3ZnPg0K", + "name": "PhysicsCar3DKeyMapper", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/Glyphster Pack/Master/SVG/Computers and Hardware/3e5871434a72821bf3ecb44a6857e62a438cf23dc8f95966f56ae97e95315468_Computers and Hardware_keyboard.svg", + "shortDescription": "3D car keyboard controls.", + "version": "1.0.1", + "description": "3D physics car keyboard controls.", + "origin": { + "identifier": "PhysicsCar3DKeyMapper", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT" + }, + "eventsBasedBehaviors": [ + { + "description": "Control a 3D physics car with a keyboard.", + "fullName": "3D car keyboard mapper", + "helpPath": "", + "iconUrl": "", + "name": "PhysicsCar3DKeyboardMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Down" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateBackwardKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Up" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateForwardKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Left" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateLeftKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Right" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateRightKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "HandBrake" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateHandBrakeKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "PhysicsCar3DKeyMapper::PhysicsCar3DKeyboardMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics car", + "extraInformation": [ + "Physics3D::PhysicsCar3D" + ], + "choices": [], + "name": "PhysicsCar3D" + }, + { + "value": "Space", + "type": "KeyboardKey", + "label": "Hand brake key", + "name": "HandBrake" + }, + { + "value": "Up", + "type": "KeyboardKey", + "label": "Up key", + "name": "Up" + }, + { + "value": "Left", + "type": "KeyboardKey", + "label": "Left key", + "name": "Left" + }, + { + "value": "Down", + "type": "KeyboardKey", + "label": "Down key", + "name": "Down" + }, + { + "value": "Right", + "type": "KeyboardKey", + "label": "Right key", + "name": "Right" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "PhysicsCar3D" + }, + { + "propertyName": "HandBrake" + }, + { + "propertyName": "Up" + }, + { + "propertyName": "Left" + }, + { + "propertyName": "Down" + }, + { + "propertyName": "Right" + } + ] + } + } + ], + "eventsBasedObjects": [] + }, + { + "author": "", + "category": "General", + "dimension": "3D", + "extensionNamespace": "", + "fullName": "3D terrain", + "gdevelopVersion": ">=5.5.222", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXRlcnJhaW4iIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTQsNkwxMC4yNSwxMUwxMy4xLDE0LjhMMTEuNSwxNkM5LjgxLDEzLjc1IDcsMTAgNywxMEwxLDE4SDIzTDE0LDZaIiAvPjwvc3ZnPg==", + "name": "Terrain3D", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/10e0a26c0d500830dfe23b94138beb0ef61607ba62bca05a5aa5c849777f1b06_terrain.svg", + "shortDescription": "A ground with hills and valleys that can be sculpted and painted in the editor, with collisions.", + "version": "0.1.0", + "description": [ + "A 3D terrain object for open worlds, adventure or racing games: hills, mountains, islands or valleys, with painted layers of grass, dirt, rock or sand.", + "", + "- **Sculpt and paint in the scene editor**: select a terrain to show the tools (raise, lower, smooth, flatten and paint 4 layers).", + "- **Start from a relief** (hills, mountains, island) or **from a heightmap image**.", + "- **Steep slopes automatically show rock** (or any layer).", + "- **Natural borders between layers**: stones and tufts of a texture cover the gaps of the other where layers meet (height blending), and the repetition of textures is made less visible (texture variation).", + "- **Physics**: add the **3D physics** behavior to the terrain. Characters and objects collide with the ground exactly. The terrain is always static.", + "- **Pathfinding**: add the **floor/obstacle for pathfinding (navmesh based)** behavior to the terrain, so that characters with the navmesh pathfinding behavior walk on its hills and around its cliffs (in recent GDevelop versions).", + "- **Shape it with the Edits property**: a list of raise, flatten, smooth and paint strokes written in JSON, shown in the scene editor. It's readable and can be changed by hand or by an AI agent, like the relief, the heightmap and the other properties. Actions only change the terrain during the game.", + "- **Change it during the game** with actions (raise, lower, flatten, smooth, paint, along a line for paths and rivers) and read the ground height, slope or layer with expressions.", + "- **Grass**: add a **terrain grass** object over the terrain. Thousands of blades sway in the wind, grow where a layer (like grass) is painted and where the ground is not too steep, and are pushed aside by characters. Sculpt or paint the terrain and the grass follows.", + "- **Forests, rocks and bushes**: add a **scattered models** object over the terrain. It places hundreds or thousands of copies of a 3D model (trees, rocks, bushes, flowers...) on the ground and draws them all at once, which is much faster than an object for each. Copies grow where a layer is painted, away from steep slopes, and follow the ground when it's sculpted. Add or erase them with the brushes of the scene editor or with the Edits property, give trees trunk collisions, and cut them down with actions. Ready-made forests, rocks and bushes are in the asset store.", + "- **Tunnels and caves**: make holes in the ground (and fill them again) with the brushes, the Edits property or actions, and put a 3D model of the tunnel or the cave (with a mesh collision shape) under the ground. Nothing is drawn in holes, and characters, cars and rays go through them.", + "- **Made for large worlds**: the terrain is split in chunks that are hidden when off-screen and simplified when far away, and collisions are updated only where the ground changes." + ], + "origin": { + "identifier": "Terrain3D", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d", + "terrain", + "ground", + "heightmap", + "landscape", + "open world", + "grass", + "foliage", + "forest", + "trees", + "tunnel", + "cave" + ], + "authorIds": [], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [ + { + "fullName": "Define helper classes", + "functionType": "Action", + "name": "DefineHelperClasses", + "sentence": "Define helper classes JavaScript code", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (gdjs.__terrain3DExtension) {", + " return;", + "}", + "", + "// Tested by the gameplay tests of the \"3D island terrain\" example (GDevelop-examples repository).", + "", + "const groundsByScene = new WeakMap();", + "/**", + " * The grounds of a scene (the terrains), followed by the terrain grass.", + " *", + " * A ground has: `getVersion()` (changing when the ground changes),", + " * `containsPoint(x, y)`, `getHeightAt(x, y)` (Z position of the ground),", + " * `getSlopeAt(x, y)` (in degrees), `getLayerWeightAt(x, y, layer)` (how", + " * much a painted layer, from 1 to 4, is there, from 0 to 1) and optionally", + " * `getTintAt(x, y)` (the painted tint, as red, green and blue from 0 to 255).", + " * @param {gdjs.RuntimeScene} runtimeScene", + " * @returns {Set}", + " */", + "const getGrounds = (runtimeScene) => {", + " let grounds = groundsByScene.get(runtimeScene);", + " if (!grounds) {", + " grounds = new Set();", + " groundsByScene.set(runtimeScene, grounds);", + " }", + " return grounds;", + "};", + "", + "/**", + " * @param {number} seed", + " * @returns {() => number} Random numbers between 0 (included) and 1 (excluded), the same for the same seed.", + " */", + "const createRandom = (seed) => {", + " let state = seed >>> 0 || 1;", + " return () => {", + " state ^= state << 13;", + " state ^= state >>> 17;", + " state ^= state << 5;", + " return (state >>> 0) / 4294967296;", + " };", + "};", + "", + "/**", + " * Find the highest ground at a position: grounds can overlap (like an island over a sea floor).", + " * @param {Iterable} grounds", + " * @param {{ground: object | null, z: number}} result Set to the ground (null if there is none) and its Z position.", + " */", + "const findHighestGround = (grounds, x, y, result) => {", + " result.ground = null;", + " for (const ground of grounds) {", + " if (!ground.containsPoint(x, y)) continue;", + " const z = ground.getHeightAt(x, y);", + " if (!result.ground || z > result.z) {", + " result.ground = ground;", + " result.z = z;", + " }", + " }", + " return result;", + "};", + "", + "// The grounds or the objects placed on them can change every frame (while", + "// sculpting or moving them in the editor): things are placed again at most this often.", + "const PLACEMENT_DELAY = 0.3;", + "", + "/**", + " * Tells when things placed on the grounds of a scene (grass blades, scattered", + " * models) must be placed again: after the grounds or the given properties", + " * changed, at most every `PLACEMENT_DELAY` seconds.", + " */", + "class GroundPlacement {", + " /** @param {gdjs.RuntimeScene} runtimeScene */", + " constructor(runtimeScene) {", + " this.grounds = getGrounds(runtimeScene);", + " this.placedFrom = '';", + " this.timeSincePlacement = 0;", + " }", + "", + " /**", + " * @param {number} elapsedSeconds", + " * @param {string} propertiesKey Changes when the things must be placed again.", + " */", + " shouldPlaceAgain(elapsedSeconds, propertiesKey) {", + " this.timeSincePlacement += elapsedSeconds;", + " if (this.placedFrom && this.timeSincePlacement < PLACEMENT_DELAY) return false;", + " let placementKey = propertiesKey;", + " for (const ground of this.grounds) placementKey += '|' + ground.getVersion();", + " if (placementKey === this.placedFrom) return false;", + " this.placedFrom = placementKey;", + " this.timeSincePlacement = 0;", + " return true;", + " }", + "", + " placeAgain() {", + " this.placedFrom = '';", + " }", + "}", + "", + "/**", + " * Replace parts of the shader code of a Three.js material, with an error if", + " * a part is missing (for example after an update of Three.js).", + " * @param {string} shaderCode", + " * @param {Array<[string, string]>} replacements The searched code and its replacement.", + " * @param {string} materialName", + " */", + "const patchShaderCode = (shaderCode, replacements, materialName) => {", + " for (const [searchedCode, newCode] of replacements) {", + " if (!shaderCode.includes(searchedCode)) {", + " console.error(`${materialName}: \"${searchedCode}\" was not found in the shader, which won't look as expected.`);", + " }", + " shaderCode = shaderCode.replace(searchedCode, newCode);", + " }", + " return shaderCode;", + "};", + "", + "// Size of the object inner area. Instances are scaled from it, so these only", + "// define the default size of a new terrain.", + "const AREA_SIZE = 4096;", + "const AREA_DEPTH = 600;", + "", + "// Terrains have at most 8 x 8 chunks (so at most 64 draw calls), of at least", + "// 32 x 32 cells.", + "const MIN_CHUNK_CELLS = 32;", + "const MAX_CHUNKS_PER_SIDE = 8;", + "/** @param {number} resolution */", + "const getChunkCells = (resolution) => Math.max(MIN_CHUNK_CELLS, resolution / MAX_CHUNKS_PER_SIDE);", + "// Grid steps of each level of detail, and the distance (in chunk sizes)", + "// from which each one is used.", + "const LOD_STEPS = [1, 2, 4];", + "const LOD_DISTANCES = [0, 3, 6];", + "", + "const LAYER_COUNT = 4;", + "const RESOLUTIONS = [64, 128, 256, 512, 1024];", + "const DEFAULT_RESOLUTION = 256;", + "// Each undo step is a copy of the data: fewer are kept for big terrains.", + "const UNDO_STEPS_SAMPLES = 20 * 257 * 257;", + "", + "const clamp01 = (value) => (value < 0 ? 0 : value > 1 ? 1 : value);", + "const clampInteger = (value, min, max) =>", + " value < min ? min : value > max ? max : Math.floor(value);", + "", + "/**", + " * A rectangle of grid samples, inclusive. Empty when `minI > maxI`.", + " */", + "class SampleRectangle {", + " constructor() {", + " this.clear();", + " }", + "", + " clear() {", + " this.minI = Number.MAX_SAFE_INTEGER;", + " this.minJ = Number.MAX_SAFE_INTEGER;", + " this.maxI = -1;", + " this.maxJ = -1;", + " }", + "", + " isEmpty() {", + " return this.minI > this.maxI;", + " }", + "", + " /** @param {SampleRectangle} other */", + " add(other) {", + " if (other.isEmpty()) return;", + " this.minI = Math.min(this.minI, other.minI);", + " this.minJ = Math.min(this.minJ, other.minJ);", + " this.maxI = Math.max(this.maxI, other.maxI);", + " this.maxJ = Math.max(this.maxJ, other.maxJ);", + " }", + "", + " set(minI, minJ, maxI, maxJ) {", + " this.minI = minI;", + " this.minJ = minJ;", + " this.maxI = maxI;", + " this.maxJ = maxJ;", + " return this;", + " }", + "}", + "", + "/**", + " * The heights (between 0 and 1) and the painted layers weights of a square", + " * grid of `size * size` samples.", + " */", + "class TerrainData {", + " /** @param {number} resolution The number of cells on each side. */", + " constructor(resolution) {", + " this.resolution = resolution;", + " this.size = resolution + 1;", + " this.heights = new Float32Array(this.size * this.size);", + " /** 4 weights (0-255) per sample, one for each layer. */", + " this.splat = new Uint8Array(this.size * this.size * LAYER_COUNT);", + " this.fillLayer(0);", + " /**", + " * 4 bytes per sample: a tint multiplying the layers (red, green and", + " * blue, white for none) and the wetness (0 for dry).", + " */", + " this.colors = new Uint8Array(this.size * this.size * 4);", + " this.clearColors();", + " }", + "", + " clearColors() {", + " this.colors.fill(255);", + " for (let index = 3; index < this.colors.length; index += 4) {", + " this.colors[index] = 0;", + " }", + " }", + "", + " /** @param {number} layer */", + " fillLayer(layer) {", + " this.splat.fill(0);", + " for (let index = layer; index < this.splat.length; index += LAYER_COUNT) {", + " this.splat[index] = 255;", + " }", + " }", + "", + " getHeight(i, j) {", + " const last = this.resolution;", + " const clampedI = i < 0 ? 0 : i > last ? last : i;", + " const clampedJ = j < 0 ? 0 : j > last ? last : j;", + " return this.heights[clampedJ * this.size + clampedI];", + " }", + "", + " /**", + " * @param {number} gridX", + " * @param {number} gridY", + " * @returns {number} The height interpolated on the triangles that are drawn.", + " */", + " sampleHeight(gridX, gridY) {", + " const x = Math.min(Math.max(gridX, 0), this.resolution);", + " const y = Math.min(Math.max(gridY, 0), this.resolution);", + " const i = Math.min(Math.floor(x), this.resolution - 1);", + " const j = Math.min(Math.floor(y), this.resolution - 1);", + " const fx = x - i;", + " const fy = y - j;", + " const topRight = this.getHeight(i + 1, j);", + " const bottomLeft = this.getHeight(i, j + 1);", + " if (fx + fy <= 1) {", + " const topLeft = this.getHeight(i, j);", + " return topLeft + fx * (topRight - topLeft) + fy * (bottomLeft - topLeft);", + " }", + " const bottomRight = this.getHeight(i + 1, j + 1);", + " return (", + " bottomRight +", + " (1 - fx) * (bottomLeft - bottomRight) +", + " (1 - fy) * (topRight - bottomRight)", + " );", + " }", + "", + " /** @returns {number} The weight (0-1) of a layer, interpolated between samples. */", + " sampleLayerWeight(gridX, gridY, layer) {", + " const x = Math.min(Math.max(gridX, 0), this.resolution);", + " const y = Math.min(Math.max(gridY, 0), this.resolution);", + " const i = Math.min(Math.floor(x), this.resolution - 1);", + " const j = Math.min(Math.floor(y), this.resolution - 1);", + " const fx = x - i;", + " const fy = y - j;", + " const weight = (i, j) => this.splat[(j * this.size + i) * LAYER_COUNT + layer];", + " const top = weight(i, j) + fx * (weight(i + 1, j) - weight(i, j));", + " const bottom = weight(i, j + 1) + fx * (weight(i + 1, j + 1) - weight(i, j + 1));", + " return (top + fy * (bottom - top)) / 255;", + " }", + "", + " /**", + " * @param {number} channel 0, 1 and 2 for the tint, 3 for the wetness.", + " * @returns {number} The channel (0-1), interpolated between samples.", + " */", + " sampleColor(gridX, gridY, channel) {", + " const x = Math.min(Math.max(gridX, 0), this.resolution);", + " const y = Math.min(Math.max(gridY, 0), this.resolution);", + " const i = Math.min(Math.floor(x), this.resolution - 1);", + " const j = Math.min(Math.floor(y), this.resolution - 1);", + " const fx = x - i;", + " const fy = y - j;", + " const value = (i, j) => this.colors[(j * this.size + i) * 4 + channel];", + " const top = value(i, j) + fx * (value(i + 1, j) - value(i, j));", + " const bottom = value(i, j + 1) + fx * (value(i + 1, j + 1) - value(i, j + 1));", + " return (top + fy * (bottom - top)) / 255;", + " }", + "", + " /**", + " * @param {number} index The index of a sample.", + " * @returns {boolean} true if no layer is painted on the sample: it's a hole.", + " */", + " isHole(index) {", + " const offset = index * LAYER_COUNT;", + " return (this.splat[offset] | this.splat[offset + 1] | this.splat[offset + 2] | this.splat[offset + 3]) === 0;", + " }", + "", + " /**", + " * @returns {number} The sum of the layer weights (0-1), interpolated between samples: there is a", + " * hole where it's under 0.5, like what is drawn.", + " */", + " sampleSolidity(gridX, gridY) {", + " let solidity = 0;", + " for (let layer = 0; layer < LAYER_COUNT; layer++) solidity += this.sampleLayerWeight(gridX, gridY, layer);", + " return solidity;", + " }", + "", + " /** @returns {number} The index (from 0) of the layer painted the most. */", + " getDominantLayer(gridX, gridY) {", + " const i = clampInteger(Math.round(gridX), 0, this.resolution);", + " const j = clampInteger(Math.round(gridY), 0, this.resolution);", + " const offset = (j * this.size + i) * LAYER_COUNT;", + " let dominantLayer = 0;", + " for (let layer = 1; layer < LAYER_COUNT; layer++) {", + " if (this.splat[offset + layer] > this.splat[offset + dominantLayer]) {", + " dominantLayer = layer;", + " }", + " }", + " return dominantLayer;", + " }", + "", + " /** @param {TerrainData} other */", + " copyFrom(other) {", + " this.heights.set(other.heights);", + " this.splat.set(other.splat);", + " this.colors.set(other.colors);", + " }", + "", + " /**", + " * @param {number} resolution", + " * @returns {TerrainData} A copy of this data with another resolution.", + " */", + " resampled(resolution) {", + " const data = new TerrainData(resolution);", + " const ratio = this.resolution / resolution;", + " for (let j = 0; j < data.size; j++) {", + " for (let i = 0; i < data.size; i++) {", + " const index = j * data.size + i;", + " data.heights[index] = this.sampleHeight(i * ratio, j * ratio);", + " const sourceOffset =", + " (Math.round(j * ratio) * this.size + Math.round(i * ratio)) * LAYER_COUNT;", + " for (let layer = 0; layer < LAYER_COUNT; layer++) {", + " data.splat[index * LAYER_COUNT + layer] = this.splat[sourceOffset + layer];", + " }", + " for (let channel = 0; channel < 4; channel++) {", + " data.colors[index * 4 + channel] = this.colors[sourceOffset + channel];", + " }", + " }", + " }", + " return data;", + " }", + "}", + "", + "/**", + " * Brushes change the samples around a segment, with an effect going smoothly", + " * from full on the segment to none at `radius`. A point is a segment of", + " * length 0. Positions are in scene units relative to the terrain origin.", + " */", + "const brushes = {", + " /**", + " * @param {TerrainData} data", + " * @param {{cellWidth: number, cellHeight: number, ax: number, ay: number, bx: number, by: number, radius: number}} stroke", + " * @param {(sampleIndex: number, weight: number, t: number) => void} apply Called with the weight of", + " * the brush and the position (from 0 to 1) of the nearest point of the segment.", + " * @param {SampleRectangle} changedSamples", + " */", + " forEachSample(data, stroke, apply, changedSamples) {", + " const { cellWidth, cellHeight, ax, ay, bx, by, radius } = stroke;", + " const last = data.resolution;", + " const minI = clampInteger(Math.floor((Math.min(ax, bx) - radius) / cellWidth), 0, last);", + " const maxI = clampInteger(Math.ceil((Math.max(ax, bx) + radius) / cellWidth), 0, last);", + " const minJ = clampInteger(Math.floor((Math.min(ay, by) - radius) / cellHeight), 0, last);", + " const maxJ = clampInteger(Math.ceil((Math.max(ay, by) + radius) / cellHeight), 0, last);", + " changedSamples.set(minI, minJ, maxI, maxJ);", + " if (radius <= 0) return;", + "", + " const abX = bx - ax;", + " const abY = by - ay;", + " const abLengthSquared = abX * abX + abY * abY;", + " const radiusSquared = radius * radius;", + " for (let j = minJ; j <= maxJ; j++) {", + " const y = j * cellHeight;", + " for (let i = minI; i <= maxI; i++) {", + " const x = i * cellWidth;", + " const t =", + " abLengthSquared > 0", + " ? clamp01(((x - ax) * abX + (y - ay) * abY) / abLengthSquared)", + " : 0;", + " const dx = x - (ax + t * abX);", + " const dy = y - (ay + t * abY);", + " const distanceSquared = dx * dx + dy * dy;", + " if (distanceSquared >= radiusSquared) continue;", + " const falloff = 1 - distanceSquared / radiusSquared;", + " apply(j * data.size + i, falloff * falloff, t);", + " }", + " }", + " },", + "", + " raise(data, stroke, amount, changedSamples) {", + " const { heights } = data;", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index, weight) => {", + " heights[index] = clamp01(heights[index] + amount * weight);", + " },", + " changedSamples", + " );", + " },", + "", + " /**", + " * @param {number} targetHeight", + " * @param {number} [endTargetHeight] The target at the end of the segment, for a slope (like a road", + " * going up). The same target as at its start by default.", + " */", + " flatten(data, stroke, targetHeight, strength, changedSamples, endTargetHeight = targetHeight) {", + " const { heights } = data;", + " const startTarget = clamp01(targetHeight);", + " const endTarget = clamp01(endTargetHeight);", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index, weight, t) => {", + " const target = startTarget + (endTarget - startTarget) * t;", + " heights[index] += (target - heights[index]) * clamp01(weight * strength);", + " },", + " changedSamples", + " );", + " },", + "", + " smooth(data, stroke, strength, changedSamples) {", + " const { heights, size } = data;", + " const changes = [];", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index, weight) => {", + " const i = index % size;", + " const j = (index - i) / size;", + " let sum = 0;", + " for (let dj = -1; dj <= 1; dj++) {", + " for (let di = -1; di <= 1; di++) {", + " sum += data.getHeight(i + di, j + dj);", + " }", + " }", + " changes.push(index, heights[index] + (sum / 9 - heights[index]) * clamp01(weight * strength));", + " },", + " changedSamples", + " );", + " // Written after all averages are computed, so that the result", + " // doesn't depend on the order in which samples are visited.", + " for (let index = 0; index < changes.length; index += 2) {", + " heights[changes[index]] = changes[index + 1];", + " }", + " },", + "", + " paint(data, stroke, layer, strength, changedSamples) {", + " const { splat } = data;", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index, weight) => {", + " const amount = clamp01(weight * strength);", + " const offset = index * LAYER_COUNT;", + " for (let otherLayer = 0; otherLayer < LAYER_COUNT; otherLayer++) {", + " const target = otherLayer === layer ? 255 : 0;", + " const value = splat[offset + otherLayer];", + " // Rounded towards the target, so that light strokes still paint.", + " const change = (target - value) * amount;", + " splat[offset + otherLayer] = value + (change > 0 ? Math.ceil(change) : Math.floor(change));", + " }", + " },", + " changedSamples", + " );", + " },", + "", + " /** Remove all the layers, making a hole (`fill` or painting a layer fills it again). */", + " hole(data, stroke, changedSamples) {", + " const { splat } = data;", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index) => {", + " splat.fill(0, index * LAYER_COUNT, (index + 1) * LAYER_COUNT);", + " },", + " changedSamples", + " );", + " },", + "", + " /**", + " * Fill the holes in the radius with the layers of the ground around them:", + " * each pass fills the holes next to the ground with the average of their", + " * neighbors, from the edges of the holes inward.", + " */", + " fill(data, stroke, changedSamples) {", + " const { splat, size, resolution } = data;", + " /** @type {number[]} */", + " let holes = [];", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index) => {", + " if (data.isHole(index)) holes.push(index);", + " },", + " changedSamples", + " );", + " const filledWeights = new Float32Array(holes.length * LAYER_COUNT);", + " while (holes.length > 0) {", + " /** @type {number[]} */", + " const remainingHoles = [];", + " /** @type {number[]} */", + " const filledHoles = [];", + " for (const index of holes) {", + " const i = index % size;", + " const j = (index - i) / size;", + " const weights = filledWeights.subarray(filledHoles.length * LAYER_COUNT, (filledHoles.length + 1) * LAYER_COUNT);", + " weights.fill(0);", + " for (let neighborJ = Math.max(j - 1, 0); neighborJ <= Math.min(j + 1, resolution); neighborJ++) {", + " for (let neighborI = Math.max(i - 1, 0); neighborI <= Math.min(i + 1, resolution); neighborI++) {", + " const neighbor = neighborJ * size + neighborI;", + " if (data.isHole(neighbor)) continue;", + " for (let layer = 0; layer < LAYER_COUNT; layer++) weights[layer] += splat[neighbor * LAYER_COUNT + layer];", + " }", + " }", + " if (weights[0] + weights[1] + weights[2] + weights[3] > 0) filledHoles.push(index);", + " else remainingHoles.push(index);", + " }", + " if (filledHoles.length === 0) {", + " // No ground around (a hole bigger than the brush): the first layer is used.", + " for (const index of remainingHoles) splat[index * LAYER_COUNT] = 255;", + " break;", + " }", + " // Filled after the pass, so that the holes are filled from the edges inward.", + " filledHoles.forEach((index, filledIndex) => {", + " const weights = filledWeights.subarray(filledIndex * LAYER_COUNT, (filledIndex + 1) * LAYER_COUNT);", + " const total = weights[0] + weights[1] + weights[2] + weights[3];", + " for (let layer = 0; layer < LAYER_COUNT; layer++) {", + " splat[index * LAYER_COUNT + layer] = Math.round((weights[layer] / total) * 255);", + " }", + " });", + " holes = remainingHoles;", + " }", + " },", + "", + " /** @param {number[]} color The red, green and blue (0-255) of the tint: white removes it. */", + " tint(data, stroke, color, strength, changedSamples) {", + " brushes.paintColors(data, stroke, 0, color, strength, changedSamples);", + " },", + "", + " /** @param {number} wetness From 0 (dry) to 1. */", + " wet(data, stroke, wetness, strength, changedSamples) {", + " brushes.paintColors(data, stroke, 3, [Math.round(clamp01(wetness) * 255)], strength, changedSamples);", + " },", + "", + " /**", + " * Bring channels of the colors (see `TerrainData.colors`) towards targets.", + " * @param {number} firstChannel", + " * @param {number[]} targets From 0 to 255, for the channels from the first one.", + " */", + " paintColors(data, stroke, firstChannel, targets, strength, changedSamples) {", + " const { colors } = data;", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index, weight) => {", + " const amount = clamp01(weight * strength);", + " for (let channel = 0; channel < targets.length; channel++) {", + " const offset = index * 4 + firstChannel + channel;", + " // Rounded towards the target, so that light strokes still paint.", + " const change = (targets[channel] - colors[offset]) * amount;", + " colors[offset] += change > 0 ? Math.ceil(change) : Math.floor(change);", + " }", + " },", + " changedSamples", + " );", + " },", + "};", + "", + "const relief = (() => {", + " const hash = (x, y, seed) => {", + " let h = Math.imul(x, 374761393) + Math.imul(y, 668265263) + Math.imul(seed, 982451653);", + " h = Math.imul(h ^ (h >>> 13), 1274126177);", + " return ((h ^ (h >>> 16)) >>> 0) / 4294967296;", + " };", + " const valueNoise = (x, y, seed) => {", + " const x0 = Math.floor(x);", + " const y0 = Math.floor(y);", + " const fx = x - x0;", + " const fy = y - y0;", + " const sx = fx * fx * (3 - 2 * fx);", + " const sy = fy * fy * (3 - 2 * fy);", + " const top = hash(x0, y0, seed) + sx * (hash(x0 + 1, y0, seed) - hash(x0, y0, seed));", + " const bottom =", + " hash(x0, y0 + 1, seed) + sx * (hash(x0 + 1, y0 + 1, seed) - hash(x0, y0 + 1, seed));", + " return top + sy * (bottom - top);", + " };", + " /** Fractal noise between 0 and 1. */", + " const fractalNoise = (x, y, seed) => {", + " let value = 0;", + " let amplitude = 0.5;", + " let frequency = 1;", + " for (let octave = 0; octave < 5; octave++) {", + " value += amplitude * valueNoise(x * frequency, y * frequency, seed + octave);", + " amplitude *= 0.5;", + " frequency *= 2;", + " }", + " return value / 0.96875;", + " };", + " const smoothstep = (edge0, edge1, x) => {", + " const t = clamp01((x - edge0) / (edge1 - edge0));", + " return t * t * (3 - 2 * t);", + " };", + " const reliefHeights = {", + " Flat: () => 0,", + " Hills: (u, v, seed) => 0.05 + 0.5 * Math.pow(fractalNoise(u * 3, v * 3, seed), 2),", + " Mountains: (u, v, seed) => {", + " const ridge = 1 - Math.abs(2 * fractalNoise(u * 2.5, v * 2.5, seed) - 1);", + " return 0.9 * Math.pow(ridge, 2.5);", + " },", + " Island: (u, v, seed) => {", + " const distanceToCenter = 2 * Math.hypot(u - 0.5, v - 0.5);", + " const coast = 0.2 * (fractalNoise(u * 4, v * 4, seed + 7) - 0.5);", + " const land = smoothstep(0.95, 0.55, distanceToCenter + coast);", + " return land * (0.12 + 0.6 * Math.pow(fractalNoise(u * 3, v * 3, seed), 1.5));", + " },", + " };", + " const beachLayer = 3;", + "", + " return {", + " /**", + " * @param {TerrainData} data", + " * @param {string} reliefName Flat, Hills, Mountains or Island.", + " * @param {number} seed", + " */", + " generate(data, reliefName, seed) {", + " const getHeight = reliefHeights[reliefName] || reliefHeights.Flat;", + " const integerSeed = Math.floor(seed) | 0;", + " data.fillLayer(0);", + " for (let j = 0; j < data.size; j++) {", + " for (let i = 0; i < data.size; i++) {", + " const index = j * data.size + i;", + " const height = clamp01(", + " getHeight(i / data.resolution, j / data.resolution, integerSeed)", + " );", + " data.heights[index] = height;", + " if (reliefName === 'Island') {", + " const sand = Math.round(255 * smoothstep(0.16, 0.1, height));", + " const offset = index * LAYER_COUNT;", + " data.splat[offset] = 255 - sand;", + " data.splat[offset + beachLayer] = sand;", + " }", + " }", + " }", + " },", + " };", + "})();", + "", + "/**", + " * Edits written in the Edits property: a JSON list of brush strokes, like", + " * `[{\"tool\": \"raise\", \"x\": 0.5, \"y\": 0.5, \"radius\": 0.2, \"height\": 0.3}]`,", + " * applied on the relief or the heightmap. Positions and radius are fractions", + " * of the terrain size, heights and Z positions fractions of its depth: they", + " * are the same for all instances, whatever their size. They are made to be", + " * read and written by people and by AI agents.", + " */", + "const edits = {", + " /**", + " * @param {TerrainData} data", + " * @param {string} text", + " */", + " apply(data, text) {", + " if (!text.trim()) return;", + " let editList;", + " try {", + " editList = JSON.parse(text);", + " } catch (error) {", + " console.warn('The edits of a terrain are not valid JSON: ' + error.message);", + " return;", + " }", + " if (!Array.isArray(editList)) {", + " console.warn('The edits of a terrain must be a JSON list (like [{\"tool\": \"raise\", ...}]).');", + " return;", + " }", + " const numberOr = (value, defaultValue) => (typeof value === 'number' && isFinite(value) ? value : defaultValue);", + " const changedSamples = new SampleRectangle();", + " for (const edit of editList) {", + " if (!edit || typeof edit !== 'object') continue;", + " const x = numberOr(edit.x, 0.5);", + " const y = numberOr(edit.y, 0.5);", + " const stroke = {", + " cellWidth: 1 / data.resolution,", + " cellHeight: 1 / data.resolution,", + " ax: x,", + " ay: y,", + " bx: numberOr(edit.toX, x),", + " by: numberOr(edit.toY, y),", + " radius: numberOr(edit.radius, 0.1),", + " };", + " const strength = clamp01(numberOr(edit.strength, 1));", + " if (edit.tool === 'raise') {", + " brushes.raise(data, stroke, numberOr(edit.height, 0.1), changedSamples);", + " } else if (edit.tool === 'flatten') {", + " const z = numberOr(edit.z, 0);", + " brushes.flatten(data, stroke, z, strength, changedSamples, numberOr(edit.toZ, z));", + " } else if (edit.tool === 'smooth') {", + " // Smoothing the same fraction of the terrain at any resolution.", + " const passes = Math.max(Math.round(((1 + 9 * strength) * data.resolution) / 256), 1);", + " for (let pass = 0; pass < passes; pass++) brushes.smooth(data, stroke, 1, changedSamples);", + " } else if (edit.tool === 'paint') {", + " const layer = clampInteger(numberOr(edit.layer, 1) - 1, 0, LAYER_COUNT - 1);", + " brushes.paint(data, stroke, layer, strength, changedSamples);", + " } else if (edit.tool === 'tint') {", + " const color = gdjs.rgbOrHexToRGBColor(typeof edit.color === 'string' ? edit.color : '255;255;255');", + " brushes.tint(data, stroke, color, strength, changedSamples);", + " } else if (edit.tool === 'wetness') {", + " brushes.wet(data, stroke, numberOr(edit.amount, 1), strength, changedSamples);", + " } else if (edit.tool === 'hole') {", + " brushes.hole(data, stroke, changedSamples);", + " } else if (edit.tool === 'fill') {", + " brushes.fill(data, stroke, changedSamples);", + " } else {", + " console.warn('Unknown tool in the edits of a terrain: \"' + edit.tool + '\" (use raise, flatten, smooth, paint, tint, wetness, hole or fill).');", + " }", + " }", + " },", + "};", + "", + "/**", + " * @param {gdjs.RuntimeGame} game", + " * @param {string} imageResourceName", + " * @returns {CanvasImageSource | null} The image, or null if it's not loaded (yet).", + " */", + "const getLoadedImage = (game, imageResourceName) => {", + " const imageManager = game.getImageManager();", + " if (typeof imageManager.getImageSource === 'function') return imageManager.getImageSource(imageResourceName);", + " // Older GDevelop versions: a 192 x 192 placeholder is given for images that are not loaded.", + " const texture = imageManager.getPIXITexture(imageResourceName);", + " return texture && texture !== imageManager.getInvalidPIXITexture() ? texture.baseTexture.getDrawableSource() : null;", + "};", + "", + "/**", + " * Loads heights from an image: black is the lowest, white the highest.", + " * Heights have 16 bits of precision when the red channel holds the most", + " * significant byte and the green one the least significant (a gray image", + " * works as expected too).", + " * @param {TerrainData} data", + " * @param {gdjs.RuntimeGame} game", + " * @param {string} imageResourceName", + " * @returns {boolean} true if the image could be read.", + " */", + "const loadHeightsFromImage = (data, game, imageResourceName) => {", + " const source = getLoadedImage(game, imageResourceName);", + " if (!source) return false;", + "", + " const canvas = document.createElement('canvas');", + " canvas.width = source.width;", + " canvas.height = source.height;", + " const context = canvas.getContext('2d', { willReadFrequently: true });", + " if (!context) return false;", + " context.drawImage(source, 0, 0);", + " const pixels = context.getImageData(0, 0, source.width, source.height).data;", + " // Pixels are decoded before being interpolated, to keep the 16 bits.", + " const pixelHeight = (x, y) => {", + " const offset = (y * source.width + x) * 4;", + " return (256 * pixels[offset] + pixels[offset + 1]) / 65535;", + " };", + " for (let j = 0; j < data.size; j++) {", + " const y = (j / data.resolution) * (source.height - 1);", + " const y0 = Math.floor(y);", + " const y1 = Math.min(y0 + 1, source.height - 1);", + " for (let i = 0; i < data.size; i++) {", + " const x = (i / data.resolution) * (source.width - 1);", + " const x0 = Math.floor(x);", + " const x1 = Math.min(x0 + 1, source.width - 1);", + " const top = pixelHeight(x0, y0) + (x - x0) * (pixelHeight(x1, y0) - pixelHeight(x0, y0));", + " const bottom = pixelHeight(x0, y1) + (x - x0) * (pixelHeight(x1, y1) - pixelHeight(x0, y1));", + " data.heights[j * data.size + i] = top + (y - y0) * (bottom - top);", + " }", + " }", + " return true;", + "};", + "", + "/**", + " * Stores the data in a string. Only the differences with the base data (from", + " * the relief or the heightmap) are stored: heights on 16 bits, layer weights", + " * on 8 bits, then (since format 2) the tint and wetness on 8 bits. Each value is predicted from its neighbors and the", + " * prediction errors are written as variable length integers, with runs of", + " * zeros collapsed. Untouched or smoothly changed areas take almost no space.", + " */", + "const codec = (() => {", + " const formatVersion = '2';", + " // Format 1 has no tint and wetness.", + " const readableFormatVersions = ['1', '2'];", + "", + " class ByteWriter {", + " constructor() {", + " this.bytes = new Uint8Array(1024);", + " this.length = 0;", + " }", + " writeVarUint(value) {", + " if (this.length + 5 > this.bytes.length) {", + " const bytes = new Uint8Array(this.bytes.length * 2);", + " bytes.set(this.bytes);", + " this.bytes = bytes;", + " }", + " while (value >= 0x80) {", + " this.bytes[this.length++] = (value & 0x7f) | 0x80;", + " value >>>= 7;", + " }", + " this.bytes[this.length++] = value;", + " }", + " }", + "", + " class ByteReader {", + " /** @param {Uint8Array} bytes */", + " constructor(bytes) {", + " this.bytes = bytes;", + " this.position = 0;", + " }", + " readVarUint() {", + " let value = 0;", + " let shift = 0;", + " let byte;", + " do {", + " if (this.position >= this.bytes.length) throw new Error('Truncated terrain data.');", + " byte = this.bytes[this.position++];", + " value += (byte & 0x7f) * Math.pow(2, shift);", + " shift += 7;", + " } while (byte & 0x80);", + " return value;", + " }", + " }", + "", + " /** Predicts a value from the left, top and top-left ones. */", + " const predict = (values, size, index) => {", + " const i = index % size;", + " if (index < size) return i === 0 ? 0 : values[index - 1];", + " if (i === 0) return values[index - size];", + " return values[index - 1] + values[index - size] - values[index - size - 1];", + " };", + "", + " /**", + " * @param {ByteWriter} writer", + " * @param {Int32Array} values A grid of `size * size` values.", + " */", + " const writeGrid = (writer, values, size) => {", + " let zeroRun = 0;", + " for (let index = 0; index < values.length; index++) {", + " const error = values[index] - predict(values, size, index);", + " if (error === 0) {", + " zeroRun++;", + " continue;", + " }", + " if (zeroRun > 0) {", + " writer.writeVarUint(0);", + " writer.writeVarUint(zeroRun);", + " zeroRun = 0;", + " }", + " // Zigzag: small negative and positive numbers get small codes.", + " writer.writeVarUint(error > 0 ? error * 2 : -error * 2 - 1);", + " }", + " if (zeroRun > 0) {", + " writer.writeVarUint(0);", + " writer.writeVarUint(zeroRun);", + " }", + " };", + "", + " /** @param {ByteReader} reader */", + " const readGrid = (reader, size) => {", + " const values = new Int32Array(size * size);", + " let zeroRun = 0;", + " for (let index = 0; index < values.length; index++) {", + " let error = 0;", + " if (zeroRun > 0) {", + " zeroRun--;", + " } else {", + " const code = reader.readVarUint();", + " if (code === 0) {", + " zeroRun = reader.readVarUint() - 1;", + " } else {", + " error = code % 2 === 0 ? code / 2 : -(code + 1) / 2;", + " }", + " }", + " values[index] = predict(values, size, index) + error;", + " }", + " return values;", + " };", + "", + " const toBase64 = (bytes, length) => {", + " let binary = '';", + " for (let offset = 0; offset < length; offset += 0x8000) {", + " binary += String.fromCharCode.apply(null, bytes.subarray(offset, Math.min(offset + 0x8000, length)));", + " }", + " return btoa(binary);", + " };", + "", + " const fromBase64 = (base64) => {", + " const binary = atob(base64);", + " const bytes = new Uint8Array(binary.length);", + " for (let index = 0; index < binary.length; index++) {", + " bytes[index] = binary.charCodeAt(index);", + " }", + " return bytes;", + " };", + "", + " return {", + " /**", + " * @param {TerrainData} data", + " * @param {TerrainData} baseData The data without changes, with the same resolution.", + " */", + " encode(data, baseData) {", + " const writer = new ByteWriter();", + " const values = new Int32Array(data.heights.length);", + " for (let index = 0; index < values.length; index++) {", + " values[index] = Math.round((data.heights[index] - baseData.heights[index]) * 65535);", + " }", + " writeGrid(writer, values, data.size);", + " for (let layer = 0; layer < LAYER_COUNT; layer++) {", + " for (let index = 0; index < values.length; index++) {", + " const offset = index * LAYER_COUNT + layer;", + " values[index] = data.splat[offset] - baseData.splat[offset];", + " }", + " writeGrid(writer, values, data.size);", + " }", + " // Not painted, these grids are zeros: a few bytes each.", + " for (let channel = 0; channel < 4; channel++) {", + " for (let index = 0; index < values.length; index++) {", + " const offset = index * 4 + channel;", + " values[index] = data.colors[offset] - baseData.colors[offset];", + " }", + " writeGrid(writer, values, data.size);", + " }", + " return formatVersion + ';' + data.resolution + ';' + toBase64(writer.bytes, writer.length);", + " },", + "", + " /**", + " * Store grids of integers in a string, like the terrain data. Used by other objects (the scattered models).", + " * @param {Int32Array[]} grids Grids of `size * size` values.", + " * @param {number} size", + " */", + " encodeGrids(grids, size) {", + " const writer = new ByteWriter();", + " for (const grid of grids) writeGrid(writer, grid, size);", + " return toBase64(writer.bytes, writer.length);", + " },", + "", + " /**", + " * @param {string} base64 A string from `encodeGrids`.", + " * @param {number} size", + " * @param {number} count The number of grids.", + " * @returns {Int32Array[]} The grids (an error is thrown if the string is not valid).", + " */", + " decodeGrids(base64, size, count) {", + " const reader = new ByteReader(fromBase64(base64));", + " return Array.from({ length: count }, () => readGrid(reader, size));", + " },", + "", + " /**", + " * @param {string} text", + " * @param {(resolution: number) => TerrainData} createBaseData", + " * @returns {TerrainData | null} null if the text is empty or not valid.", + " */", + " decode(text, createBaseData) {", + " if (!text) return null;", + " const parts = text.split(';');", + " const resolution = parseInt(parts[1], 10);", + " if (readableFormatVersions.indexOf(parts[0]) === -1 || RESOLUTIONS.indexOf(resolution) === -1) {", + " return null;", + " }", + " try {", + " const reader = new ByteReader(fromBase64(parts[2]));", + " const data = createBaseData(resolution);", + " const heightChanges = readGrid(reader, data.size);", + " for (let index = 0; index < heightChanges.length; index++) {", + " data.heights[index] = clamp01(data.heights[index] + heightChanges[index] / 65535);", + " }", + " for (let layer = 0; layer < LAYER_COUNT; layer++) {", + " const weightChanges = readGrid(reader, data.size);", + " for (let index = 0; index < weightChanges.length; index++) {", + " const offset = index * LAYER_COUNT + layer;", + " // The base may have changed since the data was saved.", + " data.splat[offset] = Math.min(Math.max(data.splat[offset] + weightChanges[index], 0), 255);", + " }", + " }", + " if (parts[0] !== '1') {", + " for (let channel = 0; channel < 4; channel++) {", + " const colorChanges = readGrid(reader, data.size);", + " for (let index = 0; index < colorChanges.length; index++) {", + " const offset = index * 4 + channel;", + " data.colors[offset] = Math.min(Math.max(data.colors[offset] + colorChanges[index], 0), 255);", + " }", + " }", + " }", + " return data;", + " } catch (error) {", + " console.error('Terrain data could not be read:', error);", + " return null;", + " }", + " },", + " };", + "})();", + "", + "const lodIndices = new Map();", + "/**", + " * The triangles of a chunk, shared by all chunks of the same size. Chunk", + " * borders have a skirt going down, hiding the gaps between chunks drawn", + " * with different levels of detail.", + " * @param {number} chunkCells", + " * @param {number} step", + " */", + "const getLodIndex = (chunkCells, step) => {", + " const key = chunkCells + ';' + step;", + " const existingIndex = lodIndices.get(key);", + " if (existingIndex) return existingIndex;", + "", + " const sideVertices = chunkCells + 1;", + " const gridVertices = sideVertices * sideVertices;", + " const indices = [];", + " const grid = (i, j) => j * sideVertices + i;", + " for (let j = 0; j < chunkCells; j += step) {", + " for (let i = 0; i < chunkCells; i += step) {", + " const topLeft = grid(i, j);", + " const topRight = grid(i + step, j);", + " const bottomLeft = grid(i, j + step);", + " const bottomRight = grid(i + step, j + step);", + " indices.push(topLeft, topRight, bottomLeft, topRight, bottomRight, bottomLeft);", + " }", + " }", + " const borders = [", + " (k) => grid(k, 0),", + " (k) => grid(k, chunkCells),", + " (k) => grid(0, k),", + " (k) => grid(chunkCells, k),", + " ];", + " borders.forEach((borderVertex, border) => {", + " const skirtVertex = (k) => gridVertices + border * sideVertices + k;", + " for (let k = 0; k < chunkCells; k += step) {", + " const a = borderVertex(k);", + " const b = borderVertex(k + step);", + " const skirtA = skirtVertex(k);", + " const skirtB = skirtVertex(k + step);", + " // Both faces, as a skirt can be seen from either side.", + " indices.push(a, b, skirtB, a, skirtB, skirtA, a, skirtB, b, a, skirtA, skirtB);", + " }", + " });", + " const index = new THREE.BufferAttribute(new Uint16Array(indices), 1);", + " lodIndices.set(key, index);", + " return index;", + "};", + "", + "const vertexShaderDeclarations = `", + "varying vec2 vTerrainLocalXY;", + "varying float vTerrainLocalZ;", + "uniform float terrainWorldScale;", + "varying vec2 vTerrainWorldXY;", + "varying float vTerrainUp;", + "`;", + "const vertexShaderCode = `", + "vTerrainLocalXY = position.xy;", + "vTerrainLocalZ = position.z;", + "vTerrainWorldXY = (modelMatrix * vec4(transformed, 1.0)).xy * terrainWorldScale;", + "vTerrainUp = normalize(transpose(inverse(mat3(modelMatrix))) * objectNormal).z;", + "`;", + "const fragmentShaderDeclarations = `", + "uniform sampler2D terrainSplatMap;", + "uniform vec2 terrainSplatUvScaleAndOffset;", + "uniform vec3 terrainLayerColors[4];", + "uniform sampler2D terrainLayerMap0;", + "uniform sampler2D terrainLayerMap1;", + "uniform sampler2D terrainLayerMap2;", + "uniform sampler2D terrainLayerMap3;", + "uniform sampler2D terrainLayerNormalMap0;", + "uniform sampler2D terrainLayerNormalMap1;", + "uniform sampler2D terrainLayerNormalMap2;", + "uniform sampler2D terrainLayerNormalMap3;", + "uniform float terrainHasNormalMaps;", + "uniform float terrainNormalMapStrength;", + "uniform float terrainTextureSize;", + "uniform float terrainHeightBlending;", + "uniform float terrainTextureVariation;", + "uniform vec4 terrainCliffLayer;", + "uniform vec2 terrainCliffCosines;", + "uniform vec4 terrainBrush;", + "uniform sampler2D terrainColorMap;", + "uniform vec3 terrainWetShore;", + "varying vec2 vTerrainLocalXY;", + "varying float vTerrainLocalZ;", + "varying vec2 vTerrainWorldXY;", + "varying float vTerrainUp;", + "", + "float terrainHash(vec2 p) {", + " return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);", + "}", + "// The rotation of the copies of textures used to make repetition less visible.", + "const mat2 terrainCopyRotation = mat2(0.8, -0.6, 0.6, 0.8);", + "/** A bump of a normal map (from -1 to 1), mixed with its rotated copy in patches. */", + "vec3 terrainSampleNormal(sampler2D normalMap, vec2 uv, vec2 uv2, float patches) {", + " vec3 bump = texture2D(normalMap, uv).xyz * 2.0 - 1.0;", + " if (patches <= 0.0) return bump;", + " vec3 copyBump = texture2D(normalMap, uv2).xyz * 2.0 - 1.0;", + " // The bumps of the copy are rotated back with the inverse rotation.", + " copyBump.xy = transpose(terrainCopyRotation) * copyBump.xy;", + " return mix(bump, copyBump, patches);", + "}", + "float terrainNoise(vec2 p) {", + " vec2 i = floor(p);", + " vec2 f = fract(p);", + " vec2 u = f * f * (3.0 - 2.0 * f);", + " return mix(", + " mix(terrainHash(i), terrainHash(i + vec2(1.0, 0.0)), u.x),", + " mix(terrainHash(i + vec2(0.0, 1.0)), terrainHash(i + vec2(1.0, 1.0)), u.x),", + " u.y", + " );", + "}", + "`;", + "// Holes are samples without any painted layer: the interpolated sum of the", + "// weights gives smooth edges. Only compiled for terrains having holes, as", + "// discard makes drawing slower on some GPUs.", + "const holeShaderCode = `", + "#ifdef TERRAIN_HOLES", + "if (dot(texture2D(terrainSplatMap, vTerrainLocalXY * terrainSplatUvScaleAndOffset.x + terrainSplatUvScaleAndOffset.y), vec4(1.0)) < 0.5) discard;", + "#endif", + "`;", + "// For the shadows: the depth of holes is not drawn.", + "const holeDepthShaderDeclarations = `", + "uniform sampler2D terrainSplatMap;", + "uniform vec2 terrainSplatUvScaleAndOffset;", + "varying vec2 vTerrainLocalXY;", + "`;", + "", + "const fragmentShaderCode = holeShaderCode + `", + "// Borders between layers are made irregular and sharper, to look painted.", + "vec2 terrainNoisePosition = vTerrainWorldXY / 24.0;", + "vec4 terrainJitter = vec4(", + " terrainNoise(terrainNoisePosition),", + " terrainNoise(terrainNoisePosition + 19.1),", + " terrainNoise(terrainNoisePosition + 37.7),", + " terrainNoise(terrainNoisePosition + 53.3)", + ");", + "// Layer weights are in a texture, so that painted details stay the same", + "// whatever the level of detail of the ground.", + "vec2 terrainSplatUv = vTerrainLocalXY * terrainSplatUvScaleAndOffset.x + terrainSplatUvScaleAndOffset.y;", + "vec4 terrainWeights = texture2D(terrainSplatMap, terrainSplatUv) * (0.7 + 0.6 * terrainJitter);", + "terrainWeights = terrainWeights * terrainWeights;", + "terrainWeights /= max(dot(terrainWeights, vec4(1.0)), 0.00001);", + "float terrainCliff = 1.0 - smoothstep(terrainCliffCosines.x, terrainCliffCosines.y, vTerrainUp);", + "terrainWeights = mix(terrainWeights, terrainCliffLayer, terrainCliff * dot(terrainCliffLayer, vec4(1.0)));", + "", + "vec2 terrainUv = vTerrainWorldXY / terrainTextureSize;", + "vec3 terrainTexture0 = texture2D(terrainLayerMap0, terrainUv).rgb;", + "vec3 terrainTexture1 = texture2D(terrainLayerMap1, terrainUv).rgb;", + "vec3 terrainTexture2 = texture2D(terrainLayerMap2, terrainUv).rgb;", + "vec3 terrainTexture3 = texture2D(terrainLayerMap3, terrainUv).rgb;", + "", + "// Less visible repetition: in irregular patches, textures are replaced by a", + "// rotated and shifted copy of themselves, and colors slowly vary.", + "float terrainPatches = 0.0;", + "vec2 terrainUv2 = terrainUv;", + "float terrainColorVariation = 1.0;", + "if (terrainTextureVariation > 0.0) {", + " // Up to half of the ground is covered by the copies, each patch showing", + " // only one of them (mixing them would make them blurry).", + " float terrainPatchThreshold = 1.0 - 0.5 * terrainTextureVariation;", + " terrainPatches = smoothstep(terrainPatchThreshold - 0.05, terrainPatchThreshold + 0.05, terrainNoise(terrainUv * 0.23 + 7.3));", + " terrainUv2 = terrainCopyRotation * terrainUv + vec2(0.31, 0.73);", + " terrainTexture0 = mix(terrainTexture0, texture2D(terrainLayerMap0, terrainUv2).rgb, terrainPatches);", + " terrainTexture1 = mix(terrainTexture1, texture2D(terrainLayerMap1, terrainUv2).rgb, terrainPatches);", + " terrainTexture2 = mix(terrainTexture2, texture2D(terrainLayerMap2, terrainUv2).rgb, terrainPatches);", + " terrainTexture3 = mix(terrainTexture3, texture2D(terrainLayerMap3, terrainUv2).rgb, terrainPatches);", + " terrainColorVariation += terrainTextureVariation * 0.3 *", + " (terrainNoise(terrainUv * 0.11) + 0.5 * terrainNoise(terrainUv * 0.37 + 3.1) - 0.75);", + "}", + "", + "// Height-based blending: the brightness of a texture, compared to its", + "// average, is used as its height. Where layers meet, the high parts (stones,", + "// tufts) cover the low parts (sand, gaps) instead of fading into each other.", + "if (terrainHeightBlending > 0.0) {", + " const vec3 terrainLuminance = vec3(0.299, 0.587, 0.114);", + " vec4 terrainHeights = vec4(", + " dot(terrainTexture0 - textureLod(terrainLayerMap0, vec2(0.5), 16.0).rgb, terrainLuminance),", + " dot(terrainTexture1 - textureLod(terrainLayerMap1, vec2(0.5), 16.0).rgb, terrainLuminance),", + " dot(terrainTexture2 - textureLod(terrainLayerMap2, vec2(0.5), 16.0).rgb, terrainLuminance),", + " dot(terrainTexture3 - textureLod(terrainLayerMap3, vec2(0.5), 16.0).rgb, terrainLuminance)", + " );", + " vec4 terrainHeightWeights = terrainWeights + terrainHeights;", + " float terrainTop = max(max(terrainHeightWeights.x, terrainHeightWeights.y), max(terrainHeightWeights.z, terrainHeightWeights.w));", + " // Only painted layers (and the cliff layer) can show.", + " vec4 terrainHeightBlend = max(terrainHeightWeights - terrainTop + 0.1, 0.0) * step(0.004, terrainWeights);", + " terrainHeightBlend /= max(dot(terrainHeightBlend, vec4(1.0)), 0.00001);", + " terrainWeights = mix(terrainWeights, terrainHeightBlend, terrainHeightBlending);", + "}", + "", + "vec3 terrainColor = terrainColorVariation * (", + " terrainWeights.x * terrainLayerColors[0] * terrainTexture0 +", + " terrainWeights.y * terrainLayerColors[1] * terrainTexture1 +", + " terrainWeights.z * terrainLayerColors[2] * terrainTexture2 +", + " terrainWeights.w * terrainLayerColors[3] * terrainTexture3);", + "", + "// The painted tint multiplies all layers. Wet ground is darker (and", + "// shinier, see the roughness).", + "vec4 terrainTintAndWetness = texture2D(terrainColorMap, terrainSplatUv);", + "float terrainWetness = max(", + " terrainTintAndWetness.a,", + " terrainWetShore.z * (1.0 - smoothstep(terrainWetShore.x, terrainWetShore.x + terrainWetShore.y, vTerrainLocalZ))", + ");", + "terrainColor *= terrainTintAndWetness.rgb * (1.0 - 0.4 * terrainWetness);", + "diffuseColor.rgb *= terrainColor;", + "", + "if (terrainBrush.w > 0.5) {", + " float brushDistance = distance(vTerrainWorldXY, terrainBrush.xy);", + " float ringWidth = max(terrainBrush.z * 0.04, 1.5);", + " float ring = 1.0 - smoothstep(0.0, ringWidth, abs(brushDistance - terrainBrush.z));", + " float disc = brushDistance < terrainBrush.z ? 0.12 : 0.0;", + " diffuseColor.rgb = mix(diffuseColor.rgb, vec3(1.0), max(ring * 0.85, disc));", + "}", + "`;", + "", + "const fragmentShaderRoughnessCode = `", + "roughnessFactor = mix(roughnessFactor, 0.3, terrainWetness);", + "`;", + "", + "const fragmentShaderNormalCode = `", + "if (terrainHasNormalMaps > 0.5) {", + " vec3 terrainMapNormal =", + " terrainWeights.x * terrainSampleNormal(terrainLayerNormalMap0, terrainUv, terrainUv2, terrainPatches) +", + " terrainWeights.y * terrainSampleNormal(terrainLayerNormalMap1, terrainUv, terrainUv2, terrainPatches) +", + " terrainWeights.z * terrainSampleNormal(terrainLayerNormalMap2, terrainUv, terrainUv2, terrainPatches) +", + " terrainWeights.w * terrainSampleNormal(terrainLayerNormalMap3, terrainUv, terrainUv2, terrainPatches);", + " terrainMapNormal.xy *= terrainNormalMapStrength;", + " // The X and Y of textures follow the X and Y of the 3D world, projected on", + " // the ground (the camera space of the normal is used).", + " vec3 terrainTangent = (viewMatrix * vec4(1.0, 0.0, 0.0, 0.0)).xyz;", + " terrainTangent -= normal * dot(normal, terrainTangent);", + " // A ground facing the X axis (a rotated terrain) uses the Y axis instead.", + " if (dot(terrainTangent, terrainTangent) < 0.000001) {", + " terrainTangent = (viewMatrix * vec4(0.0, 1.0, 0.0, 0.0)).xyz;", + " terrainTangent -= normal * dot(normal, terrainTangent);", + " }", + " terrainTangent = normalize(terrainTangent);", + " vec3 terrainBitangent = cross(normal, terrainTangent);", + " normal = normalize(mat3(terrainTangent, terrainBitangent, normal) * terrainMapNormal);", + "}", + "`;", + "", + "let whiteTexture = null;", + "const getWhiteTexture = () => {", + " if (!whiteTexture) {", + " whiteTexture = new THREE.DataTexture(new Uint8Array([255, 255, 255, 255]), 1, 1);", + " whiteTexture.needsUpdate = true;", + " }", + " return whiteTexture;", + "};", + "", + "let flatNormalTexture = null;", + "const getFlatNormalTexture = () => {", + " if (!flatNormalTexture) {", + " flatNormalTexture = new THREE.DataTexture(new Uint8Array([128, 128, 255, 255]), 1, 1);", + " flatNormalTexture.needsUpdate = true;", + " }", + " return flatNormalTexture;", + "};", + "", + "const layerTextures = new WeakMap();", + "const layerNormalMaps = new WeakMap();", + "/**", + " * Textures of the image manager have no mipmaps: they would shimmer when", + " * repeated in the distance.", + " * @param {gdjs.RuntimeGame} game", + " * @param {string} resourceName", + " * @param {boolean} isNormalMap Normal maps are directions, not colors.", + " */", + "const getLayerTexture = (game, resourceName, isNormalMap) => {", + " if (!resourceName) return isNormalMap ? getFlatNormalTexture() : getWhiteTexture();", + " const imageTexture = game.getImageManager().getThreeTexture(resourceName);", + " const textures = isNormalMap ? layerNormalMaps : layerTextures;", + " let texture = textures.get(imageTexture);", + " if (!texture) {", + " texture = imageTexture.clone();", + " if (isNormalMap) texture.colorSpace = THREE.NoColorSpace;", + " texture.wrapS = THREE.RepeatWrapping;", + " texture.wrapT = THREE.RepeatWrapping;", + " texture.minFilter = THREE.LinearMipmapLinearFilter;", + " texture.generateMipmaps = true;", + " texture.anisotropy = 4;", + " texture.needsUpdate = true;", + " textures.set(imageTexture, texture);", + " }", + " return texture;", + "};", + "", + "/**", + " * A texture of 4 bytes per sample of a grid of `size * size` samples (layer", + " * weights, tint and wetness...), interpolated between samples.", + " * @param {Uint8Array} bytes", + " * @param {number} size", + " */", + "const createGridTexture = (bytes, size) => {", + " const texture = new THREE.DataTexture(bytes, size, size);", + " texture.magFilter = THREE.LinearFilter;", + " texture.minFilter = THREE.LinearFilter;", + " texture.userData.isUploaded = false;", + " texture.onUpdate = () => {", + " texture.userData.isUploaded = true;", + " };", + " return texture;", + "};", + "", + "/**", + " * Send the rows of the changed samples to the GPU, or the whole texture", + " * before its first upload (and in older GDevelop versions).", + " * @param {THREE.DataTexture} texture", + " * @param {SampleRectangle} samples", + " */", + "const uploadGridTextureRows = (texture, samples) => {", + " if (texture.userData.isUploaded && typeof texture.addUpdateRange === 'function') {", + " const size = texture.image.width;", + " for (let j = samples.minJ; j <= samples.maxJ; j++) {", + " texture.addUpdateRange((j * size + samples.minI) * 4, (samples.maxI - samples.minI + 1) * 4);", + " }", + " }", + " texture.needsUpdate = true;", + "};", + "", + "class TerrainRenderer {", + " /** @param {Terrain} terrain */", + " constructor(terrain) {", + " this.terrain = terrain;", + " this.group = new THREE.Group();", + " terrain.object.get3DRendererObject().add(this.group);", + " /** @type {Array<{mesh: THREE.Mesh, geometry: THREE.BufferGeometry, firstI: number, firstJ: number, center: THREE.Vector3, lod: number}>} */", + " this.chunks = [];", + " this.chunksPerSide = 0;", + " this.chunkCells = 0;", + " this.chunkSideVertices = 0;", + " this.chunkGridVertices = 0;", + " this.chunkVertices = 0;", + " /** @type {THREE.DataTexture | null} */", + " this.splatTexture = null;", + " /** @type {THREE.DataTexture | null} */", + " this.colorTexture = null;", + " this.uniforms = {", + " terrainSplatMap: { value: getWhiteTexture() },", + " terrainSplatUvScaleAndOffset: { value: new THREE.Vector2() },", + " terrainLayerColors: { value: [new THREE.Color(), new THREE.Color(), new THREE.Color(), new THREE.Color()] },", + " terrainLayerMap0: { value: getWhiteTexture() },", + " terrainLayerMap1: { value: getWhiteTexture() },", + " terrainLayerMap2: { value: getWhiteTexture() },", + " terrainLayerMap3: { value: getWhiteTexture() },", + " terrainLayerNormalMap0: { value: getFlatNormalTexture() },", + " terrainLayerNormalMap1: { value: getFlatNormalTexture() },", + " terrainLayerNormalMap2: { value: getFlatNormalTexture() },", + " terrainLayerNormalMap3: { value: getFlatNormalTexture() },", + " terrainHasNormalMaps: { value: 0 },", + " terrainNormalMapStrength: { value: 1 },", + " terrainTextureSize: { value: 256 },", + " terrainHeightBlending: { value: 0 },", + " terrainTextureVariation: { value: 0 },", + " terrainCliffLayer: { value: new THREE.Vector4() },", + " terrainCliffCosines: { value: new THREE.Vector2() },", + " terrainBrush: { value: new THREE.Vector4() },", + " terrainColorMap: { value: getWhiteTexture() },", + " // Z (in the terrain group) under which the ground is wet, fade height, and 1 if it's used.", + " terrainWetShore: { value: new THREE.Vector3() },", + " // Scene units by 3D world unit.", + " terrainWorldScale: { value: 1 },", + " };", + " this.material = new THREE.MeshStandardMaterial({ roughness: 1, metalness: 0 });", + " this.material.onBeforeCompile = (shader) => {", + " Object.assign(shader.uniforms, this.uniforms);", + " shader.vertexShader = patchShaderCode(shader.vertexShader, [", + " ['#include ', '#include \\n' + vertexShaderDeclarations],", + " ['#include ', '#include \\n' + vertexShaderCode],", + " ], 'Terrain3D');", + " shader.fragmentShader = patchShaderCode(shader.fragmentShader, [", + " ['#include ', '#include \\n' + fragmentShaderDeclarations],", + " ['#include ', fragmentShaderCode],", + " ['#include ', '#include \\n' + fragmentShaderRoughnessCode],", + " ['#include ', '#include \\n' + fragmentShaderNormalCode],", + " ], 'Terrain3D');", + " };", + " this.material.customProgramCacheKey = () => (this.hasHoles ? 'Terrain3DWithHoles' : 'Terrain3D');", + " this.hasHoles = false;", + " // Without them, holes would cast shadows.", + " this.depthMaterial = this._createHoleDepthMaterial(new THREE.MeshDepthMaterial());", + " this.distanceMaterial = this._createHoleDepthMaterial(new THREE.MeshDistanceMaterial());", + " this._cameraPosition = new THREE.Vector3();", + " this._chunkPosition = new THREE.Vector3();", + " this._scale = new THREE.Vector3();", + " }", + "", + " /**", + " * @param {THREE.MeshDepthMaterial | THREE.MeshDistanceMaterial} material", + " * @returns The material, drawing nothing in holes.", + " */", + " _createHoleDepthMaterial(material) {", + " material.defines = { TERRAIN_HOLES: '' };", + " material.onBeforeCompile = (shader) => {", + " Object.assign(shader.uniforms, this.uniforms);", + " shader.vertexShader = patchShaderCode(shader.vertexShader, [", + " ['#include ', '#include \\nvarying vec2 vTerrainLocalXY;'],", + " ['#include ', '#include \\nvTerrainLocalXY = position.xy;'],", + " ], 'Terrain3D shadows');", + " shader.fragmentShader = patchShaderCode(shader.fragmentShader, [", + " ['#include ', '#include \\n' + holeDepthShaderDeclarations],", + " ['#include ', holeShaderCode + '#include '],", + " ], 'Terrain3D shadows');", + " };", + " material.customProgramCacheKey = () => 'Terrain3DHoles';", + " return material;", + " }", + "", + " /**", + " * Use the shaders drawing holes, or the faster ones without them.", + " * @param {boolean} hasHoles", + " */", + " setHasHoles(hasHoles) {", + " if (hasHoles === this.hasHoles) return;", + " this.hasHoles = hasHoles;", + " // The material has its own defines (STANDARD).", + " if (hasHoles) this.material.defines.TERRAIN_HOLES = '';", + " else delete this.material.defines.TERRAIN_HOLES;", + " this.material.needsUpdate = true;", + " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", + " }", + "", + " /** @param {THREE.Mesh} mesh */", + " _setChunkShadowMaterials(mesh) {", + " mesh.customDepthMaterial = this.hasHoles ? this.depthMaterial : undefined;", + " mesh.customDistanceMaterial = this.hasHoles ? this.distanceMaterial : undefined;", + " }", + "", + " /** Recreate all the chunks, for example after the resolution changed. */", + " rebuild() {", + " this.disposeChunks();", + " const { data } = this.terrain;", + " this.chunkCells = getChunkCells(data.resolution);", + " this.chunkSideVertices = this.chunkCells + 1;", + " this.chunkGridVertices = this.chunkSideVertices * this.chunkSideVertices;", + " this.chunkVertices = this.chunkGridVertices + 4 * this.chunkSideVertices;", + " this.chunksPerSide = data.resolution / this.chunkCells;", + " const cellSize = AREA_SIZE / data.resolution;", + " // Sample centers are at the texel centers.", + " this.splatTexture = createGridTexture(data.splat, data.size);", + " this.uniforms.terrainSplatMap.value = this.splatTexture;", + " this.colorTexture = createGridTexture(data.colors, data.size);", + " // Tints are colors chosen like the layer colors.", + " this.colorTexture.colorSpace = THREE.SRGBColorSpace;", + " this.uniforms.terrainColorMap.value = this.colorTexture;", + " this.uniforms.terrainSplatUvScaleAndOffset.value.set(1 / (cellSize * data.size), 0.5 / data.size);", + " for (let chunkJ = 0; chunkJ < this.chunksPerSide; chunkJ++) {", + " for (let chunkI = 0; chunkI < this.chunksPerSide; chunkI++) {", + " const firstI = chunkI * this.chunkCells;", + " const firstJ = chunkJ * this.chunkCells;", + " const positions = new Float32Array(this.chunkVertices * 3);", + " for (let j = 0; j < this.chunkSideVertices; j++) {", + " for (let i = 0; i < this.chunkSideVertices; i++) {", + " const vertex = j * this.chunkSideVertices + i;", + " positions[vertex * 3] = (firstI + i) * cellSize;", + " positions[vertex * 3 + 1] = (firstJ + j) * cellSize;", + " }", + " }", + " // Skirt vertices are below border vertices.", + " this._forEachSkirtVertex((skirtVertex, borderVertex) => {", + " positions[skirtVertex * 3] = positions[borderVertex * 3];", + " positions[skirtVertex * 3 + 1] = positions[borderVertex * 3 + 1];", + " });", + " const geometry = new THREE.BufferGeometry();", + " geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));", + " geometry.setAttribute('normal', new THREE.BufferAttribute(new Float32Array(this.chunkVertices * 3), 3));", + " geometry.setIndex(getLodIndex(this.chunkCells, LOD_STEPS[0]));", + " geometry.boundingBox = new THREE.Box3();", + " geometry.boundingSphere = new THREE.Sphere();", + " const mesh = new THREE.Mesh(geometry, this.material);", + " mesh.matrixAutoUpdate = false;", + " // Rays hit the ground at its full resolution, whatever the level of", + " // detail drawn, and go through holes.", + " const chunkCells = this.chunkCells;", + " const terrainData = () => this.terrain.data;", + " const localPoint = new THREE.Vector3();", + " mesh.raycast = function (raycaster, intersects) {", + " const drawnIndex = geometry.index;", + " const firstIntersection = intersects.length;", + " geometry.setIndex(getLodIndex(chunkCells, LOD_STEPS[0]));", + " THREE.Mesh.prototype.raycast.call(this, raycaster, intersects);", + " geometry.setIndex(drawnIndex);", + " const data = terrainData();", + " const cellSize = AREA_SIZE / data.resolution;", + " for (let index = intersects.length - 1; index >= firstIntersection; index--) {", + " this.worldToLocal(localPoint.copy(intersects[index].point));", + " if (data.sampleSolidity(localPoint.x / cellSize, localPoint.y / cellSize) < 0.5) {", + " intersects.splice(index, 1);", + " }", + " }", + " };", + " this._setChunkShadowMaterials(mesh);", + " this.group.add(mesh);", + " this.chunks.push({", + " mesh,", + " geometry,", + " firstI,", + " firstJ,", + " center: new THREE.Vector3(", + " (firstI + this.chunkCells / 2) * cellSize,", + " (firstJ + this.chunkCells / 2) * cellSize,", + " 0", + " ),", + " lod: 0,", + " });", + " }", + " }", + " this.updateSamples(new SampleRectangle().set(0, 0, data.resolution, data.resolution), true);", + " this.updateMaterial();", + " }", + "", + " _forEachSkirtVertex(callback) {", + " for (let k = 0; k < this.chunkSideVertices; k++) {", + " callback(this.chunkGridVertices + k, k);", + " callback(this.chunkGridVertices + this.chunkSideVertices + k, this.chunkCells * this.chunkSideVertices + k);", + " callback(this.chunkGridVertices + 2 * this.chunkSideVertices + k, k * this.chunkSideVertices);", + " callback(", + " this.chunkGridVertices + 3 * this.chunkSideVertices + k,", + " k * this.chunkSideVertices + this.chunkCells", + " );", + " }", + " }", + "", + " /**", + " * Skirts go down only to the lowest point a coarser neighbor chunk can", + " * draw along the border, so that they can't be seen through holes.", + " * @param {Float32Array} positions", + " */", + " _updateSkirtHeights(positions) {", + " const coarsestStep = LOD_STEPS[LOD_STEPS.length - 1];", + " const borderVertices = [", + " (k) => k,", + " (k) => this.chunkCells * this.chunkSideVertices + k,", + " (k) => k * this.chunkSideVertices,", + " (k) => k * this.chunkSideVertices + this.chunkCells,", + " ];", + " borderVertices.forEach((borderVertex, border) => {", + " for (let k = 0; k < this.chunkSideVertices; k++) {", + " let minZ = Infinity;", + " const from = Math.max(k - coarsestStep, 0);", + " const to = Math.min(k + coarsestStep, this.chunkCells);", + " for (let other = from; other <= to; other++) {", + " minZ = Math.min(minZ, positions[borderVertex(other) * 3 + 2]);", + " }", + " const skirtVertex = this.chunkGridVertices + border * this.chunkSideVertices + k;", + " positions[skirtVertex * 3 + 2] = minZ;", + " }", + " });", + " }", + "", + " /**", + " * Update the vertices of the chunks sharing the given samples.", + " * @param {SampleRectangle} samples", + " * @param {boolean} hasPaintChanged", + " */", + " updateSamples(samples, hasPaintChanged) {", + " if (samples.isEmpty()) return;", + " const { data } = this.terrain;", + " const cellSize = AREA_SIZE / data.resolution;", + " const slopeFactor = AREA_DEPTH / cellSize;", + " // Normals of samples next to changed ones change too.", + " const minI = Math.max(samples.minI - 1, 0);", + " const minJ = Math.max(samples.minJ - 1, 0);", + " const maxI = Math.min(samples.maxI + 1, data.resolution);", + " const maxJ = Math.min(samples.maxJ + 1, data.resolution);", + " const firstChunkI = Math.max(Math.ceil(minI / this.chunkCells) - 1, 0);", + " const firstChunkJ = Math.max(Math.ceil(minJ / this.chunkCells) - 1, 0);", + " const lastChunkI = Math.min(Math.floor(maxI / this.chunkCells), this.chunksPerSide - 1);", + " const lastChunkJ = Math.min(Math.floor(maxJ / this.chunkCells), this.chunksPerSide - 1);", + "", + " for (let chunkJ = firstChunkJ; chunkJ <= lastChunkJ; chunkJ++) {", + " for (let chunkI = firstChunkI; chunkI <= lastChunkI; chunkI++) {", + " const chunk = this.chunks[chunkJ * this.chunksPerSide + chunkI];", + " const positions = chunk.geometry.attributes.position.array;", + " const normals = chunk.geometry.attributes.normal.array;", + " const fromI = Math.max(minI - chunk.firstI, 0);", + " const toI = Math.min(maxI - chunk.firstI, this.chunkCells);", + " const fromJ = Math.max(minJ - chunk.firstJ, 0);", + " const toJ = Math.min(maxJ - chunk.firstJ, this.chunkCells);", + " for (let j = fromJ; j <= toJ; j++) {", + " const gridJ = chunk.firstJ + j;", + " for (let i = fromI; i <= toI; i++) {", + " const gridI = chunk.firstI + i;", + " const vertex = j * this.chunkSideVertices + i;", + " const sample = gridJ * data.size + gridI;", + " positions[vertex * 3 + 2] = data.heights[sample] * AREA_DEPTH;", + "", + " const leftI = Math.max(gridI - 1, 0);", + " const rightI = Math.min(gridI + 1, data.resolution);", + " const upJ = Math.max(gridJ - 1, 0);", + " const downJ = Math.min(gridJ + 1, data.resolution);", + " const nx =", + " ((data.getHeight(leftI, gridJ) - data.getHeight(rightI, gridJ)) * slopeFactor) /", + " (rightI - leftI);", + " const ny =", + " ((data.getHeight(gridI, upJ) - data.getHeight(gridI, downJ)) * slopeFactor) /", + " (downJ - upJ);", + " const inverseLength = 1 / Math.sqrt(nx * nx + ny * ny + 1);", + " normals[vertex * 3] = nx * inverseLength;", + " normals[vertex * 3 + 1] = ny * inverseLength;", + " normals[vertex * 3 + 2] = inverseLength;", + " }", + " }", + " this._forEachSkirtVertex((skirtVertex, borderVertex) => {", + " for (let axis = 0; axis < 3; axis++) {", + " normals[skirtVertex * 3 + axis] = normals[borderVertex * 3 + axis];", + " }", + " });", + " this._updateSkirtHeights(positions);", + " let maxZ = 0;", + " for (let vertex = 0; vertex < this.chunkGridVertices; vertex++) {", + " maxZ = Math.max(maxZ, positions[vertex * 3 + 2]);", + " }", + " const chunkSize = this.chunkCells * cellSize;", + " chunk.geometry.boundingBox.min.set(chunk.firstI * cellSize, chunk.firstJ * cellSize, 0);", + " chunk.geometry.boundingBox.max.set(", + " chunk.firstI * cellSize + chunkSize,", + " chunk.firstJ * cellSize + chunkSize,", + " maxZ", + " );", + " chunk.geometry.boundingBox.getBoundingSphere(chunk.geometry.boundingSphere);", + " chunk.center.z = maxZ / 2;", + " chunk.geometry.attributes.position.needsUpdate = true;", + " chunk.geometry.attributes.normal.needsUpdate = true;", + " }", + " }", + " if (hasPaintChanged) {", + " // The data may have been replaced by one with the same resolution.", + " this.splatTexture.image.data = data.splat;", + " uploadGridTextureRows(this.splatTexture, samples);", + " this.colorTexture.image.data = data.colors;", + " uploadGridTextureRows(this.colorTexture, samples);", + " }", + " }", + "", + " updateMaterial() {", + " const { object } = this.terrain;", + " const game = object.getRuntimeScene().getGame();", + " const colors = [object._getLayer1Color(), object._getLayer2Color(), object._getLayer3Color(), object._getLayer4Color()];", + " const textures = [", + " object._getLayer1Texture(),", + " object._getLayer2Texture(),", + " object._getLayer3Texture(),", + " object._getLayer4Texture(),", + " ];", + " const normalMaps = [", + " object._getLayer1NormalMap(),", + " object._getLayer2NormalMap(),", + " object._getLayer3NormalMap(),", + " object._getLayer4NormalMap(),", + " ];", + " for (let layer = 0; layer < LAYER_COUNT; layer++) {", + " this.uniforms.terrainLayerColors.value[layer].set(gdjs.rgbOrHexStringToNumber(colors[layer]));", + " this.uniforms['terrainLayerMap' + layer].value = getLayerTexture(game, textures[layer], false);", + " this.uniforms['terrainLayerNormalMap' + layer].value = getLayerTexture(game, normalMaps[layer], true);", + " }", + " this.uniforms.terrainHasNormalMaps.value = normalMaps.some((normalMap) => !!normalMap) ? 1 : 0;", + " this.uniforms.terrainNormalMapStrength.value = Math.max(object._getNormalMapStrength(), 0);", + " this.uniforms.terrainTextureSize.value = Math.max(object._getTextureSize(), 1);", + " this.uniforms.terrainHeightBlending.value = clamp01(object._getHeightBlending());", + " this.uniforms.terrainTextureVariation.value = clamp01(object._getTextureVariation());", + "", + " const cliffLayer = parseInt(object._getCliffLayer(), 10);", + " const cliffAngle = gdjs.toRad(object._getCliffAngle());", + " const cliffBlendAngle = gdjs.toRad(4);", + " this.uniforms.terrainCliffLayer.value.set(", + " cliffLayer === 1 ? 1 : 0,", + " cliffLayer === 2 ? 1 : 0,", + " cliffLayer === 3 ? 1 : 0,", + " cliffLayer === 4 ? 1 : 0", + " );", + " this.uniforms.terrainCliffCosines.value.set(", + " Math.cos(cliffAngle + cliffBlendAngle),", + " Math.cos(cliffAngle - cliffBlendAngle)", + " );", + "", + " const castShadow = object._getCastShadow();", + " const receiveShadow = object._getReceiveShadow();", + " for (const chunk of this.chunks) {", + " chunk.mesh.castShadow = castShadow;", + " chunk.mesh.receiveShadow = receiveShadow;", + " }", + " }", + "", + " /**", + " * Show the brush circle of the editor.", + " * @param {THREE.Vector3 | null} localPosition", + " * @param {number} radius In scene units.", + " */", + " setBrush(localPosition, radius) {", + " const brush = this.uniforms.terrainBrush.value;", + " if (!localPosition) {", + " brush.w = 0;", + " return;", + " }", + " this.group.updateWorldMatrix(true, false);", + " const worldPosition = this._chunkPosition.copy(localPosition).applyMatrix4(this.group.matrixWorld);", + " const worldScale = this.uniforms.terrainWorldScale.value;", + " brush.set(worldPosition.x * worldScale, worldPosition.y * worldScale, radius, 1);", + " }", + "", + " /** @param {THREE.Camera | null} camera */", + " updateLevelsOfDetail(camera) {", + " if (!camera || !this.group.visible) return;", + " const matrixWorld = this.group.matrixWorld;", + " this._cameraPosition.setFromMatrixPosition(camera.matrixWorld);", + " this._scale.setFromMatrixScale(matrixWorld);", + " const cellSize = AREA_SIZE / this.terrain.data.resolution;", + " const chunkWorldSize = this.chunkCells * cellSize * Math.max(this._scale.x, this._scale.y);", + " for (const chunk of this.chunks) {", + " const distance =", + " this._chunkPosition.copy(chunk.center).applyMatrix4(matrixWorld).distanceTo(this._cameraPosition) /", + " chunkWorldSize;", + " let lod = LOD_DISTANCES.length - 1;", + " while (lod > 0 && distance < LOD_DISTANCES[lod]) lod--;", + " if (lod !== chunk.lod) {", + " chunk.lod = lod;", + " chunk.geometry.setIndex(getLodIndex(this.chunkCells, LOD_STEPS[lod]));", + " }", + " }", + " }", + "", + " disposeChunks() {", + " for (const chunk of this.chunks) {", + " chunk.mesh.removeFromParent();", + " // The index is shared by all chunks: it must stay on the GPU.", + " chunk.geometry.setIndex(null);", + " chunk.geometry.dispose();", + " }", + " this.chunks.length = 0;", + " if (this.splatTexture) this.splatTexture.dispose();", + " this.splatTexture = null;", + " if (this.colorTexture) this.colorTexture.dispose();", + " this.colorTexture = null;", + " }", + "", + " dispose() {", + " this.disposeChunks();", + " this.material.dispose();", + " this.depthMaterial.dispose();", + " this.distanceMaterial.dispose();", + " this.group.removeFromParent();", + " }", + "}", + "", + "/**", + " * Makes a Physics3D behavior use a height field shape following the terrain.", + " * Changes of heights are applied to the shape without recreating it.", + " * @implements {gdjs.Physics3DRuntimeBehavior.BodyUpdater}", + " */", + "class TerrainBodyUpdater {", + " /**", + " * @param {Terrain} terrain", + " * @param {gdjs.Physics3DRuntimeBehavior} physics", + " */", + " constructor(terrain, physics) {", + " this.terrain = terrain;", + " this.physics = physics;", + " this.defaultBodyUpdater = physics.bodyUpdater;", + " this.changedSamples = new SampleRectangle();", + " /** @type {Jolt.HeightFieldShape | null} */", + " this.heightField = null;", + " this.heightScale = 1;", + " this.heightsBufferPointer = 0;", + " this.heightsBufferLength = 0;", + " this.bodyX = 0;", + " this.bodyY = 0;", + " this.bodyZ = 0;", + " this.bodyHeight = 0;", + " physics.bodyType = 'Static';", + " physics.bodyUpdater = this;", + " }", + "", + " createShape() {", + " const { data, object } = this.terrain;", + " const worldInvScale = this.physics._sharedData.worldInvScale;", + " const { size } = data;", + " this.heightScale = object.getDepth() * worldInvScale;", + "", + " // Changes of heights (SetHeights) break collisions unless the sample", + " // count is a power of 2: extra samples have no collision.", + " const sampleCount = Math.pow(2, Math.ceil(Math.log2(size)));", + " const settings = new Jolt.HeightFieldShapeSettings();", + " settings.mScale.Set(", + " (object.getWidth() / data.resolution) * worldInvScale,", + " 1,", + " (object.getHeight() / data.resolution) * worldInvScale", + " );", + " settings.mSampleCount = sampleCount;", + " settings.mBlockSize = 4;", + " // Allows later changes of heights on the whole depth of the object.", + " settings.mMinHeightValue = 0;", + " settings.mMaxHeightValue = this.heightScale;", + " settings.mHeightSamples.resize(sampleCount * sampleCount);", + " const samples = new Float32Array(", + " Jolt.HEAPF32.buffer,", + " Jolt.getPointer(settings.mHeightSamples.data()),", + " sampleCount * sampleCount", + " );", + " const noCollision = Jolt.HeightFieldShapeConstantValues.prototype.cNoCollisionValue;", + " samples.fill(noCollision);", + " // Height fields go along X and Z: rows are flipped so that, once", + " // rotated to have heights along Z, rows go along Y.", + " for (let j = 0; j < size; j++) {", + " const row = (size - 1 - j) * sampleCount;", + " for (let i = 0; i < size; i++) {", + " samples[row + i] = this._getSampleHeight(j * size + i, noCollision);", + " }", + " }", + " const shape = settings.Create().Get();", + " Jolt.destroy(settings);", + " this.heightField = Jolt.castObject(shape, Jolt.HeightFieldShape);", + " this.changedSamples.clear();", + " return shape;", + " }", + "", + " /** @returns {number} The height of a sample in the shape, or no collision in a hole. */", + " _getSampleHeight(index, noCollision) {", + " const height = this.terrain.surfaceHeights[index];", + " return Number.isNaN(height) ? noCollision : height * this.heightScale;", + " }", + "", + " _getBodyPosition() {", + " const { object } = this.terrain;", + " const worldInvScale = this.physics._sharedData.worldInvScale;", + " this.bodyX = object.getX();", + " this.bodyY = object.getY();", + " this.bodyZ = object.getZ();", + " // The origin of the height field is at the bottom of the terrain (see createShape).", + " this.bodyHeight = object.getHeight();", + " return this.physics._sharedData.getRVec3(", + " object.getX() * worldInvScale,", + " (object.getY() + object.getHeight()) * worldInvScale,", + " object.getZ() * worldInvScale", + " );", + " }", + "", + " _getBodyRotation() {", + " // A quarter turn around X: heights go along Z.", + " return this.physics._sharedData.getQuat(Math.SQRT1_2, 0, 0, Math.SQRT1_2);", + " }", + "", + " createAndAddBody() {", + " const { physics } = this;", + " const { bodyInterface } = physics._sharedData;", + " const shape = this.createShape();", + " const settings = new Jolt.BodyCreationSettings(", + " shape,", + " this._getBodyPosition(),", + " this._getBodyRotation(),", + " Jolt.EMotionType_Static,", + " physics.getBodyLayer()", + " );", + " settings.mFriction = physics.friction;", + " settings.mRestitution = physics.restitution;", + " const body = bodyInterface.CreateBody(settings);", + " Jolt.destroy(settings);", + " bodyInterface.AddBody(body.GetID(), Jolt.EActivation_DontActivate);", + " return body;", + " }", + "", + " updateObjectFromBody() {", + " // The terrain is static: the body never moves it.", + " }", + "", + " updateBodyFromObject() {", + " const { physics } = this;", + " const body = physics._body;", + " if (!body) return;", + " const { object } = this.terrain;", + " const { bodyInterface } = physics._sharedData;", + " if (", + " object.getX() !== this.bodyX ||", + " object.getY() !== this.bodyY ||", + " object.getZ() !== this.bodyZ ||", + " object.getHeight() !== this.bodyHeight", + " ) {", + " bodyInterface.SetPositionAndRotation(", + " body.GetID(),", + " this._getBodyPosition(),", + " this._getBodyRotation(),", + " Jolt.EActivation_DontActivate", + " );", + " }", + " if (!this.changedSamples.isEmpty()) {", + " this._applyChangedHeights(body);", + " }", + " }", + "", + " _applyChangedHeights(body) {", + " const { data } = this.terrain;", + " const { size } = data;", + " const { minI, minJ, maxI, maxJ } = this.changedSamples;", + " // Jolt only changes blocks of samples. Rows are flipped and extra", + " // samples have no collision (see createShape).", + " const blockSize = this.heightField.GetBlockSize();", + " const sampleCount = this.heightField.GetSampleCount();", + " const alignDown = (value) => Math.floor(value / blockSize) * blockSize;", + " const alignUp = (value) => Math.min(Math.ceil(value / blockSize) * blockSize, sampleCount);", + " const firstX = alignDown(minI);", + " const firstRow = alignDown(size - 1 - maxJ);", + " const width = alignUp(maxI + 1) - firstX;", + " const height = alignUp(size - minJ) - firstRow;", + " if (width * height > this.heightsBufferLength) {", + " if (this.heightsBufferPointer) Jolt._webidl_free(this.heightsBufferPointer);", + " this.heightsBufferLength = width * height;", + " this.heightsBufferPointer = Jolt._webidl_malloc(this.heightsBufferLength * 4);", + " }", + " const heights = new Float32Array(Jolt.HEAPF32.buffer, this.heightsBufferPointer, width * height);", + " const noCollision = Jolt.HeightFieldShapeConstantValues.prototype.cNoCollisionValue;", + " for (let row = 0; row < height; row++) {", + " const j = size - 1 - (firstRow + row);", + " for (let column = 0; column < width; column++) {", + " const i = firstX + column;", + " heights[row * width + column] =", + " i < size && j >= 0 ? this._getSampleHeight(j * size + i, noCollision) : noCollision;", + " }", + " }", + " const sharedData = this.physics._sharedData;", + " this.heightField.SetHeights(", + " firstX,", + " firstRow,", + " width,", + " height,", + " this.heightsBufferPointer,", + " width,", + " sharedData.jolt.GetTempAllocator()", + " );", + " sharedData.bodyInterface.NotifyShapeChanged(", + " body.GetID(),", + " sharedData.getVec3(0, 0, 0),", + " false,", + " Jolt.EActivation_DontActivate", + " );", + " this._wakeUpBodiesOnChangedSamples();", + " this.changedSamples.clear();", + " }", + "", + " /** Bodies sleeping on the changed area would otherwise float or sink. */", + " _wakeUpBodiesOnChangedSamples() {", + " const { object, data } = this.terrain;", + " const worldInvScale = this.physics._sharedData.worldInvScale;", + " const cellWidth = object.getWidth() / data.resolution;", + " const cellHeight = object.getHeight() / data.resolution;", + " const { minI, minJ, maxI, maxJ } = this.changedSamples;", + " const min = new Jolt.Vec3(", + " (object.getX() + (minI - 1) * cellWidth) * worldInvScale,", + " (object.getY() + (minJ - 1) * cellHeight) * worldInvScale,", + " (object.getZ() - 1) * worldInvScale", + " );", + " const max = new Jolt.Vec3(", + " (object.getX() + (maxI + 1) * cellWidth) * worldInvScale,", + " (object.getY() + (maxJ + 1) * cellHeight) * worldInvScale,", + " (object.getZ() + object.getDepth() + 1) * worldInvScale", + " );", + " const box = new Jolt.AABox(min, max);", + " const broadPhaseLayerFilter = new Jolt.BroadPhaseLayerFilter();", + " const objectLayerFilter = new Jolt.ObjectLayerFilter();", + " this.physics._sharedData.bodyInterface.ActivateBodiesInAABox(box, broadPhaseLayerFilter, objectLayerFilter);", + " Jolt.destroy(min);", + " Jolt.destroy(max);", + " Jolt.destroy(box);", + " Jolt.destroy(broadPhaseLayerFilter);", + " Jolt.destroy(objectLayerFilter);", + " }", + "", + " recreateShape() {", + " const body = this.physics._body;", + " if (!body) return;", + " this.physics._sharedData.bodyInterface.SetShape(", + " body.GetID(),", + " this.createShape(),", + " false,", + " Jolt.EActivation_DontActivate", + " );", + " const { resolution } = this.terrain.data;", + " this.changedSamples.set(0, 0, resolution, resolution);", + " this._wakeUpBodiesOnChangedSamples();", + " this.changedSamples.clear();", + " }", + "", + " destroyBody() {", + " this.defaultBodyUpdater.destroyBody();", + " this.heightField = null;", + " }", + "", + " dispose() {", + " if (this.heightsBufferPointer) Jolt._webidl_free(this.heightsBufferPointer);", + " this.heightsBufferPointer = 0;", + " this.heightsBufferLength = 0;", + " }", + "}", + "", + "/** @param {TerrainData} data */", + "const copyTerrainData = (data) => {", + " const copy = new TerrainData(data.resolution);", + " copy.copyFrom(data);", + " return copy;", + "};", + "", + "// Generating a relief or reading an image is long for big terrains, and done", + "// for every instance and every hot-reload: the results are kept.", + "const baseDataCache = new Map();", + "const MAX_CACHED_BASE_DATA = 8;", + "// Changes of the shape of a terrain kept for consumers of its surface that", + "// missed some versions: older changes update the whole surface.", + "const MAX_SURFACE_CHANGES = 64;", + "// Unique among all terrains: a terrain replaced by another one never has its version.", + "let lastGroundVersion = 0;", + "", + "/**", + " * The terrain of a Terrain3D object: its data, how it's drawn and its", + " * surface (for collisions and navigation meshes). Positions are in the scene.", + " */", + "class Terrain {", + " /** @param {gdjs.CustomRuntimeObject3D} object */", + " constructor(object) {", + " this.object = object;", + " /** @type {TerrainData} */", + " this.data = new TerrainData(DEFAULT_RESOLUTION);", + " /** The data before any sculpting or painting, from the relief or heightmap. */", + " this.baseData = this.data;", + " this.renderer = new TerrainRenderer(this);", + " /** @type {TerrainBodyUpdater[]} */", + " this.bodyUpdaters = [];", + " this.changedSamples = new SampleRectangle();", + " this.hasPaintChanged = false;", + " /** Increased each time the ground changes, for objects following it (like grass). */", + " this.version = 0;", + " /** Increased each time the shape of the ground changes (not its paint or position). */", + " this.surfaceVersion = 0;", + " /**", + " * The last changes of the shape of the ground, for `getChangedArea`.", + " * @type {Array<{version: number, samples: SampleRectangle}>}", + " */", + " this.surfaceChanges = [];", + " /** The changes since this version are all in `surfaceChanges`. */", + " this.surfaceChangesStartVersion = 0;", + " this._stroke = { cellWidth: 1, cellHeight: 1, ax: 0, ay: 0, bx: 0, by: 0, radius: 0 };", + " this._strokeChangedSamples = new SampleRectangle();", + " /** The properties the data was loaded from, to reload it only if they change. */", + " this.loadedFrom = '';", + " /** The heightmap image being loaded: the relief is used until it is. */", + " this.loadingHeightmapImage = '';", + " this.isDisposed = false;", + " /** The last value of the sculpt data property read or saved. */", + " this.sculptData = '';", + " /**", + " * Values saved by the editor tools: the editor sends them back with a", + " * hot-reload, possibly after newer ones were saved.", + " * @type {string[]}", + " */", + " this.savedSculptData = [];", + " /** The heights of the surface: the heights of the data, and NaN in holes. */", + " this.surfaceHeights = new Float32Array(0);", + " this.loadFromProperties();", + " this._transform = this._getTransform();", + " getGrounds(object.getRuntimeScene()).add(this);", + " /**", + " * The shape of the ground, for the physics engine and navigation meshes.", + " * @type {gdjs.Surface}", + " */", + " this.surface = {", + " getVersion: () => this.surfaceVersion,", + " getChangedArea: (sinceVersion) => this.getChangedArea(sinceVersion),", + " getTriangles: () => null,", + " getHeightField: () => ({ columns: this.data.size, rows: this.data.size, heights: this.surfaceHeights }),", + " };", + " if (typeof object.setSurface === 'function') {", + " object.setSurface(this.surface);", + " } else if (gdjs.Physics3DRuntimeBehavior) {", + " // Older GDevelop versions don't use surfaces.", + " for (const behavior of object._behaviors) {", + " if (behavior instanceof gdjs.Physics3DRuntimeBehavior) {", + " this.bodyUpdaters.push(new TerrainBodyUpdater(this, behavior));", + " }", + " }", + " }", + " }", + "", + " _getSourceProperties() {", + " const { object } = this;", + " return [", + " object._getResolution(),", + " object._getRelief(),", + " object._getSeed(),", + " object._getHeightmapImage(),", + " object._getEdits(),", + " ].join('|');", + " }", + "", + " loadFromProperties() {", + " const { object } = this;", + " const resolution = parseInt(object._getResolution(), 10);", + " const validResolution = RESOLUTIONS.indexOf(resolution) !== -1 ? resolution : DEFAULT_RESOLUTION;", + " this.sculptData = object._getSculptData();", + " this.loadedFrom = this._getSourceProperties();", + "", + " this.baseData = this._getBaseData(validResolution);", + " let data = codec.decode(this.sculptData, (resolution) => copyTerrainData(this._getBaseData(resolution)));", + " if (data && data.resolution !== validResolution) {", + " data = data.resampled(validResolution);", + " }", + " this.setData(data || copyTerrainData(this.baseData));", + " }", + "", + " /**", + " * @param {number} resolution", + " * @returns {TerrainData} The data from the relief or the heightmap, shared: it must not be changed.", + " */", + " _getBaseData(resolution) {", + " const { object } = this;", + " const key = [resolution, object._getRelief(), object._getSeed(), object._getHeightmapImage(), object._getEdits()].join('|');", + " const cachedData = baseDataCache.get(key);", + " if (cachedData) return cachedData;", + "", + " const data = new TerrainData(resolution);", + " const heightmapImage = object._getHeightmapImage();", + " const isHeightmapLoaded =", + " !!heightmapImage && loadHeightsFromImage(data, object.getRuntimeScene().getGame(), heightmapImage);", + " if (!isHeightmapLoaded) {", + " relief.generate(data, object._getRelief(), object._getSeed());", + " if (heightmapImage) this._reloadOnceLoaded(heightmapImage);", + " }", + " edits.apply(data, object._getEdits());", + " // Not kept if the image is missing: it may be loaded later.", + " if (isHeightmapLoaded || !heightmapImage) baseDataCache.set(key, data);", + " if (baseDataCache.size > MAX_CACHED_BASE_DATA) {", + " baseDataCache.delete(baseDataCache.keys().next().value);", + " }", + " return data;", + " }", + "", + " /** @param {TerrainData} data */", + " setData(data) {", + " this.version = ++lastGroundVersion;", + " this.surfaceVersion = this.version;", + " this.surfaceChanges.length = 0;", + " this.surfaceChangesStartVersion = this.version;", + " const hasSameResolution = data.resolution === this.data.resolution && this.renderer.chunks.length > 0;", + " this.data = data;", + " const allSamples = new SampleRectangle().set(0, 0, data.resolution, data.resolution);", + " this.surfaceHeights = new Float32Array(data.heights.length);", + " this.renderer.setHasHoles(false);", + " this._updateSurfaceHeights(allSamples);", + " if (hasSameResolution) {", + " this.renderer.updateSamples(allSamples, true);", + " } else {", + " this.renderer.rebuild();", + " }", + " for (const bodyUpdater of this.bodyUpdaters) {", + " bodyUpdater.recreateShape();", + " }", + " }", + "", + " onHotReloading() {", + " const sculptData = this.object._getSculptData();", + " const savedIndex = this.savedSculptData.indexOf(sculptData);", + " if (savedIndex !== -1 && this._getSourceProperties() === this.loadedFrom) {", + " // The terrain already has this data, or newer changes.", + " this.savedSculptData.splice(0, savedIndex + 1);", + " } else if (sculptData !== this.sculptData || this._getSourceProperties() !== this.loadedFrom) {", + " this.savedSculptData.length = 0;", + " this.loadFromProperties();", + " }", + " this.renderer.updateMaterial();", + " }", + "", + " /** @returns {string} The data to save in the sculpt data property. */", + " save() {", + " this.sculptData = codec.encode(this.data, this.baseData);", + " this.savedSculptData.push(this.sculptData);", + " return this.sculptData;", + " }", + "", + " getCellWidth() {", + " return this.object.getWidth() / this.data.resolution;", + " }", + "", + " getCellHeight() {", + " return this.object.getHeight() / this.data.resolution;", + " }", + "", + " _toGridX(x) {", + " return (x - this.object.getX()) / this.getCellWidth();", + " }", + "", + " _toGridY(y) {", + " return (y - this.object.getY()) / this.getCellHeight();", + " }", + "", + " /** @returns {number} The Z position of the ground. */", + " getHeightAt(x, y) {", + " return this.object.getZ() + this.data.sampleHeight(this._toGridX(x), this._toGridY(y)) * this.object.getDepth();", + " }", + "", + " /** @returns {number} The angle of the ground with the horizontal, in degrees. */", + " getSlopeAt(x, y) {", + " const gridX = this._toGridX(x);", + " const gridY = this._toGridY(y);", + " const { data } = this;", + " const depth = this.object.getDepth();", + " const slopeX = ((data.sampleHeight(gridX + 0.5, gridY) - data.sampleHeight(gridX - 0.5, gridY)) * depth) / this.getCellWidth();", + " const slopeY = ((data.sampleHeight(gridX, gridY + 0.5) - data.sampleHeight(gridX, gridY - 0.5)) * depth) / this.getCellHeight();", + " return gdjs.toDegrees(Math.atan(Math.hypot(slopeX, slopeY)));", + " }", + "", + " /** @returns {number} The layer painted the most, from 1 to 4. */", + " getLayerAt(x, y) {", + " return this.data.getDominantLayer(this._toGridX(x), this._toGridY(y)) + 1;", + " }", + "", + " /**", + " * @param {number} layer From 1 to 4.", + " * @returns {number} How much the layer is painted, from 0 to 1.", + " */", + " getLayerWeightAt(x, y, layer) {", + " const layerIndex = clampInteger(layer - 1, 0, LAYER_COUNT - 1);", + " return this.data.sampleLayerWeight(this._toGridX(x), this._toGridY(y), layerIndex);", + " }", + "", + " /** @returns {number[]} The painted tint: red, green and blue, from 0 to 255 (white when there is none). */", + " getTintAt(x, y) {", + " const gridX = this._toGridX(x);", + " const gridY = this._toGridY(y);", + " return [0, 1, 2].map((channel) => Math.round(this.data.sampleColor(gridX, gridY, channel) * 255));", + " }", + "", + " /** @returns {number} How wet the ground is, from 0 (dry) to 1, painted or near the water (see the wet shore). */", + " getWetnessAt(x, y) {", + " const paintedWetness = this.data.sampleColor(this._toGridX(x), this._toGridY(y), 3);", + " const { object } = this;", + " if (!object._getWetShore()) return paintedWetness;", + " const fade = Math.max(object._getWetShoreFade(), 0.001);", + " const shoreWetness = clamp01((object._getWetShoreZ() + fade - this.getHeightAt(x, y)) / fade);", + " // Like the smoothstep of the shader.", + " return Math.max(paintedWetness, shoreWetness * shoreWetness * (3 - 2 * shoreWetness));", + " }", + "", + " _isInArea(x, y) {", + " const { object } = this;", + " return (", + " x >= object.getX() &&", + " x <= object.getX() + object.getWidth() &&", + " y >= object.getY() &&", + " y <= object.getY() + object.getHeight()", + " );", + " }", + "", + " /** @returns {boolean} true if the position is above or under the terrain, and not in a hole. */", + " containsPoint(x, y) {", + " return this._isInArea(x, y) && this.data.sampleSolidity(this._toGridX(x), this._toGridY(y)) >= 0.5;", + " }", + "", + " /** @returns {boolean} true if there is a hole in the terrain at the position, like what is drawn. */", + " isHoleAt(x, y) {", + " return this._isInArea(x, y) && this.data.sampleSolidity(this._toGridX(x), this._toGridY(y)) < 0.5;", + " }", + "", + " _getStroke(x1, y1, x2, y2, radius) {", + " const stroke = this._stroke;", + " stroke.cellWidth = this.getCellWidth();", + " stroke.cellHeight = this.getCellHeight();", + " stroke.ax = x1 - this.object.getX();", + " stroke.ay = y1 - this.object.getY();", + " stroke.bx = x2 - this.object.getX();", + " stroke.by = y2 - this.object.getY();", + " stroke.radius = Math.max(radius, 0);", + " return stroke;", + " }", + "", + " /**", + " * @param {SampleRectangle} samples", + " * @param {boolean} haveHeightsChanged false when only the paint changed.", + " */", + " _onSamplesChanged(samples, haveHeightsChanged) {", + " if (samples.isEmpty()) return;", + " this.version = ++lastGroundVersion;", + " this.changedSamples.add(samples);", + " // Painting changes the surface when it makes holes or fills them.", + " const hasSurfaceChanged = this._updateSurfaceHeights(samples);", + " if (!haveHeightsChanged && !hasSurfaceChanged) return;", + " this.surfaceVersion = this.version;", + " this.surfaceChanges.push({ version: this.version, samples: new SampleRectangle().set(samples.minI, samples.minJ, samples.maxI, samples.maxJ) });", + " if (this.surfaceChanges.length > MAX_SURFACE_CHANGES) {", + " this.surfaceChangesStartVersion = this.surfaceChanges.shift().version;", + " }", + " for (const bodyUpdater of this.bodyUpdaters) {", + " bodyUpdater.changedSamples.add(samples);", + " }", + " }", + "", + " /**", + " * @param {SampleRectangle} samples", + " * @returns {boolean} true if heights of the surface changed, or holes were made or filled.", + " */", + " _updateSurfaceHeights(samples) {", + " const { data, surfaceHeights } = this;", + " let hasChanged = false;", + " let hasHoles = false;", + " for (let j = samples.minJ; j <= samples.maxJ; j++) {", + " for (let i = samples.minI; i <= samples.maxI; i++) {", + " const index = j * data.size + i;", + " const isHole = data.isHole(index);", + " const height = isHole ? NaN : data.heights[index];", + " const previousHeight = surfaceHeights[index];", + " if (isHole) {", + " hasHoles = true;", + " if (Number.isNaN(previousHeight)) continue;", + " } else if (height === previousHeight) {", + " continue;", + " }", + " surfaceHeights[index] = height;", + " hasChanged = true;", + " }", + " }", + " // Filled holes keep the shaders drawing holes until the data is loaded again.", + " if (hasHoles) this.renderer.setHasHoles(true);", + " return hasChanged;", + " }", + "", + " /** @param {number} height In scene units, negative to lower the ground. */", + " raise(x1, y1, x2, y2, radius, height) {", + " const samples = this._strokeChangedSamples;", + " brushes.raise(this.data, this._getStroke(x1, y1, x2, y2, radius), height / this.object.getDepth(), samples);", + " this._onSamplesChanged(samples, true);", + " }", + "", + " /** @param {number} z The Z position to flatten the ground to. */", + " /** @param {number} [toZ] The Z position at the end of the segment, for a slope. The same as z by default. */", + " flatten(x1, y1, x2, y2, radius, z, strength, toZ = z) {", + " const samples = this._strokeChangedSamples;", + " const toHeight = (value) => (value - this.object.getZ()) / this.object.getDepth();", + " brushes.flatten(this.data, this._getStroke(x1, y1, x2, y2, radius), toHeight(z), strength, samples, toHeight(toZ));", + " this._onSamplesChanged(samples, true);", + " }", + "", + " smooth(x1, y1, x2, y2, radius, strength) {", + " const samples = this._strokeChangedSamples;", + " brushes.smooth(this.data, this._getStroke(x1, y1, x2, y2, radius), strength, samples);", + " this._onSamplesChanged(samples, true);", + " }", + "", + " /** @param {number} layer From 1 to 4. */", + " paint(x1, y1, x2, y2, radius, layer, strength) {", + " const layerIndex = clampInteger(layer - 1, 0, LAYER_COUNT - 1);", + " const samples = this._strokeChangedSamples;", + " brushes.paint(this.data, this._getStroke(x1, y1, x2, y2, radius), layerIndex, strength, samples);", + " this.hasPaintChanged = true;", + " this._onSamplesChanged(samples, false);", + " }", + "", + " /** @param {number[]} color The red, green and blue (0-255) of the tint: white removes it. */", + " tint(x1, y1, x2, y2, radius, color, strength) {", + " const samples = this._strokeChangedSamples;", + " brushes.tint(this.data, this._getStroke(x1, y1, x2, y2, radius), color, strength, samples);", + " this.hasPaintChanged = true;", + " this._onSamplesChanged(samples, false);", + " }", + "", + " /** @param {number} wetness From 0 (dry) to 1. */", + " wet(x1, y1, x2, y2, radius, wetness, strength) {", + " const samples = this._strokeChangedSamples;", + " brushes.wet(this.data, this._getStroke(x1, y1, x2, y2, radius), wetness, strength, samples);", + " this.hasPaintChanged = true;", + " this._onSamplesChanged(samples, false);", + " }", + "", + " /** Make a hole around a segment (`fillHoles` fills it again). */", + " makeHole(x1, y1, x2, y2, radius) {", + " const samples = this._strokeChangedSamples;", + " brushes.hole(this.data, this._getStroke(x1, y1, x2, y2, radius), samples);", + " this.hasPaintChanged = true;", + " this._onSamplesChanged(samples, false);", + " }", + "", + " /** Fill the holes around a segment with the layers of the ground around them. */", + " fillHoles(x1, y1, x2, y2, radius) {", + " const samples = this._strokeChangedSamples;", + " brushes.fill(this.data, this._getStroke(x1, y1, x2, y2, radius), samples);", + " this.hasPaintChanged = true;", + " this._onSamplesChanged(samples, false);", + " }", + "", + " /**", + " * Make the entrance of a tunnel or a cave: a hole, with the ground in", + " * front of it flattened to the floor of the tunnel.", + " * @param {number} angle The direction going into the ground, in degrees.", + " * @param {number} floorZ", + " * @param {number} length The length of the flat ground in front of the entrance.", + " */", + " carveEntrance(x, y, radius, angle, floorZ, length) {", + " const directionX = Math.cos(gdjs.toRad(angle));", + " const directionY = Math.sin(gdjs.toRad(angle));", + " this.flatten(x - directionX * length, y - directionY * length, x, y, radius * 1.5, floorZ, 1);", + " this.makeHole(x, y, x + directionX * radius, y + directionY * radius, radius);", + " }", + "", + " generate(reliefName, seed) {", + " this.baseData = new TerrainData(this.data.resolution);", + " relief.generate(this.baseData, reliefName, seed);", + " this.setData(copyTerrainData(this.baseData));", + " }", + "", + " loadHeightmap(imageResourceName) {", + " const baseData = new TerrainData(this.data.resolution);", + " if (loadHeightsFromImage(baseData, this.object.getRuntimeScene().getGame(), imageResourceName)) {", + " baseData.splat.set(this.data.splat);", + " baseData.colors.set(this.data.colors);", + " this.baseData = baseData;", + " this.setData(copyTerrainData(baseData));", + " }", + " }", + "", + " /** @param {gdjs.RuntimeObject[]} objects */", + " placeOnGround(objects) {", + " for (const object of objects) {", + " if (object === this.object || !gdjs.Base3DHandler.is3D(object)) continue;", + " const groundZ = this.getHeightAt(object.getCenterXInScene(), object.getCenterYInScene());", + " // The origin of the object is not always at its bottom.", + " object.setZ(groundZ + object.getZ() - object.getUnrotatedAABBMinZ());", + " }", + " }", + "", + " /**", + " * @returns {THREE.Vector3 | null} The point of the ground under the cursor,", + " * in the terrain group coordinates. Holes are hit too, so that brushes can fill them.", + " */", + " raycastFromCursor(game, raycaster, ndc) {", + " const layer = this.object.getInstanceContainer().getLayer(this.object.getLayer());", + " const camera = layer.getRenderer().getThreeCamera();", + " if (!camera) return null;", + " const inputManager = game.getInputManager();", + " ndc.set(", + " (inputManager.getCursorX() / game.getGameResolutionWidth()) * 2 - 1,", + " -(inputManager.getCursorY() / game.getGameResolutionHeight()) * 2 + 1", + " );", + " raycaster.setFromCamera(ndc, camera);", + " this.renderer.group.updateWorldMatrix(true, false);", + " const ray = raycaster.ray.clone().applyMatrix4(this.renderer.group.matrixWorld.clone().invert());", + " ray.direction.normalize();", + "", + " const box = new THREE.Box3(new THREE.Vector3(0, 0, 0), new THREE.Vector3(AREA_SIZE, AREA_SIZE, AREA_DEPTH));", + " const entry = box.containsPoint(ray.origin) ? ray.origin.clone() : ray.intersectBox(box, new THREE.Vector3());", + " if (!entry) return null;", + " const cellSize = AREA_SIZE / this.data.resolution;", + " const groundZAt = (point) => this.data.sampleHeight(point.x / cellSize, point.y / cellSize) * AREA_DEPTH;", + " const step = cellSize / 2;", + " const point = entry.clone();", + " const previousPoint = entry.clone();", + " for (let distance = 0; distance < AREA_SIZE * 2; distance += step) {", + " if (point.z <= groundZAt(point)) {", + " // Refine between the last point above the ground and this one.", + " for (let iteration = 0; iteration < 8; iteration++) {", + " const middle = previousPoint.clone().lerp(point, 0.5);", + " if (middle.z <= groundZAt(middle)) point.copy(middle);", + " else previousPoint.copy(middle);", + " }", + " return point;", + " }", + " if (!box.containsPoint(point)) return null;", + " previousPoint.copy(point);", + " point.addScaledVector(ray.direction, step);", + " }", + " return null;", + " }", + "", + " /**", + " * @param {THREE.Vector3} localPosition In the terrain group coordinates (like from `raycastFromCursor`).", + " * @returns {{x: number, y: number, z: number}} The position in the scene.", + " */", + " toScenePosition(localPosition) {", + " const { object } = this;", + " return {", + " x: object.getX() + (localPosition.x / AREA_SIZE) * object.getWidth(),", + " y: object.getY() + (localPosition.y / AREA_SIZE) * object.getHeight(),", + " z: object.getZ() + (localPosition.z / AREA_DEPTH) * object.getDepth(),", + " };", + " }", + "", + " /** Draw the changes of the ground (collisions are updated by the physics engine). */", + " applyChanges() {", + " this.renderer.updateSamples(this.changedSamples, this.hasPaintChanged);", + " this.changedSamples.clear();", + " this.hasPaintChanged = false;", + " }", + "", + " /** @returns {number} A number changing each time the ground changes (see `getGrounds`). */", + " getVersion() {", + " return this.version;", + " }", + "", + " /**", + " * @param {number} sinceVersion", + " * @returns {gdjs.SurfaceArea | null} The part of the ground whose shape changed since the version (see `gdjs.Surface`).", + " */", + " getChangedArea(sinceVersion) {", + " if (sinceVersion < this.surfaceChangesStartVersion) return null;", + " const samples = new SampleRectangle();", + " for (const change of this.surfaceChanges) {", + " if (change.version > sinceVersion) samples.add(change.samples);", + " }", + " if (samples.isEmpty()) return { minX: 0, minY: 0, maxX: 0, maxY: 0 };", + " const { resolution } = this.data;", + " return {", + " minX: samples.minI / resolution,", + " minY: samples.minJ / resolution,", + " maxX: samples.maxI / resolution,", + " maxY: samples.maxJ / resolution,", + " };", + " }", + "", + " _getTransform() {", + " const { object } = this;", + " return [object.getX(), object.getY(), object.getZ(), object.getWidth(), object.getHeight(), object.getDepth()].join(',');", + " }", + "", + " /** @param {string} imageResourceName A heightmap image that is not loaded yet. */", + " _reloadOnceLoaded(imageResourceName) {", + " if (this.loadingHeightmapImage === imageResourceName) return;", + " this.loadingHeightmapImage = imageResourceName;", + " const game = this.object.getRuntimeScene().getGame();", + " game.getImageManager()", + " .loadResource(imageResourceName)", + " .then(() => {", + " if (this.isDisposed || this.loadingHeightmapImage !== imageResourceName) return;", + " this.loadingHeightmapImage = '';", + " if (getLoadedImage(game, imageResourceName)) this.loadFromProperties();", + " })", + " .catch(() => {});", + " }", + "", + " update() {", + " const transform = this._getTransform();", + " if (transform !== this._transform) {", + " this._transform = transform;", + " this.version = ++lastGroundVersion;", + " }", + " this.applyChanges();", + " this.renderer.uniforms.terrainWorldScale.value = getWorldScale(this.object);", + " this._updateWetShore();", + " const layer = this.object.getInstanceContainer().getLayer(this.object.getLayer());", + " this.renderer.updateLevelsOfDetail(layer.getRenderer().getThreeCamera());", + " }", + "", + " _updateWetShore() {", + " const { object } = this;", + " // In the coordinates of the terrain group, like the vertices.", + " const depthScale = AREA_DEPTH / Math.max(object.getDepth(), 0.0001);", + " this.renderer.uniforms.terrainWetShore.value.set(", + " (object._getWetShoreZ() - object.getZ()) * depthScale,", + " Math.max(object._getWetShoreFade(), 0.001) * depthScale,", + " object._getWetShore() ? 1 : 0", + " );", + " }", + "", + " dispose() {", + " this.isDisposed = true;", + " getGrounds(this.object.getRuntimeScene()).delete(this);", + " if (typeof this.object.setSurface === 'function' && this.object.getSurface() === this.surface) {", + " this.object.setSurface(null);", + " }", + " this.renderer.dispose();", + " for (const bodyUpdater of this.bodyUpdaters) {", + " bodyUpdater.dispose();", + " }", + " }", + "}", + "", + "/**", + " * @param {gdjs.RuntimeObject} object", + " * @returns {number} The number of scene units by 3D world unit (1 in GDevelop versions before the world scale).", + " */", + "const getWorldScale = (object) => {", + " const scene = object.getRuntimeScene();", + " return typeof scene.getRenderer3DWorldScale === 'function' ? scene.getRenderer3DWorldScale() : 1;", + "};", + "", + "const averageColors = new WeakMap();", + "let white = null;", + "/**", + " * @param {THREE.Texture} texture", + " * @returns {THREE.Color} The average color of the texture (white when it's not loaded).", + " */", + "const getAverageColor = (texture) => {", + " const cachedColor = averageColors.get(texture);", + " if (cachedColor) return cachedColor;", + " const image = texture.image;", + " const isDrawable =", + " typeof document !== 'undefined' &&", + " ((image instanceof HTMLImageElement && image.complete && image.naturalWidth > 0) ||", + " image instanceof HTMLCanvasElement ||", + " (typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap));", + " if (!isDrawable) return white || (white = new THREE.Color(1, 1, 1));", + " const size = 8;", + " const canvas = document.createElement('canvas');", + " canvas.width = size;", + " canvas.height = size;", + " const context = canvas.getContext('2d');", + " context.drawImage(image, 0, 0, size, size);", + " const pixels = context.getImageData(0, 0, size, size).data;", + " let red = 0;", + " let green = 0;", + " let blue = 0;", + " for (let index = 0; index < pixels.length; index += 4) {", + " red += pixels[index];", + " green += pixels[index + 1];", + " blue += pixels[index + 2];", + " }", + " const count = size * size * 255;", + " const color = new THREE.Color().setRGB(red / count, green / count, blue / count, THREE.SRGBColorSpace);", + " averageColors.set(texture, color);", + " return color;", + "};", + "", + "const svgIcon = (content) =>", + " 'data:image/svg+xml,' +", + " encodeURIComponent('' + content + '');", + "const strokes = 'fill=\"none\" stroke=\"#000\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"';", + "const toolIcons = {", + " Raise: svgIcon(''),", + " Lower: svgIcon(''),", + " Smooth: svgIcon(''),", + " Flatten: svgIcon(''),", + " Size: svgIcon(''),", + " Strength: svgIcon(''),", + " Undo: svgIcon(''),", + " Hole: svgIcon(''),", + " FillHoles: svgIcon(''),", + " Wetness: svgIcon(''),", + "};", + "// Tints for the color brush when the editor has no color picker (older GDevelop versions).", + "const tintPresets = ['#e2c879', '#a5c45a', '#8a6a48', '#c98a62', '#9a9a92', '#ffffff'];", + "", + "/**", + " * The sculpt and paint tools shown in the scene editor when a terrain is", + " * selected. Without the needed editor functions (older GDevelop versions),", + " * nothing is shown: the terrain can still be changed with events.", + " */", + "class TerrainEditorTools {", + " constructor() {", + " /** '' when no brush is used. */", + " this.toolName = '';", + " this.sizePercent = 30;", + " this.strengthPercent = 50;", + " /** @type {Terrain | null} */", + " this.terrain = null;", + " /** @type {gdjs.InGameEditor | null} */", + " this.editor = null;", + " this.isStroking = false;", + " this.flattenHeight = 0;", + " /** The color of the \"Tint\" brush, as \"#rrggbb\". */", + " this.tintColor = tintPresets[0];", + " this.wetnessPercent = 100;", + " this.lastStrokeTime = 0;", + " /** @type {{data: TerrainData, baseData: TerrainData}[]} */", + " this.undoSteps = [];", + " this.raycaster = new THREE.Raycaster();", + " this.ndc = new THREE.Vector2();", + " this._swatchColor = new THREE.Color();", + " }", + "", + " /** @param {gdjs.InGameEditor} editor */", + " update(editor) {", + " if (typeof editor.showToolbar !== 'function') {", + " // Older GDevelop versions: the terrain can only be changed with events.", + " return;", + " }", + " this.editor = editor;", + " const selectedObjects = editor.getSelectedObjects();", + " const terrain =", + " selectedObjects.length === 1 && selectedObjects[0].__terrain3D ? selectedObjects[0].__terrain3D : null;", + " if (terrain !== this.terrain) {", + " this._endStroke();", + " if (this.terrain) this.terrain.renderer.setBrush(null, 0);", + " this.terrain = terrain;", + " this.undoSteps.length = 0;", + " }", + " if (terrain) editor.showToolbar('Terrain3D', this._getToolbarItems(terrain));", + " const game = editor.getRuntimeGame();", + " if (!terrain || !this.toolName) {", + " if (terrain) terrain.renderer.setBrush(null, 0);", + " return;", + " }", + "", + " const inputManager = game.getInputManager();", + " if (inputManager.wasKeyJustPressed(gdjs.evtTools.input.keysNameToCode.Escape)) {", + " this._endStroke();", + " this.toolName = '';", + " terrain.renderer.setBrush(null, 0);", + " return;", + " }", + " editor.captureLeftMouseButton();", + " const hit = terrain.raycastFromCursor(game, this.raycaster, this.ndc);", + " const radius = this._getRadius();", + " terrain.renderer.setBrush(hit, radius);", + "", + " // Older GDevelop versions don't have it.", + " const isPressedForTool =", + " typeof editor.isCapturedLeftMouseButtonPressed === 'function'", + " ? editor.isCapturedLeftMouseButtonPressed()", + " : inputManager.isMouseButtonPressed(0);", + " // The button is released outside the canvas when the mouse is on a toolbar.", + " const isPressed = isPressedForTool && inputManager.isMouseInsideCanvas();", + " if (isPressed && hit && !this.isStroking) {", + " this._startStroke(terrain, hit);", + " }", + " if (this.isStroking && isPressed && hit) {", + " this._applyBrush(terrain, hit, radius);", + " }", + " if (!isPressed) {", + " this._endStroke();", + " }", + " }", + "", + " _getRadius() {", + " const { object } = this.terrain;", + " const maxRadius = Math.max(object.getWidth(), object.getHeight()) / 4;", + " return Math.max(maxRadius * Math.pow(this.sizePercent / 100, 2), this.terrain.getCellWidth());", + " }", + "", + " _startStroke(terrain, hit) {", + " this.isStroking = true;", + " this.lastStrokeTime = performance.now();", + " this.flattenHeight = terrain.toScenePosition(hit).z;", + " this.undoSteps.push({ data: copyTerrainData(terrain.data), baseData: terrain.baseData });", + " const maxUndoSteps = Math.max(Math.floor(UNDO_STEPS_SAMPLES / terrain.data.heights.length), 2);", + " while (this.undoSteps.length > maxUndoSteps) this.undoSteps.shift();", + " }", + "", + " _endStroke() {", + " if (!this.isStroking) return;", + " this.isStroking = false;", + " this._save();", + " }", + "", + " _applyBrush(terrain, hit, radius) {", + " const now = performance.now();", + " const elapsedSeconds = Math.min((now - this.lastStrokeTime) / 1000, 0.1);", + " this.lastStrokeTime = now;", + " const { x, y } = terrain.toScenePosition(hit);", + " const amount = (this.strengthPercent / 100) * elapsedSeconds;", + " const depth = terrain.object.getDepth();", + " if (this.toolName === 'Raise') terrain.raise(x, y, x, y, radius, amount * depth * 0.5);", + " else if (this.toolName === 'Lower') terrain.raise(x, y, x, y, radius, -amount * depth * 0.5);", + " else if (this.toolName === 'Smooth') terrain.smooth(x, y, x, y, radius, amount * 10);", + " else if (this.toolName === 'Flatten') terrain.flatten(x, y, x, y, radius, this.flattenHeight, amount * 10);", + " else if (this.toolName.startsWith('Paint')) {", + " terrain.paint(x, y, x, y, radius, parseInt(this.toolName.slice(5), 10), amount * 8);", + " } else if (this.toolName === 'Hole') {", + " terrain.makeHole(x, y, x, y, radius);", + " } else if (this.toolName === 'FillHoles') {", + " terrain.fillHoles(x, y, x, y, radius);", + " } else if (this.toolName === 'Tint') {", + " terrain.tint(x, y, x, y, radius, gdjs.rgbOrHexToRGBColor(this.tintColor), amount * 8);", + " } else if (this.toolName === 'Wetness') {", + " terrain.wet(x, y, x, y, radius, this.wetnessPercent / 100, amount * 8);", + " }", + " // Drawn now, as the terrain was updated earlier in this frame.", + " terrain.applyChanges();", + " }", + "", + " _save() {", + " const { terrain, editor } = this;", + " if (!terrain || !editor) return;", + " editor.updateObjectProperties(terrain.object.getName(), { SculptData: terrain.save() });", + " }", + "", + " _undo() {", + " const undoStep = this.undoSteps.pop();", + " // The relief, the edits or the resolution may have changed since the stroke.", + " if (!this.terrain || !undoStep || undoStep.baseData !== this.terrain.baseData) {", + " this.undoSteps.length = 0;", + " return;", + " }", + " this.terrain.setData(undoStep.data);", + " this._save();", + " }", + "", + " /** @returns {Array} The settings of the tint or wetness brush, when it's used. */", + " _getColorToolItems() {", + " const { editor } = this;", + " if (this.toolName === 'Tint') {", + " if (editor && typeof editor.isToolbarItemTypeSupported === 'function' && editor.isToolbarItemTypeSupported('color')) {", + " return [", + " {", + " type: 'color',", + " id: 'TintColor',", + " tooltip: 'Color of the tint',", + " value: this.tintColor,", + " onChange: (color) => (this.tintColor = color),", + " },", + " ];", + " }", + " return tintPresets.map((color) => ({", + " type: 'button',", + " id: 'TintPreset' + color,", + " tooltip: color === '#ffffff' ? 'No tint' : 'Tint with this color',", + " color,", + " isActive: this.tintColor === color,", + " onClick: () => (this.tintColor = color),", + " }));", + " }", + " if (this.toolName === 'Wetness') {", + " return [", + " {", + " type: 'slider',", + " id: 'WetnessAmount',", + " tooltip: 'Wetness',", + " iconUrl: toolIcons.Wetness,", + " min: 0,", + " max: 100,", + " value: this.wetnessPercent,", + " onChange: (value) => (this.wetnessPercent = value),", + " },", + " ];", + " }", + " return [];", + " }", + "", + " /**", + " * @param {Terrain} terrain", + " * @returns {Array} The toolbar for the current state (the editor only", + " * updates what changed).", + " */", + " _getToolbarItems(terrain) {", + " const { object } = terrain;", + " const toolButton = (toolName, tooltip, color) => ({", + " type: 'button',", + " id: toolName,", + " tooltip,", + " iconUrl: toolIcons[toolName],", + " color,", + " isActive: this.toolName === toolName,", + " onClick: () => (this.toolName = this.toolName === toolName ? '' : toolName),", + " });", + " const layerColors = [object._getLayer1Color(), object._getLayer2Color(), object._getLayer3Color(), object._getLayer4Color()];", + " // Layers are shown with their color, tinting their texture.", + " const swatchColors = layerColors.map((color, index) =>", + " this._swatchColor", + " .set(gdjs.rgbOrHexStringToNumber(color))", + " .multiply(getAverageColor(terrain.renderer.uniforms['terrainLayerMap' + index].value))", + " .getHexString()", + " );", + " return [", + " toolButton('Raise', 'Raise the ground (drag on the terrain)'),", + " toolButton('Lower', 'Lower the ground'),", + " toolButton('Smooth', 'Smooth the ground'),", + " toolButton('Flatten', 'Flatten the ground to the height where the drag starts'),", + " { type: 'divider', id: 'PaintDivider' },", + " ...swatchColors.map((swatchColor, index) =>", + " toolButton('Paint' + (index + 1), 'Paint layer ' + (index + 1), '#' + swatchColor)", + " ),", + " toolButton('Hole', 'Make a hole, for the entrance of a tunnel or a cave'),", + " toolButton('FillHoles', 'Fill holes, with the layers of the ground around them'),", + " { type: 'divider', id: 'ColorDivider' },", + " toolButton('Tint', 'Tint the ground (white removes the tint)', this.tintColor),", + " toolButton('Wetness', 'Make the ground wet, or dry with a wetness of 0'),", + " ...this._getColorToolItems(),", + " { type: 'divider', id: 'BrushDivider' },", + " {", + " type: 'slider',", + " id: 'Size',", + " tooltip: 'Brush size',", + " iconUrl: toolIcons.Size,", + " min: 1,", + " max: 100,", + " value: this.sizePercent,", + " onChange: (value) => (this.sizePercent = value),", + " },", + " {", + " type: 'slider',", + " id: 'Strength',", + " tooltip: 'Brush strength',", + " iconUrl: toolIcons.Strength,", + " min: 5,", + " max: 100,", + " value: this.strengthPercent,", + " onChange: (value) => (this.strengthPercent = value),", + " },", + " { type: 'button', id: 'Undo', tooltip: 'Undo the last stroke on this terrain', iconUrl: toolIcons.Undo, onClick: () => this._undo() },", + " ];", + " }", + "}", + "", + "const editorTools = new TerrainEditorTools();", + "if (gdjs.registerInGameEditorPostStepCallback) {", + " gdjs.registerInGameEditorPostStepCallback((editor) => editorTools.update(editor));", + "}", + "", + "gdjs.__terrain3DExtension = {", + " Terrain,", + " // Used by the terrain grass and the scattered models.", + " getWorldScale,", + " getGrounds,", + " patchShaderCode,", + " createRandom,", + " findHighestGround,", + " GroundPlacement,", + " svgIcon,", + " toolIcons,", + " // Exposed for tests.", + " TerrainBodyUpdater,", + " TerrainData,", + " SampleRectangle,", + " brushes,", + " edits,", + " codec,", + " relief,", + "};" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [], + "description": "Define helper classes JavaScript code.", + "private": true + }, + { + "fullName": "Define grass helper classes", + "functionType": "Action", + "name": "DefineGrassHelperClasses", + "sentence": "Define grass helper classes JavaScript code", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (gdjs.__terrainGrassExtension) {", + " return;", + "}", + "// Blades grow on the grounds of the scene: the 3D terrains (see `getGrounds`", + "// in the terrain helper).", + "", + "// Size of the object inner area: instances are scaled from it.", + "const AREA_SIZE = 1000;", + "const AREA_DEPTH = 40;", + "const CHUNK_SIZE = 500;", + "const MAX_BENDING_OBJECTS = 8;", + "", + "/**", + " * A blade: a thin, tapered strip. Its width and height are 1: blades are", + " * scaled by the shader.", + " */", + "const createBladeGeometry = () => {", + " const halfWidths = [0.5, 0.42, 0.28, 0];", + " const heights = [0, 0.35, 0.7, 1];", + " const positions = [];", + " halfWidths.forEach((halfWidth, level) => {", + " positions.push(-halfWidth, 0, heights[level]);", + " if (halfWidth > 0) positions.push(halfWidth, 0, heights[level]);", + " });", + " const indices = [0, 1, 2, 1, 3, 2, 2, 3, 4, 3, 5, 4, 4, 5, 6];", + " const geometry = new THREE.InstancedBufferGeometry();", + " geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));", + " geometry.setAttribute('normal', new THREE.Float32BufferAttribute(new Array(positions.length).fill(0), 3));", + " geometry.setIndex(indices);", + " return geometry;", + "};", + "", + "const vertexShaderDeclarations = `", + "attribute vec4 grassRoot;", + "attribute vec2 grassShape;", + "// The tint of the ground at the root (linear, from 0 to 1).", + "attribute vec3 grassGroundTint;", + "uniform mat3 grassWorldToLocal;", + "uniform float grassWorldScale;", + "uniform float grassBladeWidth;", + "uniform float grassTime;", + "uniform float grassWindStrength;", + "uniform float grassWindSpeed;", + "uniform vec4 grassBenders[${MAX_BENDING_OBJECTS}];", + "uniform vec3 grassBaseColor;", + "uniform vec3 grassTipColor;", + "varying vec3 vGrassColor;", + "`;", + "const vertexShaderPosition = `", + "// Blades are shaped in scene units (with Y flipped like the 3D world), then", + "// moved back in the (scaled) object space.", + "float bladeHeight = position.z;", + "float grassAngle = grassRoot.w;", + "float bladeWidth = position.x * grassBladeWidth;", + "vec3 blade = vec3(", + " bladeWidth * cos(grassAngle),", + " bladeWidth * sin(grassAngle),", + " bladeHeight * grassShape.x", + ");", + "vec3 rootWorld = (modelMatrix * vec4(grassRoot.xyz, 1.0)).xyz * grassWorldScale;", + "// Tips bend more than the base.", + "float bendFactor = bladeHeight * bladeHeight * grassShape.x;", + "float gust = sin(grassTime * grassWindSpeed + rootWorld.x * 0.013 + rootWorld.y * 0.011);", + "float flutter = sin(grassTime * grassWindSpeed * 2.7 + grassShape.y * 40.0) * 0.3;", + "blade.xy += vec2(0.8, 0.5) * (gust + flutter + 0.6) * grassWindStrength * 0.35 * bendFactor;", + "for (int i = 0; i < ${MAX_BENDING_OBJECTS}; i++) {", + " vec4 bender = grassBenders[i];", + " if (bender.z <= 0.0) continue;", + " vec2 away = rootWorld.xy - bender.xy;", + " float distanceToBender = length(away);", + " float push = 1.0 - smoothstep(bender.z * 0.3, bender.z, distanceToBender);", + " if (push > 0.0) {", + " blade.xy += normalize(away + vec2(0.001)) * push * bendFactor * 0.9;", + " blade.z *= 1.0 - push * 0.5;", + " }", + "}", + "vec3 transformed = grassRoot.xyz + grassWorldToLocal * (blade / grassWorldScale);", + "vGrassColor = mix(grassBaseColor, grassTipColor, bladeHeight) * (0.85 + 0.3 * grassShape.y) * grassGroundTint;", + "`;", + "const vertexShaderNormal = `", + "// Blades are lit like the ground below them.", + "transformedNormal = normalize((viewMatrix * vec4(0.0, 0.0, 1.0, 0.0)).xyz);", + "`;", + "const fragmentShaderDeclarations = `", + "varying vec3 vGrassColor;", + "`;", + "const fragmentShaderColor = `", + "diffuseColor.rgb *= vGrassColor;", + "`;", + "// Both faces of blades are lit like the ground (not flipped for back faces).", + "const fragmentShaderNormal = `", + "#include ", + "normal = normalize(vNormal);", + "nonPerturbedNormal = normal;", + "`;", + "", + "const { getWorldScale, patchShaderCode, createRandom, findHighestGround, GroundPlacement } = gdjs.__terrain3DExtension;", + "", + "/**", + " * The grass of a terrain grass object: blades drawn with GPU instancing, in chunks", + " * that are hidden when off-screen and thinned out with the distance. They grow", + " * on the grounds below, like 3D terrains (only where a given layer is painted).", + " */", + "class Grass {", + " /** @param {gdjs.CustomRuntimeObject3D} object */", + " constructor(object) {", + " this.object = object;", + " this.group = new THREE.Group();", + " object.get3DRendererObject().add(this.group);", + " this.bladeGeometry = createBladeGeometry();", + " /** @type {Array<{mesh: THREE.Mesh, blades: number, center: THREE.Vector3}>} */", + " this.chunks = [];", + " this.placement = new GroundPlacement(object.getRuntimeScene());", + " this._groundAtBlade = { ground: null, z: 0 };", + " this.uniforms = {", + " grassWorldToLocal: { value: new THREE.Matrix3() },", + " // Scene units by 3D world unit.", + " grassWorldScale: { value: 1 },", + " grassBladeWidth: { value: 1 },", + " grassTime: { value: 0 },", + " grassWindStrength: { value: 1 },", + " grassWindSpeed: { value: 1 },", + " grassBenders: { value: Array.from({ length: MAX_BENDING_OBJECTS }, () => new THREE.Vector4()) },", + " grassBaseColor: { value: new THREE.Color() },", + " grassTipColor: { value: new THREE.Color() },", + " };", + " this.material = new THREE.MeshStandardMaterial({ roughness: 1, metalness: 0, side: THREE.DoubleSide });", + " this.material.onBeforeCompile = (shader) => {", + " Object.assign(shader.uniforms, this.uniforms);", + " shader.vertexShader = patchShaderCode(shader.vertexShader, [", + " ['#include ', '#include \\n' + vertexShaderDeclarations],", + " ['#include ', vertexShaderPosition],", + " ['#include ', '#include \\n' + vertexShaderNormal],", + " ], 'TerrainGrass');", + " shader.fragmentShader = patchShaderCode(shader.fragmentShader, [", + " ['#include ', '#include \\n' + fragmentShaderDeclarations],", + " ['#include ', '#include \\n' + fragmentShaderColor],", + " ['#include ', fragmentShaderNormal],", + " ], 'TerrainGrass');", + " };", + " this.material.customProgramCacheKey = () => 'TerrainGrass';", + "", + " this._matrix = new THREE.Matrix4();", + " this._cameraPosition = new THREE.Vector3();", + " this._chunkPosition = new THREE.Vector3();", + " this._scale = new THREE.Vector3();", + " this.updateFromProperties();", + " }", + "", + " updateFromProperties() {", + " const { object } = this;", + " this.uniforms.grassBaseColor.value.set(gdjs.rgbOrHexStringToNumber(object._getBaseColor()));", + " this.uniforms.grassTipColor.value.set(gdjs.rgbOrHexStringToNumber(object._getTipColor()));", + " this.uniforms.grassWindStrength.value = object._getWindStrength();", + " this.uniforms.grassWindSpeed.value = object._getWindSpeed();", + " this.uniforms.grassBladeWidth.value = Math.max(object._getBladeWidth(), 0);", + " this.material.needsUpdate = true;", + " this.placement.placeAgain();", + " }", + "", + " _getPlacementKey() {", + " const { object } = this;", + " return [", + " object.getX(), object.getY(), object.getZ(), object.getWidth(), object.getHeight(), object.getDepth(),", + " object._getDensity(), object._getBladeHeight(), object._getGroundLayer(), object._getMaxSlope(),", + " object._getSeed(),", + " ].join('|');", + " }", + "", + " /** Place the blades again, for example after the ground or the object changed. */", + " rebuild() {", + " const grounds = Array.from(this.placement.grounds);", + " for (const chunk of this.chunks) {", + " chunk.mesh.removeFromParent();", + " chunk.mesh.geometry.dispose();", + " }", + " this.chunks.length = 0;", + "", + " const { object } = this;", + " const width = object.getWidth();", + " const height = object.getHeight();", + " const scaleX = width / AREA_SIZE;", + " const scaleY = height / AREA_SIZE;", + " const scaleZ = Math.max(object.getDepth() / AREA_DEPTH, 0.0001);", + " const bladesPerChunk = Math.round((Math.max(object._getDensity(), 0) * CHUNK_SIZE * CHUNK_SIZE) / 10000);", + " const groundLayer = parseInt(object._getGroundLayer(), 10) || 0;", + " const maxSlope = object._getMaxSlope();", + " const chunksX = Math.max(Math.ceil(width / CHUNK_SIZE), 1);", + " const chunksY = Math.max(Math.ceil(height / CHUNK_SIZE), 1);", + " const nextRandom = createRandom(object._getSeed() * 7919 + 1);", + " const bladeHeight = Math.max(object._getBladeHeight(), 0);", + "", + " for (let chunkY = 0; chunkY < chunksY; chunkY++) {", + " for (let chunkX = 0; chunkX < chunksX; chunkX++) {", + " const roots = new Float32Array(bladesPerChunk * 4);", + " const shapes = new Float32Array(bladesPerChunk * 2);", + " const groundTints = new Uint8Array(bladesPerChunk * 3).fill(255);", + " let blades = 0;", + " let minZ = Infinity;", + " let maxZ = -Infinity;", + " for (let attempt = 0; attempt < bladesPerChunk; attempt++) {", + " // In scene units, relative to the object.", + " const x = (chunkX + nextRandom()) * CHUNK_SIZE;", + " const y = (chunkY + nextRandom()) * CHUNK_SIZE;", + " const angle = nextRandom() * Math.PI * 2;", + " const heightFactor = 0.6 + 0.8 * nextRandom();", + " const tint = nextRandom();", + " const growth = nextRandom();", + " if (x > width || y > height) continue;", + " const sceneX = object.getX() + x;", + " const sceneY = object.getY() + y;", + " const { ground, z } = findHighestGround(grounds, sceneX, sceneY, this._groundAtBlade);", + " const groundZ = ground ? z : object.getZ();", + " if (ground) {", + " if (groundLayer > 0 && growth > ground.getLayerWeightAt(sceneX, sceneY, groundLayer)) continue;", + " if (ground.getSlopeAt(sceneX, sceneY) > maxSlope) continue;", + " if (typeof ground.getTintAt === 'function') {", + " const tint = ground.getTintAt(sceneX, sceneY);", + " for (let channel = 0; channel < 3; channel++) {", + " groundTints[blades * 3 + channel] = Math.round(255 * Math.pow(tint[channel] / 255, 2.2));", + " }", + " }", + " }", + " const localZ = (groundZ - object.getZ()) / scaleZ;", + " roots[blades * 4] = x / scaleX;", + " roots[blades * 4 + 1] = y / scaleY;", + " roots[blades * 4 + 2] = localZ;", + " roots[blades * 4 + 3] = angle;", + " shapes[blades * 2] = heightFactor * bladeHeight;", + " shapes[blades * 2 + 1] = tint;", + " minZ = Math.min(minZ, localZ);", + " maxZ = Math.max(maxZ, localZ);", + " blades++;", + " }", + " if (blades === 0) continue;", + "", + " const geometry = new THREE.InstancedBufferGeometry();", + " geometry.index = this.bladeGeometry.index;", + " geometry.setAttribute('position', this.bladeGeometry.attributes.position);", + " geometry.setAttribute('normal', this.bladeGeometry.attributes.normal);", + " geometry.setAttribute('grassRoot', new THREE.InstancedBufferAttribute(roots.subarray(0, blades * 4), 4));", + " geometry.setAttribute('grassShape', new THREE.InstancedBufferAttribute(shapes.subarray(0, blades * 2), 2));", + " geometry.setAttribute('grassGroundTint', new THREE.InstancedBufferAttribute(groundTints.subarray(0, blades * 3), 3, true));", + " geometry.instanceCount = blades;", + " // The furthest tips can go with the wind and the bending objects (see the shader).", + " const maxBladeHeight = bladeHeight * 1.4;", + " const lean =", + " (0.35 * 1.9 * 0.943 * Math.max(object._getWindStrength(), 0) + 0.9) * maxBladeHeight +", + " Math.max(object._getBladeWidth(), 0) / 2;", + " const leanX = lean / scaleX;", + " const leanY = lean / scaleY;", + " geometry.boundingBox = new THREE.Box3(", + " new THREE.Vector3((chunkX * CHUNK_SIZE) / scaleX - leanX, (chunkY * CHUNK_SIZE) / scaleY - leanY, minZ),", + " new THREE.Vector3(", + " Math.min((chunkX + 1) * CHUNK_SIZE, width) / scaleX + leanX,", + " Math.min((chunkY + 1) * CHUNK_SIZE, height) / scaleY + leanY,", + " maxZ + (bladeHeight * 1.4) / scaleZ", + " )", + " );", + " geometry.boundingSphere = geometry.boundingBox.getBoundingSphere(new THREE.Sphere());", + " const mesh = new THREE.Mesh(geometry, this.material);", + " mesh.receiveShadow = true;", + " // Raycasts would only test one blade: the selection plane is used instead.", + " mesh.raycast = () => {};", + " this.group.add(mesh);", + " this.chunks.push({ mesh, blades, center: geometry.boundingSphere.center.clone() });", + " }", + " }", + " }", + "", + " _updateBendingObjects() {", + " const benders = this.uniforms.grassBenders.value;", + " const objectName = this.object._getBendingObject();", + " const bendingObjects = objectName ? this.object.getInstanceContainer().getObjects(objectName) || [] : [];", + " for (let index = 0; index < MAX_BENDING_OBJECTS; index++) {", + " const bendingObject = bendingObjects[index];", + " if (!bendingObject) {", + " benders[index].set(0, 0, 0, 0);", + " continue;", + " }", + " // Blades are pushed in the 3D scene, which is flipped on Y.", + " const radius = Math.max(bendingObject.getWidth(), bendingObject.getHeight()) * 1.2;", + " benders[index].set(bendingObject.getCenterXInScene(), -bendingObject.getCenterYInScene(), radius, 0);", + " }", + " }", + "", + " /**", + " * @param {number} elapsedSeconds", + " * @param {number} time In seconds, the same for all grass objects.", + " */", + " update(elapsedSeconds, time, camera) {", + " if (this.placement.shouldPlaceAgain(elapsedSeconds, this._getPlacementKey())) {", + " this.rebuild();", + " }", + "", + " this.uniforms.grassTime.value = time;", + " this.uniforms.grassWorldScale.value = getWorldScale(this.object);", + " // The object transformation is applied just before rendering, it's needed now.", + " this.object.getRenderer().ensureUpToDate();", + " this.group.updateWorldMatrix(true, false);", + " this.uniforms.grassWorldToLocal.value.setFromMatrix4(this._matrix.copy(this.group.matrixWorld).invert());", + " this._updateBendingObjects();", + " if (!camera) return;", + "", + " // Far chunks are hidden, and thinned out before (in 3D world units).", + " const fadeDistance = Math.max(this.object._getFadeDistance(), 1) / getWorldScale(this.object);", + " this._cameraPosition.setFromMatrixPosition(camera.matrixWorld);", + " for (const chunk of this.chunks) {", + " const distance = this._chunkPosition", + " .copy(chunk.center)", + " .applyMatrix4(this.group.matrixWorld)", + " .distanceTo(this._cameraPosition);", + " const density = gdjs.evtTools.common.clamp((fadeDistance - distance) / (fadeDistance * 0.5), 0, 1);", + " chunk.mesh.visible = density > 0;", + " chunk.mesh.geometry.instanceCount = Math.ceil(chunk.blades * density);", + " }", + " }", + "", + " dispose() {", + " for (const chunk of this.chunks) {", + " chunk.mesh.geometry.dispose();", + " }", + " this.bladeGeometry.dispose();", + " this.material.dispose();", + " this.group.removeFromParent();", + " }", + "}", + "", + "gdjs.__terrainGrassExtension = { Grass };" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [], + "description": "Define grass helper classes JavaScript code.", + "private": true + }, + { + "fullName": "Define scattered models helper classes", + "functionType": "Action", + "name": "DefineScatterHelperClasses", + "sentence": "Define scattered models helper classes JavaScript code", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (gdjs.__terrainScatterExtension) {", + " return;", + "}", + "// Copies grow on the grounds of the scene, like the terrain grass (see", + "// `getGrounds` in the terrain helper).", + "", + "const {", + " getWorldScale,", + " getGrounds,", + " patchShaderCode,", + " createRandom,", + " findHighestGround,", + " GroundPlacement,", + " SampleRectangle,", + " brushes,", + " codec,", + " svgIcon,", + " toolIcons,", + "} = gdjs.__terrain3DExtension;", + "", + "// Size of the object inner area: instances are scaled from it.", + "const AREA_SIZE = 2000;", + "const AREA_DEPTH = 200;", + "// The painted density is stored in a grid of this many cells on each side,", + "// whatever the size of the object.", + "const MASK_RESOLUTION = 256;", + "const MASK_FORMAT_VERSION = '1';", + "// Copies are hidden in chunks: big chunks mean fewer draw calls, small ones", + "// hide more copies out of the screen.", + "const MIN_CHUNK_SIZE = 500;", + "const MAX_CHUNK_SIZE = 2000;", + "// Copies shrink to nothing over this part of the draw distance, instead of popping out.", + "const FADE_PART = 0.15;", + "const MAX_COPIES = 200000;", + "// Prisms of the trunk collisions.", + "const TRUNK_SIDES = 6;", + "", + "const clamp01 = (value) => (value < 0 ? 0 : value > 1 ? 1 : value);", + "", + "/**", + " * @returns {number} A number for a cell of the copies grid, changing with the seed.", + " */", + "const hashCell = (seed, i, j) => {", + " let h = Math.imul(i, 374761393) + Math.imul(j, 668265263) + Math.imul(seed, 982451653);", + " h = Math.imul(h ^ (h >>> 13), 1274126177);", + " return (h ^ (h >>> 16)) >>> 0;", + "};", + "", + "/**", + " * How dense copies are, from 0 to 1, in a grid covering the object: painted", + " * with the editor brushes, the edits or events.", + " */", + "class DensityMask {", + " /** @param {number} fillValue From 0 to 255. */", + " constructor(fillValue) {", + " // `resolution` and `size` let terrain brushes paint it.", + " this.resolution = MASK_RESOLUTION;", + " this.size = MASK_RESOLUTION + 1;", + " this.values = new Uint8Array(this.size * this.size).fill(fillValue);", + " }", + "", + " /**", + " * @param {number} u From 0 to 1, along the object width.", + " * @param {number} v From 0 to 1, along the object height.", + " * @returns {number} The density, from 0 to 1, interpolated between samples.", + " */", + " sample(u, v) {", + " const x = clamp01(u) * this.resolution;", + " const y = clamp01(v) * this.resolution;", + " const i = Math.min(Math.floor(x), this.resolution - 1);", + " const j = Math.min(Math.floor(y), this.resolution - 1);", + " const fx = x - i;", + " const fy = y - j;", + " const value = (i, j) => this.values[j * this.size + i];", + " const top = value(i, j) + fx * (value(i + 1, j) - value(i, j));", + " const bottom = value(i, j + 1) + fx * (value(i + 1, j + 1) - value(i, j + 1));", + " return (top + fy * (bottom - top)) / 255;", + " }", + "", + " /**", + " * Bring the density towards a target around a segment: fully up to 70%", + " * of the radius, then fading out (forests have dense centers and soft edges).", + " * @param {{cellWidth: number, cellHeight: number, ax: number, ay: number, bx: number, by: number, radius: number}} stroke", + " * @param {number} density The target, from 0 to 1.", + " * @param {number} strength From 0 to 1.", + " * @param {SampleRectangle} changedSamples", + " */", + " paint(stroke, density, strength, changedSamples) {", + " const { values } = this;", + " const target = Math.round(clamp01(density) * 255);", + " brushes.forEachSample(", + " this,", + " stroke,", + " (index, falloff) => {", + " // The falloff of terrain brushes is (1 - distance² / radius²)².", + " const weight = clamp01(2 * Math.sqrt(falloff));", + " // Rounded towards the target, so that light strokes still paint.", + " const change = (target - values[index]) * clamp01(weight * strength);", + " values[index] += change > 0 ? Math.ceil(change) : Math.floor(change);", + " },", + " changedSamples", + " );", + " }", + "", + " /** @returns {DensityMask} */", + " clone() {", + " const mask = new DensityMask(0);", + " mask.values.set(this.values);", + " return mask;", + " }", + "}", + "", + "/**", + " * Edits written in the Edits property: a JSON list of brush strokes, like", + " * `[{\"tool\": \"add\", \"x\": 0.5, \"y\": 0.5, \"radius\": 0.2}]`. Positions and", + " * radius are fractions of the object size, like the terrain edits.", + " */", + "const applyEdits = (mask, text) => {", + " if (!text.trim()) return;", + " let editList;", + " try {", + " editList = JSON.parse(text);", + " } catch (error) {", + " console.warn('The edits of scattered models are not valid JSON: ' + error.message);", + " return;", + " }", + " if (!Array.isArray(editList)) {", + " console.warn('The edits of scattered models must be a JSON list (like [{\"tool\": \"add\", ...}]).');", + " return;", + " }", + " const numberOr = (value, defaultValue) => (typeof value === 'number' && isFinite(value) ? value : defaultValue);", + " const changedSamples = new SampleRectangle();", + " for (const edit of editList) {", + " if (!edit || typeof edit !== 'object') continue;", + " const x = numberOr(edit.x, 0.5);", + " const y = numberOr(edit.y, 0.5);", + " const stroke = {", + " cellWidth: 1 / mask.resolution,", + " cellHeight: 1 / mask.resolution,", + " ax: x,", + " ay: y,", + " bx: numberOr(edit.toX, x),", + " by: numberOr(edit.toY, y),", + " radius: numberOr(edit.radius, 0.1),", + " };", + " const strength = clamp01(numberOr(edit.strength, 1));", + " if (edit.tool === 'add') {", + " mask.paint(stroke, numberOr(edit.density, 1), strength, changedSamples);", + " } else if (edit.tool === 'erase') {", + " mask.paint(stroke, 0, strength, changedSamples);", + " } else {", + " console.warn('Unknown tool in the edits of scattered models: \"' + edit.tool + '\" (use add or erase).');", + " }", + " }", + "};", + "", + "/**", + " * The density painted in the editor, stored as its difference with the", + " * density from the properties (the coverage and the edits), like the terrain", + " * sculpt data.", + " */", + "const maskCodec = {", + " /**", + " * @param {DensityMask} mask", + " * @param {DensityMask} baseMask", + " */", + " encode(mask, baseMask) {", + " const changes = new Int32Array(mask.values.length);", + " let hasChanges = false;", + " for (let index = 0; index < changes.length; index++) {", + " changes[index] = mask.values[index] - baseMask.values[index];", + " if (changes[index] !== 0) hasChanges = true;", + " }", + " if (!hasChanges) return '';", + " return MASK_FORMAT_VERSION + ';' + MASK_RESOLUTION + ';' + codec.encodeGrids([changes], mask.size);", + " },", + "", + " /**", + " * @param {string} text", + " * @param {DensityMask} baseMask", + " * @returns {DensityMask | null} null if the text is empty or not valid.", + " */", + " decode(text, baseMask) {", + " if (!text) return null;", + " const parts = text.split(';');", + " if (parts[0] !== MASK_FORMAT_VERSION || parseInt(parts[1], 10) !== MASK_RESOLUTION) return null;", + " try {", + " const mask = baseMask.clone();", + " const [changes] = codec.decodeGrids(parts[2], mask.size, 1);", + " for (let index = 0; index < changes.length; index++) {", + " // The base may have changed since the data was saved.", + " mask.values[index] = Math.min(Math.max(mask.values[index] + changes[index], 0), 255);", + " }", + " return mask;", + " } catch (error) {", + " console.error('The painted data of scattered models could not be read:', error);", + " return null;", + " }", + " },", + "};", + "", + "const fadeShaderDeclarations = `", + "uniform float scatterDrawDistance;", + "`;", + "// Instance matrices are applied after this: copies shrink towards their base.", + "const fadeShaderCode = `", + "#ifdef USE_INSTANCING", + "vec3 scatterCopyPosition = (modelMatrix * instanceMatrix * vec4(0.0, 0.0, 0.0, 1.0)).xyz;", + "transformed *= clamp(", + " (scatterDrawDistance - distance(scatterCopyPosition, cameraPosition)) / (scatterDrawDistance * ${FADE_PART}),", + " 0.0,", + " 1.0", + ");", + "#endif", + "`;", + "", + "const flipTriangles = (geometry) => {", + " const index = geometry.index;", + " for (let offset = 0; offset + 2 < index.count; offset += 3) {", + " const second = index.getX(offset + 1);", + " index.setX(offset + 1, index.getX(offset + 2));", + " index.setX(offset + 2, second);", + " }", + "};", + "", + "/**", + " * The parts of a model, ready to be drawn with instancing: each glTF", + " * primitive with its material. Geometries are moved so that the model base", + " * is at the origin, its height is 1 and it stands along Z (like 3D model", + " * objects with a 90° rotation around X).", + " * @param {THREE.Object3D} modelScene", + " * @param {object} uniforms Uniforms added to the materials.", + " * @returns {{parts: Array<{geometry: THREE.BufferGeometry, material: THREE.Material | THREE.Material[]}>, width: number, depth: number}}", + " */", + "const createModelParts = (modelScene, uniforms) => {", + " modelScene.updateMatrixWorld(true);", + " const rootInverse = modelScene.matrixWorld.clone().invert();", + " const standUp = new THREE.Matrix4().makeRotationX(Math.PI / 2);", + " /** @type {Array<{mesh: THREE.Mesh, matrix: THREE.Matrix4}>} */", + " const meshes = [];", + " const box = new THREE.Box3();", + " modelScene.traverse((node) => {", + " if (!node.isMesh || !node.geometry || !node.geometry.attributes.position) return;", + " const matrix = standUp.clone().multiply(rootInverse).multiply(node.matrixWorld);", + " if (!node.geometry.boundingBox) node.geometry.computeBoundingBox();", + " box.union(node.geometry.boundingBox.clone().applyMatrix4(matrix));", + " meshes.push({ mesh: node, matrix });", + " });", + " if (box.isEmpty()) return { parts: [], width: 0, depth: 0 };", + "", + " const height = Math.max(box.max.z - box.min.z, 0.000001);", + " // The Y axis of the scene goes down: the model is flipped, like 3D model objects.", + " const normalization = new THREE.Matrix4()", + " .makeScale(1 / height, -1 / height, 1 / height)", + " .multiply(", + " new THREE.Matrix4().makeTranslation(", + " -(box.min.x + box.max.x) / 2,", + " -(box.min.y + box.max.y) / 2,", + " -box.min.z", + " )", + " );", + " const parts = meshes.map(({ mesh, matrix }) => {", + " let geometry = mesh.geometry.clone();", + " if (!geometry.index) {", + " const indices = Array.from({ length: geometry.attributes.position.count }, (_, index) => index);", + " geometry.setIndex(indices);", + " }", + " geometry.applyMatrix4(normalization.clone().multiply(matrix));", + " // The flip turned the faces inside out.", + " flipTriangles(geometry);", + " geometry.computeBoundingSphere();", + " geometry.computeBoundingBox();", + " const patchMaterial = (originalMaterial) => {", + " const material = originalMaterial.clone();", + " // Like 3D model objects (\"Standard without metalness\"), which look better without an environment map.", + " if (material.metalness) material.metalness = 0;", + " material.onBeforeCompile = (shader) => {", + " Object.assign(shader.uniforms, uniforms);", + " shader.vertexShader = patchShaderCode(shader.vertexShader, [", + " ['#include ', '#include \\n' + fadeShaderDeclarations],", + " ['#include ', '#include \\n' + fadeShaderCode],", + " ], 'ScatteredModels');", + " };", + " material.customProgramCacheKey = () => 'ScatteredModels';", + " return material;", + " };", + " const material = Array.isArray(mesh.material) ? mesh.material.map(patchMaterial) : patchMaterial(mesh.material);", + " return { geometry, material };", + " });", + " return {", + " parts,", + " width: (box.max.x - box.min.x) / height,", + " depth: (box.max.y - box.min.y) / height,", + " };", + "};", + "", + "/**", + " * A copy of the model: its base position in the scene, its scale (the", + " * height of the copy) and the key of its cell in the grid of copies.", + " * @typedef {{x: number, y: number, z: number, angle: number, height: number, key: number}} Copy", + " */", + "", + "/**", + " * The scattered models of an object: copies of a 3D model placed on the", + " * grounds of the scene, drawn with GPU instancing in chunks that are hidden", + " * when far or off-screen.", + " */", + "class Scatter {", + " /** @param {gdjs.CustomRuntimeObject3D} object */", + " constructor(object) {", + " this.object = object;", + " this.group = new THREE.Group();", + " object.get3DRendererObject().add(this.group);", + " /** @type {Array<{firstCellI: number, firstCellJ: number, meshes: THREE.InstancedMesh[], copies: Copy[], center: THREE.Vector3, radius: number}>} */", + " this.chunks = [];", + " /**", + " * The grid of copies: one copy at most in each cell, cells grouped in chunks.", + " * @type {{spacing: number, cellsPerChunk: number, cellsX: number, cellsY: number} | null}", + " */", + " this.grid = null;", + " this.placement = new GroundPlacement(object.getRuntimeScene());", + " this.uniforms = {", + " // In 3D world units.", + " scatterDrawDistance: { value: 1 },", + " };", + " /** @type {{parts: Array<{geometry: THREE.BufferGeometry, material: THREE.Material | THREE.Material[]}>, width: number, depth: number}} */", + " this.model = { parts: [], width: 0, depth: 0 };", + " this.modelResourceName = '';", + " this.loadingModelResourceName = '';", + " /**", + " * Copies removed by events (for example, cut trees), by key.", + " * @type {Map}", + " */", + " this.removedCopies = new Map();", + " /** Increased each time copies change, for the collisions. */", + " this.version = 0;", + " this.isDisposed = false;", + " /** The last value of the painted data property read or saved. */", + " this.paintData = '';", + " /**", + " * Values saved by the editor tools: the editor sends them back with a", + " * hot-reload, possibly after newer ones were saved.", + " * @type {string[]}", + " */", + " this.savedPaintData = [];", + " this.maskSource = '';", + " /** @type {DensityMask} */", + " this.baseMask = new DensityMask(255);", + " /** @type {DensityMask} */", + " this.mask = this.baseMask;", + " this.changedSamples = new SampleRectangle();", + " this._stroke = { cellWidth: 1, cellHeight: 1, ax: 0, ay: 0, bx: 0, by: 0, radius: 0 };", + " this._groundAtCopy = { ground: null, z: 0 };", + " this._matrix = new THREE.Matrix4();", + " this._raycastSphere = new THREE.Sphere();", + " this._raycastRay = new THREE.Ray();", + " this._raycastBox = new THREE.Box3();", + " this._raycastInverseMatrix = new THREE.Matrix4();", + " this._raycastScale = new THREE.Vector3();", + " /** Placed on a copy to test the triangles of the model. */", + " this._raycastMesh = new THREE.Mesh();", + " /** @type {THREE.Intersection[]} */", + " this._raycastHits = [];", + " this._color = new THREE.Color();", + " this._tint = new THREE.Color();", + " this._cameraPosition = new THREE.Vector3();", + " this._chunkPosition = new THREE.Vector3();", + " /**", + " * Trunks, for the 3D physics behavior and navigation meshes. Always given,", + " * empty without trunk collisions: otherwise these behaviors would use the", + " * object box, covering the whole area.", + " * @type {gdjs.Surface}", + " */", + " this.surface = {", + " getVersion: () => this.version,", + " getChangedArea: () => null,", + " getTriangles: () => this._getTrunkTriangles(),", + " getHeightField: () => null,", + " };", + " /** The trunk collision properties, changing the surface. */", + " this.trunkKey = '';", + " this.loadMaskFromProperties();", + " this.updateFromProperties();", + " // Before the behaviors create their shapes.", + " this._updateSurface();", + " }", + "", + " _getMaskSource() {", + " return this.object._getCoverage() + '|' + this.object._getEdits();", + " }", + "", + " loadMaskFromProperties() {", + " const { object } = this;", + " this.maskSource = this._getMaskSource();", + " this.paintData = object._getPaintData();", + " this.baseMask = new DensityMask(object._getCoverage() === 'Painted' ? 0 : 255);", + " applyEdits(this.baseMask, object._getEdits());", + " this.mask = maskCodec.decode(this.paintData, this.baseMask) || this.baseMask.clone();", + " this.placement.placeAgain();", + " }", + "", + " onHotReloading() {", + " const paintData = this.object._getPaintData();", + " const savedIndex = this.savedPaintData.indexOf(paintData);", + " if (savedIndex !== -1 && this._getMaskSource() === this.maskSource) {", + " // The object already has this data, or newer changes.", + " this.savedPaintData.splice(0, savedIndex + 1);", + " } else if (paintData !== this.paintData || this._getMaskSource() !== this.maskSource) {", + " this.savedPaintData.length = 0;", + " this.loadMaskFromProperties();", + " }", + " this.updateFromProperties();", + " }", + "", + " /** @returns {string} The data to save in the painted data property. */", + " save() {", + " this.paintData = maskCodec.encode(this.mask, this.baseMask);", + " this.savedPaintData.push(this.paintData);", + " return this.paintData;", + " }", + "", + " updateFromProperties() {", + " const { object } = this;", + " const modelResourceName = object._getModel();", + " if (modelResourceName !== this.modelResourceName) {", + " this.modelResourceName = modelResourceName;", + " this._loadModel();", + " }", + " this.placement.placeAgain();", + " }", + "", + " _disposeModel() {", + " for (const part of this.model.parts) {", + " part.geometry.dispose();", + " for (const material of Array.isArray(part.material) ? part.material : [part.material]) {", + " material.dispose();", + " }", + " }", + " this.model = { parts: [], width: 0, depth: 0 };", + " }", + "", + " _loadModel() {", + " this._disposeChunks(this.chunks);", + " this._disposeModel();", + " const resourceName = this.modelResourceName;", + " if (!resourceName) return;", + " const game = this.object.getRuntimeScene().getGame();", + " const modelManager = game.getModel3DManager();", + " const gltf = modelManager.getModel(resourceName);", + " if (gltf === modelManager._invalidModel) {", + " // Not loaded yet, for example a model just chosen in the editor.", + " if (this.loadingModelResourceName === resourceName) return;", + " this.loadingModelResourceName = resourceName;", + " game.getResourceLoader()", + " .loadResource(resourceName)", + " .then(() => {", + " if (this.isDisposed || this.modelResourceName !== resourceName) return;", + " this.loadingModelResourceName = '';", + " if (modelManager.getModel(resourceName) !== modelManager._invalidModel) {", + " this._loadModel();", + " this.placement.placeAgain();", + " }", + " })", + " .catch(() => {});", + " return;", + " }", + " this.model = createModelParts(gltf.scene, this.uniforms);", + " }", + "", + " _getPlacementKey() {", + " const { object } = this;", + " return [", + " object.getX(), object.getY(), object.getZ(), object.getWidth(), object.getHeight(), object.getDepth(),", + " object._getDensity(), object._getSize(), object._getMinScale(), object._getMaxScale(),", + " object._getRandomRotation(), object._getRandomTint(), object._getGroundLayer(), object._getMaxSlope(),", + " object._getMinGroundZ(), object._getMaxGroundZ(), object._getSeed(), object._getDrawDistance(),", + " object._getCastShadow(), object._getReceiveShadow(), this.model.parts.length,", + " ].join('|');", + " }", + "", + " _getChunkSize() {", + " return Math.min(Math.max(this.object._getDrawDistance() / 4, MIN_CHUNK_SIZE), MAX_CHUNK_SIZE);", + " }", + "", + " /** @returns {number} The distance between copies (one copy per cell of this size at most). */", + " _getSpacing() {", + " const { object } = this;", + " const density = Math.max(object._getDensity(), 0);", + " if (density <= 0) return 0;", + " const spacing = 100 / Math.sqrt(density);", + " // Too many cells would freeze the game: the density is lowered.", + " const cellCount = (object.getWidth() / spacing) * (object.getHeight() / spacing);", + " return cellCount > MAX_COPIES ? spacing * Math.sqrt(cellCount / MAX_COPIES) : spacing;", + " }", + "", + " /**", + " * Find the copy of a cell of the grid, if there is one.", + " * @returns {Copy | null}", + " */", + " _placeCopy(cellI, cellJ, spacing, grounds) {", + " const { object } = this;", + " const width = object.getWidth();", + " const height = object.getHeight();", + " const nextRandom = createRandom(hashCell(Math.floor(object._getSeed()), cellI, cellJ));", + " // Always drawn in the same order, so that a copy stays the same when properties change.", + " const x = (cellI + nextRandom()) * spacing;", + " const y = (cellJ + nextRandom()) * spacing;", + " const presence = nextRandom();", + " const growth = nextRandom();", + " const scaleFactor = nextRandom();", + " const angle = nextRandom() * Math.PI * 2;", + " if (x > width || y > height) return null;", + " if (presence >= this.mask.sample(x / width, y / height)) return null;", + " const key = cellJ * 65536 + cellI;", + " if (this.removedCopies.has(key)) return null;", + "", + " const sceneX = object.getX() + x;", + " const sceneY = object.getY() + y;", + " const { ground, z } = findHighestGround(grounds, sceneX, sceneY, this._groundAtCopy);", + " const groundZ = ground ? z : object.getZ();", + " if (groundZ < object._getMinGroundZ() || groundZ > object._getMaxGroundZ()) return null;", + " const copyHeight =", + " Math.max(object._getSize(), 0) *", + " (object._getMinScale() + (object._getMaxScale() - object._getMinScale()) * scaleFactor);", + " if (ground) {", + " if (ground.getSlopeAt(sceneX, sceneY) > object._getMaxSlope()) return null;", + " const groundLayer = parseInt(object._getGroundLayer(), 10) || 0;", + " if (groundLayer > 0) {", + " if (growth > ground.getLayerWeightAt(sceneX, sceneY, groundLayer)) return null;", + " // Copies don't stand at the border of their layer, so that trees don't cover roads.", + " const margin = (copyHeight * Math.max(this.model.width, this.model.depth)) / 4;", + " for (const [offsetX, offsetY] of [[margin, 0], [-margin, 0], [0, margin], [0, -margin]]) {", + " if (ground.getLayerWeightAt(sceneX + offsetX, sceneY + offsetY, groundLayer) < 0.5) return null;", + " }", + " }", + " }", + " return {", + " x: sceneX,", + " y: sceneY,", + " z: groundZ,", + " angle: object._getRandomRotation() ? angle : 0,", + " height: copyHeight,", + " key,", + " };", + " }", + "", + " _disposeChunks(chunks) {", + " for (const chunk of chunks) {", + " for (const mesh of chunk.meshes) {", + " mesh.removeFromParent();", + " // Geometries and materials are the ones of the model.", + " mesh.dispose();", + " }", + " }", + " if (chunks === this.chunks) this.chunks = [];", + " }", + "", + " /** Place the copies again, for example after the ground or the object changed. */", + " rebuild() {", + " this._disposeChunks(this.chunks);", + " this.version++;", + " const { object } = this;", + " const spacing = this._getSpacing();", + " if (spacing <= 0 || this.model.parts.length === 0) {", + " this.grid = null;", + " return;", + " }", + " const cellsPerChunk = Math.max(Math.round(this._getChunkSize() / spacing), 1);", + " const grid = {", + " spacing,", + " cellsPerChunk,", + " cellsX: Math.ceil(object.getWidth() / spacing),", + " cellsY: Math.ceil(object.getHeight() / spacing),", + " };", + " this.grid = grid;", + " for (let firstCellJ = 0; firstCellJ < grid.cellsY; firstCellJ += cellsPerChunk) {", + " for (let firstCellI = 0; firstCellI < grid.cellsX; firstCellI += cellsPerChunk) {", + " this._createChunk(firstCellI, firstCellJ);", + " }", + " }", + " }", + "", + " /**", + " * Place again the copies of the chunks touching a rectangle of the scene,", + " * for example after painting.", + " */", + " _rebuildArea(minX, minY, maxX, maxY) {", + " const { grid, object } = this;", + " if (!grid) return;", + " const chunkSize = grid.spacing * grid.cellsPerChunk;", + " const firstChunkI = Math.max(Math.floor((minX - object.getX()) / chunkSize), 0);", + " const firstChunkJ = Math.max(Math.floor((minY - object.getY()) / chunkSize), 0);", + " const lastChunkI = Math.min(Math.floor((maxX - object.getX()) / chunkSize), Math.ceil(grid.cellsX / grid.cellsPerChunk) - 1);", + " const lastChunkJ = Math.min(Math.floor((maxY - object.getY()) / chunkSize), Math.ceil(grid.cellsY / grid.cellsPerChunk) - 1);", + " if (firstChunkI > lastChunkI || firstChunkJ > lastChunkJ) return;", + " const isInArea = (chunk) => {", + " const chunkI = chunk.firstCellI / grid.cellsPerChunk;", + " const chunkJ = chunk.firstCellJ / grid.cellsPerChunk;", + " return chunkI >= firstChunkI && chunkI <= lastChunkI && chunkJ >= firstChunkJ && chunkJ <= lastChunkJ;", + " };", + " this._disposeChunks(this.chunks.filter(isInArea));", + " this.chunks = this.chunks.filter((chunk) => !isInArea(chunk));", + " for (let chunkJ = firstChunkJ; chunkJ <= lastChunkJ; chunkJ++) {", + " for (let chunkI = firstChunkI; chunkI <= lastChunkI; chunkI++) {", + " this._createChunk(chunkI * grid.cellsPerChunk, chunkJ * grid.cellsPerChunk);", + " }", + " }", + " this.version++;", + " }", + "", + " _createChunk(firstCellI, firstCellJ) {", + " const { grid } = this;", + " const grounds = Array.from(this.placement.grounds);", + " /** @type {Copy[]} */", + " const copies = [];", + " for (let cellJ = firstCellJ; cellJ < Math.min(firstCellJ + grid.cellsPerChunk, grid.cellsY); cellJ++) {", + " for (let cellI = firstCellI; cellI < Math.min(firstCellI + grid.cellsPerChunk, grid.cellsX); cellI++) {", + " const copy = this._placeCopy(cellI, cellJ, grid.spacing, grounds);", + " if (copy) copies.push(copy);", + " }", + " }", + " if (copies.length === 0) return;", + "", + " const { object } = this;", + " // Copies are placed in scene units, and the object group is scaled", + " // from its inner area: they are scaled back to keep their proportions.", + " const scaleX = Math.max(object.getWidth() / AREA_SIZE, 0.0001);", + " const scaleY = Math.max(object.getHeight() / AREA_SIZE, 0.0001);", + " const scaleZ = Math.max(object.getDepth() / AREA_DEPTH, 0.0001);", + " const matrices = new Float32Array(copies.length * 16);", + " const tintColor = gdjs.rgbOrHexStringToNumber(object._getRandomTint());", + " const hasTint = tintColor !== 0xffffff;", + " const colors = hasTint ? new Float32Array(copies.length * 3) : null;", + " this._tint.setHex(tintColor);", + " const min = new THREE.Vector3(Infinity, Infinity, Infinity);", + " const max = new THREE.Vector3(-Infinity, -Infinity, -Infinity);", + " copies.forEach((copy, index) => {", + " const cos = Math.cos(copy.angle) * copy.height;", + " const sin = Math.sin(copy.angle) * copy.height;", + " const localX = (copy.x - object.getX()) / scaleX;", + " const localY = (copy.y - object.getY()) / scaleY;", + " const localZ = (copy.z - object.getZ()) / scaleZ;", + " this._matrix.set(", + " cos / scaleX, -sin / scaleX, 0, localX,", + " sin / scaleY, cos / scaleY, 0, localY,", + " 0, 0, copy.height / scaleZ, localZ,", + " 0, 0, 0, 1", + " );", + " this._matrix.toArray(matrices, index * 16);", + " if (colors) {", + " // The tint of each copy is between white and the tint color.", + " this._color.setRGB(1, 1, 1).lerp(this._tint, createRandom(copy.key + 1)());", + " this._color.toArray(colors, index * 3);", + " }", + " const reach = (copy.height * Math.max(this.model.width, this.model.depth)) / 2;", + " min.min(this._chunkPosition.set(copy.x - reach, copy.y - reach, copy.z));", + " max.max(this._chunkPosition.set(copy.x + reach, copy.y + reach, copy.z + copy.height));", + " });", + " const instanceMatrix = new THREE.InstancedBufferAttribute(matrices, 16);", + " const instanceColor = colors ? new THREE.InstancedBufferAttribute(colors, 3) : null;", + " const castShadow = object._getCastShadow();", + " const receiveShadow = object._getReceiveShadow();", + " const meshes = this.model.parts.map((part) => {", + " const mesh = new THREE.InstancedMesh(part.geometry, part.material, 0);", + " // All the parts of the model share the positions of the copies.", + " mesh.instanceMatrix = instanceMatrix;", + " mesh.instanceColor = instanceColor;", + " mesh.count = copies.length;", + " mesh.computeBoundingSphere();", + " mesh.castShadow = castShadow;", + " mesh.receiveShadow = receiveShadow;", + " // The first mesh tests all the parts of the copies.", + " mesh.raycast = () => {};", + " this.group.add(mesh);", + " return mesh;", + " });", + " const center = new THREE.Vector3(", + " ((min.x + max.x) / 2 - object.getX()) / scaleX,", + " ((min.y + max.y) / 2 - object.getY()) / scaleY,", + " ((min.z + max.z) / 2 - object.getZ()) / scaleZ", + " );", + " const chunk = { firstCellI, firstCellJ, meshes, copies, center, radius: min.distanceTo(max) / 2 };", + " // Used by the scene editor to select the object when a copy is clicked.", + " meshes[0].raycast = (raycaster, intersects) => this._raycastChunk(chunk, raycaster, intersects);", + " this.chunks.push(chunk);", + " }", + "", + " /**", + " * Add the hits of a ray on the copies of a chunk, like they are drawn. There", + " * can be thousands of copies: only the ones whose box is crossed by the ray", + " * are tested with the triangles of the model.", + " * @param {{meshes: THREE.InstancedMesh[], copies: Copy[], center: THREE.Vector3, radius: number}} chunk", + " * @param {THREE.Raycaster} raycaster", + " * @param {THREE.Intersection[]} intersects", + " */", + " _raycastChunk(chunk, raycaster, intersects) {", + " const { object, group } = this;", + " this._raycastSphere.center.copy(chunk.center).applyMatrix4(group.matrixWorld);", + " this._raycastSphere.radius = chunk.radius / getWorldScale(object);", + " if (!raycaster.ray.intersectsSphere(this._raycastSphere)) return;", + "", + " // The ray in the scene coordinates, like the copies.", + " const scale = this._raycastScale.set(", + " Math.max(object.getWidth() / AREA_SIZE, 0.0001),", + " Math.max(object.getHeight() / AREA_SIZE, 0.0001),", + " Math.max(object.getDepth() / AREA_DEPTH, 0.0001)", + " );", + " const ray = this._raycastRay.copy(raycaster.ray).applyMatrix4(this._raycastInverseMatrix.copy(group.matrixWorld).invert());", + " ray.origin.multiply(scale).add(this._chunkPosition.set(object.getX(), object.getY(), object.getZ()));", + " ray.direction.multiply(scale);", + "", + " const reachPerHeight = Math.max(this.model.width, this.model.depth) / 2;", + " const instanceMatrices = chunk.meshes[0].instanceMatrix.array;", + " chunk.copies.forEach((copy, index) => {", + " const reach = copy.height * reachPerHeight;", + " this._raycastBox.min.set(copy.x - reach, copy.y - reach, copy.z);", + " this._raycastBox.max.set(copy.x + reach, copy.y + reach, copy.z + copy.height);", + " if (!ray.intersectsBox(this._raycastBox)) return;", + " this._raycastMesh.matrixWorld.fromArray(instanceMatrices, index * 16).premultiply(group.matrixWorld);", + " for (const mesh of chunk.meshes) {", + " this._raycastMesh.geometry = mesh.geometry;", + " this._raycastMesh.material = mesh.material;", + " this._raycastHits.length = 0;", + " this._raycastMesh.raycast(raycaster, this._raycastHits);", + " for (const hit of this._raycastHits) {", + " hit.object = mesh;", + " hit.instanceId = index;", + " intersects.push(hit);", + " }", + " }", + " });", + " }", + "", + " /** @param {THREE.Camera | null} camera */", + " _hideFarChunks(camera) {", + " if (!camera) return;", + " const worldScale = getWorldScale(this.object);", + " const drawDistance = Math.max(this.object._getDrawDistance(), 1) / worldScale;", + " this.uniforms.scatterDrawDistance.value = drawDistance;", + " this._cameraPosition.setFromMatrixPosition(camera.matrixWorld);", + " for (const chunk of this.chunks) {", + " const distance = this._chunkPosition", + " .copy(chunk.center)", + " .applyMatrix4(this.group.matrixWorld)", + " .distanceTo(this._cameraPosition);", + " const isVisible = distance - chunk.radius / worldScale < drawDistance;", + " for (const mesh of chunk.meshes) mesh.visible = isVisible;", + " }", + " }", + "", + " /**", + " * @param {number} elapsedSeconds", + " * @param {THREE.Camera | null} camera", + " */", + " update(elapsedSeconds, camera) {", + " if (this.placement.shouldPlaceAgain(elapsedSeconds, this._getPlacementKey())) {", + " this.rebuild();", + " }", + " if (!this.changedSamples.isEmpty()) {", + " const { object } = this;", + " const sampleWidth = object.getWidth() / MASK_RESOLUTION;", + " const sampleHeight = object.getHeight() / MASK_RESOLUTION;", + " const { minI, minJ, maxI, maxJ } = this.changedSamples;", + " // Copies between samples use the density of the samples around them.", + " this._rebuildArea(", + " object.getX() + (minI - 1) * sampleWidth,", + " object.getY() + (minJ - 1) * sampleHeight,", + " object.getX() + (maxI + 1) * sampleWidth,", + " object.getY() + (maxJ + 1) * sampleHeight", + " );", + " this.changedSamples.clear();", + " }", + " // The object transformation is applied just before rendering, it's needed now.", + " this.object.getRenderer().ensureUpToDate();", + " this.group.updateWorldMatrix(true, false);", + " this._hideFarChunks(camera);", + " this._updateSurface();", + " }", + "", + " _updateSurface() {", + " const { object } = this;", + " if (typeof object.setSurface !== 'function') return;", + " const trunkKey = object._getTrunkCollisions() + '|' + object._getTrunkDiameter();", + " if (trunkKey !== this.trunkKey) {", + " this.trunkKey = trunkKey;", + " this.version++;", + " }", + " if (object.getSurface() !== this.surface) object.setSurface(this.surface);", + " }", + "", + " /** @returns {Copy[]} */", + " getCopies() {", + " const copies = [];", + " for (const chunk of this.chunks) copies.push(...chunk.copies);", + " return copies;", + " }", + "", + " /**", + " * @returns {Copy | null} The copy whose base is the nearest to a position (on X and Y), if any.", + " */", + " findNearestCopy(x, y) {", + " let nearestCopy = null;", + " let nearestDistanceSquared = Infinity;", + " for (const chunk of this.chunks) {", + " for (const copy of chunk.copies) {", + " const distanceSquared = (copy.x - x) * (copy.x - x) + (copy.y - y) * (copy.y - y);", + " if (distanceSquared < nearestDistanceSquared) {", + " nearestDistanceSquared = distanceSquared;", + " nearestCopy = copy;", + " }", + " }", + " }", + " return nearestCopy;", + " }", + "", + " getCopyCount() {", + " let count = 0;", + " for (const chunk of this.chunks) count += chunk.copies.length;", + " return count;", + " }", + "", + " /** Remove the copies whose base is in a circle (they come back when the scene is restarted). */", + " removeCopies(x, y, radius) {", + " let hasRemovedCopies = false;", + " for (const chunk of this.chunks) {", + " for (const copy of chunk.copies) {", + " if ((copy.x - x) * (copy.x - x) + (copy.y - y) * (copy.y - y) <= radius * radius) {", + " this.removedCopies.set(copy.key, copy);", + " hasRemovedCopies = true;", + " }", + " }", + " }", + " if (hasRemovedCopies) this._rebuildArea(x - radius, y - radius, x + radius, y + radius);", + " }", + "", + " /** Paint the full density around a position, and bring back the copies removed there. */", + " addCopies(x, y, radius) {", + " for (const [key, copy] of this.removedCopies) {", + " if ((copy.x - x) * (copy.x - x) + (copy.y - y) * (copy.y - y) <= radius * radius) {", + " this.removedCopies.delete(key);", + " }", + " }", + " this.paint(x, y, x, y, radius, 1, 1);", + " }", + "", + " _getStroke(x1, y1, x2, y2, radius) {", + " const { object } = this;", + " const stroke = this._stroke;", + " stroke.cellWidth = object.getWidth() / MASK_RESOLUTION;", + " stroke.cellHeight = object.getHeight() / MASK_RESOLUTION;", + " stroke.ax = x1 - object.getX();", + " stroke.ay = y1 - object.getY();", + " stroke.bx = x2 - object.getX();", + " stroke.by = y2 - object.getY();", + " stroke.radius = Math.max(radius, 0);", + " return stroke;", + " }", + "", + " /**", + " * Paint the density around a segment (copies are placed again at the next frame).", + " * @param {number} density From 0 (erase) to 1 (as dense as the Density property).", + " * @param {number} strength From 0 to 1.", + " */", + " paint(x1, y1, x2, y2, radius, density, strength) {", + " const changedSamples = new SampleRectangle();", + " this.mask.paint(this._getStroke(x1, y1, x2, y2, radius), density, strength, changedSamples);", + " this.changedSamples.add(changedSamples);", + " }", + "", + " /** @param {DensityMask} mask */", + " setMask(mask) {", + " this.mask = mask;", + " this.rebuild();", + " }", + "", + " /**", + " * Prisms around the trunks, from 0 to 1 in the object box (see `gdjs.Surface`).", + " * @returns {gdjs.SurfaceTriangles}", + " */", + " _getTrunkTriangles() {", + " const { object } = this;", + " const copies = object._getTrunkCollisions() ? this.getCopies() : [];", + " const verticesPerCopy = TRUNK_SIDES * 2 + 1;", + " const positions = new Float32Array(copies.length * verticesPerCopy * 3);", + " const indices = new Uint32Array(copies.length * TRUNK_SIDES * 3 * 3);", + " const width = object.getWidth() || 1;", + " const height = object.getHeight() || 1;", + " const depth = object.getDepth() || 1;", + " let indexOffset = 0;", + " copies.forEach((copy, copyIndex) => {", + " const radius = (Math.max(object._getTrunkDiameter(), 0) / 2) * (copy.height / Math.max(object._getSize(), 1));", + " const top = copy.z + copy.height * 0.8;", + " const firstVertex = copyIndex * verticesPerCopy;", + " const setVertex = (vertex, x, y, z) => {", + " positions[vertex * 3] = (x - object.getX()) / width;", + " positions[vertex * 3 + 1] = (y - object.getY()) / height;", + " positions[vertex * 3 + 2] = (z - object.getZ()) / depth;", + " };", + " for (let side = 0; side < TRUNK_SIDES; side++) {", + " const angle = ((side + 0.5) / TRUNK_SIDES) * Math.PI * 2;", + " const x = copy.x + Math.cos(angle) * radius;", + " const y = copy.y + Math.sin(angle) * radius;", + " // Starting a bit under the ground, which can be sloped.", + " setVertex(firstVertex + side * 2, x, y, copy.z - radius);", + " setVertex(firstVertex + side * 2 + 1, x, y, top);", + " }", + " const center = firstVertex + TRUNK_SIDES * 2;", + " setVertex(center, copy.x, copy.y, top);", + " for (let side = 0; side < TRUNK_SIDES; side++) {", + " const bottom = firstVertex + side * 2;", + " const nextBottom = firstVertex + ((side + 1) % TRUNK_SIDES) * 2;", + " // Triangles face out of the prism (vertices at odd indices are at the top).", + " indices.set([bottom, nextBottom, bottom + 1, nextBottom, nextBottom + 1, bottom + 1], indexOffset);", + " indices.set([center, bottom + 1, nextBottom + 1], indexOffset + 6);", + " indexOffset += 9;", + " }", + " });", + " if (copies.length === 0) {", + " // Without triangles, the physics engine and navigation meshes would use", + " // the object box: a tiny triangle under the object is used instead.", + " return { positions: new Float32Array([0, 0, -1, 0.0001, 0, -1, 0, 0.0001, -1]), indices: new Uint32Array([0, 2, 1]) };", + " }", + " return { positions, indices };", + " }", + "", + " dispose() {", + " this.isDisposed = true;", + " if (typeof this.object.setSurface === 'function' && this.object.getSurface() === this.surface) {", + " this.object.setSurface(null);", + " }", + " this._disposeChunks(this.chunks);", + " this._disposeModel();", + " this.group.removeFromParent();", + " }", + "}", + "", + "const scatterToolIcons = {", + " Add: svgIcon(", + " ''", + " ),", + " Erase: svgIcon(", + " ''", + " ),", + "};", + "", + "/**", + " * The brushes shown in the scene editor when scattered models are selected,", + " * to add or erase copies. Without the needed editor functions (older GDevelop", + " * versions), nothing is shown: copies can still be painted with events.", + " */", + "class ScatterEditorTools {", + " constructor() {", + " /** '' when no brush is used. */", + " this.toolName = '';", + " this.sizePercent = 30;", + " this.strengthPercent = 50;", + " /** @type {Scatter | null} */", + " this.scatter = null;", + " /** @type {gdjs.InGameEditor | null} */", + " this.editor = null;", + " this.isStroking = false;", + " this.lastStrokeTime = 0;", + " /** @type {Array<{mask: DensityMask, baseMask: DensityMask}>} */", + " this.undoSteps = [];", + " this.raycaster = new THREE.Raycaster();", + " this.ndc = new THREE.Vector2();", + " this._groundAtCursor = { ground: null, z: 0 };", + " /** A circle following the ground under the cursor. */", + " this.brushRing = new THREE.LineLoop(", + " new THREE.BufferGeometry().setAttribute('position', new THREE.BufferAttribute(new Float32Array(64 * 3), 3)),", + " new THREE.LineBasicMaterial({ color: 0xffffff, depthTest: false, transparent: true })", + " );", + " this.brushRing.renderOrder = 1000;", + " this.brushRing.frustumCulled = false;", + " this.brushRing.raycast = () => {};", + " }", + "", + " /** @param {gdjs.InGameEditor} editor */", + " update(editor) {", + " if (typeof editor.showToolbar !== 'function') return;", + " this.editor = editor;", + " const selectedObjects = editor.getSelectedObjects();", + " const scatter =", + " selectedObjects.length === 1 && selectedObjects[0].__terrainScatter ? selectedObjects[0].__terrainScatter : null;", + " if (scatter !== this.scatter) {", + " this._endStroke();", + " this.scatter = scatter;", + " this.undoSteps.length = 0;", + " }", + " if (scatter) editor.showToolbar('ScatteredModels', this._getToolbarItems());", + " const game = editor.getRuntimeGame();", + " if (!scatter || !this.toolName) {", + " this.brushRing.removeFromParent();", + " return;", + " }", + " const inputManager = game.getInputManager();", + " if (inputManager.wasKeyJustPressed(gdjs.evtTools.input.keysNameToCode.Escape)) {", + " this._endStroke();", + " this.toolName = '';", + " this.brushRing.removeFromParent();", + " return;", + " }", + " editor.captureLeftMouseButton();", + " const hit = this._getPositionUnderCursor(scatter, game);", + " const radius = this._getRadius();", + " this._showBrushRing(scatter, hit, radius);", + "", + " const isPressedForTool =", + " typeof editor.isCapturedLeftMouseButtonPressed === 'function'", + " ? editor.isCapturedLeftMouseButtonPressed()", + " : inputManager.isMouseButtonPressed(0);", + " // The button is released outside the canvas when the mouse is on a toolbar.", + " const isPressed = isPressedForTool && inputManager.isMouseInsideCanvas();", + " if (isPressed && hit && !this.isStroking) {", + " this._startStroke(scatter);", + " }", + " if (this.isStroking && isPressed && hit) {", + " this._applyBrush(scatter, hit, radius);", + " }", + " if (!isPressed) {", + " this._endStroke();", + " }", + " }", + "", + " _getRadius() {", + " const { object } = this.scatter;", + " const maxRadius = Math.max(object.getWidth(), object.getHeight()) / 4;", + " return Math.max(maxRadius * Math.pow(this.sizePercent / 100, 2), 10);", + " }", + "", + " /**", + " * @returns {{x: number, y: number} | null} The position of the scene under the cursor: on the", + " * highest ground hit, or on the plane at the object Z.", + " */", + " _getPositionUnderCursor(scatter, game) {", + " let hit = null;", + " for (const ground of getGrounds(scatter.object.getRuntimeScene())) {", + " if (typeof ground.raycastFromCursor !== 'function') continue;", + " const localHit = ground.raycastFromCursor(game, this.raycaster, this.ndc);", + " if (!localHit) continue;", + " const sceneHit = ground.toScenePosition(localHit);", + " if (!hit || sceneHit.z > hit.z) hit = sceneHit;", + " }", + " if (hit) return hit;", + "", + " const { object } = scatter;", + " const layer = object.getInstanceContainer().getLayer(object.getLayer());", + " const camera = layer.getRenderer().getThreeCamera();", + " if (!camera) return null;", + " const inputManager = game.getInputManager();", + " this.ndc.set(", + " (inputManager.getCursorX() / game.getGameResolutionWidth()) * 2 - 1,", + " -(inputManager.getCursorY() / game.getGameResolutionHeight()) * 2 + 1", + " );", + " this.raycaster.setFromCamera(this.ndc, camera);", + " scatter.group.updateWorldMatrix(true, false);", + " const ray = this.raycaster.ray.clone().applyMatrix4(scatter.group.matrixWorld.clone().invert());", + " const point = ray.intersectPlane(new THREE.Plane(new THREE.Vector3(0, 0, 1), 0), new THREE.Vector3());", + " if (!point) return null;", + " return {", + " x: object.getX() + (point.x / AREA_SIZE) * object.getWidth(),", + " y: object.getY() + (point.y / AREA_SIZE) * object.getHeight(),", + " };", + " }", + "", + " _showBrushRing(scatter, hit, radius) {", + " if (!hit) {", + " this.brushRing.removeFromParent();", + " return;", + " }", + " const { object } = scatter;", + " if (this.brushRing.parent !== scatter.group) scatter.group.add(this.brushRing);", + " const grounds = getGrounds(object.getRuntimeScene());", + " const positions = this.brushRing.geometry.attributes.position;", + " for (let index = 0; index < positions.count; index++) {", + " const angle = (index / positions.count) * Math.PI * 2;", + " const x = hit.x + Math.cos(angle) * radius;", + " const y = hit.y + Math.sin(angle) * radius;", + " const { ground, z } = findHighestGround(grounds, x, y, this._groundAtCursor);", + " positions.setXYZ(", + " index,", + " ((x - object.getX()) / object.getWidth()) * AREA_SIZE,", + " ((y - object.getY()) / object.getHeight()) * AREA_SIZE,", + " (((ground ? z : object.getZ()) + 2 - object.getZ()) / Math.max(object.getDepth(), 0.0001)) * AREA_DEPTH", + " );", + " }", + " positions.needsUpdate = true;", + " }", + "", + " _startStroke(scatter) {", + " this.isStroking = true;", + " this.lastStrokeTime = performance.now();", + " this.undoSteps.push({ mask: scatter.mask.clone(), baseMask: scatter.baseMask });", + " while (this.undoSteps.length > 30) this.undoSteps.shift();", + " }", + "", + " _endStroke() {", + " if (!this.isStroking) return;", + " this.isStroking = false;", + " this._save();", + " }", + "", + " _applyBrush(scatter, hit, radius) {", + " const now = performance.now();", + " const elapsedSeconds = Math.min((now - this.lastStrokeTime) / 1000, 0.1);", + " this.lastStrokeTime = now;", + " const strength = (this.strengthPercent / 100) * elapsedSeconds * 8;", + " scatter.paint(hit.x, hit.y, hit.x, hit.y, radius, this.toolName === 'Add' ? 1 : 0, strength);", + " }", + "", + " _save() {", + " const { scatter, editor } = this;", + " if (!scatter || !editor) return;", + " editor.updateObjectProperties(scatter.object.getName(), { PaintData: scatter.save() });", + " }", + "", + " _undo() {", + " const undoStep = this.undoSteps.pop();", + " // The coverage or the edits may have changed since the stroke.", + " if (!this.scatter || !undoStep || undoStep.baseMask !== this.scatter.baseMask) {", + " this.undoSteps.length = 0;", + " return;", + " }", + " this.scatter.setMask(undoStep.mask);", + " this._save();", + " }", + "", + " /** @returns {Array} */", + " _getToolbarItems() {", + " const toolButton = (toolName, tooltip) => ({", + " type: 'button',", + " id: toolName,", + " tooltip,", + " iconUrl: scatterToolIcons[toolName],", + " isActive: this.toolName === toolName,", + " onClick: () => (this.toolName = this.toolName === toolName ? '' : toolName),", + " });", + " return [", + " toolButton('Add', 'Add copies (drag on the ground)'),", + " toolButton('Erase', 'Erase copies'),", + " { type: 'divider', id: 'BrushDivider' },", + " {", + " type: 'slider',", + " id: 'Size',", + " tooltip: 'Brush size',", + " iconUrl: toolIcons.Size,", + " min: 1,", + " max: 100,", + " value: this.sizePercent,", + " onChange: (value) => (this.sizePercent = value),", + " },", + " {", + " type: 'slider',", + " id: 'Strength',", + " tooltip: 'Brush strength',", + " iconUrl: toolIcons.Strength,", + " min: 5,", + " max: 100,", + " value: this.strengthPercent,", + " onChange: (value) => (this.strengthPercent = value),", + " },", + " {", + " type: 'button',", + " id: 'Undo',", + " tooltip: 'Undo the last stroke on these scattered models',", + " iconUrl: toolIcons.Undo,", + " onClick: () => this._undo(),", + " },", + " ];", + " }", + "}", + "", + "const editorTools = new ScatterEditorTools();", + "if (gdjs.registerInGameEditorPostStepCallback) {", + " gdjs.registerInGameEditorPostStepCallback((editor) => editorTools.update(editor));", + "}", + "", + "gdjs.__terrainScatterExtension = {", + " Scatter,", + " // Exposed for tests.", + " DensityMask,", + " maskCodec,", + " applyEdits,", + "};", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [], + "description": "Define scattered models helper classes JavaScript code.", + "private": true + } + ], + "eventsBasedBehaviors": [], + "eventsBasedObjects": [ + { + "areaMaxX": 4096, + "areaMaxY": 4096, + "areaMaxZ": 600, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "defaultName": "Terrain", + "assetStoreTag": "3d terrains", + "description": "A ground with hills, mountains or valleys that can be sculpted and painted in the scene editor, or changed with events. Add the 3D physics behavior to it to make objects walk or roll on it.", + "fullName": "3D terrain", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXRlcnJhaW4iIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTQsNkwxMC4yNSwxMUwxMy4xLDE0LjhMMTEuNSwxNkM5LjgxLDEzLjc1IDcsMTAgNywxMEwxLDE4SDIzTDE0LDZaIiAvPjwvc3ZnPg==", + "is3D": true, + "isUsingLegacyInstancesRenderer": true, + "name": "Terrain3D", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/10e0a26c0d500830dfe23b94138beb0ef61607ba62bca05a5aa5c849777f1b06_terrain.svg", + "objects": [ + { + "assetStoreId": "", + "name": "AreaPlaceholder", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 4096, + "height": 4096, + "depth": 600, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "Basic", + "tint": "255;255;255", + "frontFaceResourceName": "", + "frontFaceVisible": false, + "frontFaceResourceRepeat": false, + "backFaceResourceName": "", + "backFaceVisible": false, + "backFaceResourceRepeat": false, + "leftFaceResourceName": "", + "leftFaceVisible": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceName": "", + "rightFaceVisible": false, + "rightFaceResourceRepeat": false, + "topFaceResourceName": "", + "topFaceVisible": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceName": "", + "bottomFaceVisible": false, + "bottomFaceResourceRepeat": false + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "AreaPlaceholder" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [ + { + "angle": 0, + "customSize": true, + "width": 4096, + "height": 4096, + "depth": 600, + "keepRatio": true, + "layer": "", + "name": "AreaPlaceholder", + "persistentUuid": "5d7a8b1e-3c2f-4e6a-9b1d-004096004096", + "x": 0, + "y": 0, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": {}, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Terrain3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "for (const child of object.getChildrenContainer().getAdhocListOfAllInstances()) {", + " child.deleteFromScene();", + "}", + "if (object.__terrain3D) object.__terrain3D.dispose();", + "object.__terrain3D = new gdjs.__terrain3DExtension.Terrain(object);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDestroy", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrain3D.dispose();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrain3D.update();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrain3D.onHotReloading();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + } + ], + "objectGroups": [] + }, + { + "fullName": "Raise or lower the ground", + "functionType": "Action", + "name": "RaiseGround", + "sentence": "Raise the ground of _PARAM0_ by _PARAM4_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const height = eventsFunctionContext.getArgument(\"Height\");", + "object.__terrain3D.raise(x, y, x, y, radius, height);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Height to add (negative to lower the ground)", + "name": "Height", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Raise the ground around a position (or lower it with a negative height). It's raised the most at the center and less and less up to the radius.", + "group": "Sculpt" + }, + { + "fullName": "Raise or lower the ground along a line", + "functionType": "Action", + "name": "RaiseGroundAlongLine", + "sentence": "Raise the ground of _PARAM0_ by _PARAM6_ from _PARAM1_; _PARAM2_ to _PARAM3_; _PARAM4_ with a radius of _PARAM5_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x1 = eventsFunctionContext.getArgument(\"X1\");", + "const y1 = eventsFunctionContext.getArgument(\"Y1\");", + "const x2 = eventsFunctionContext.getArgument(\"X2\");", + "const y2 = eventsFunctionContext.getArgument(\"Y2\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const height = eventsFunctionContext.getArgument(\"Height\");", + "object.__terrain3D.raise(x1, y1, x2, y2, radius, height);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the start", + "name": "X1", + "type": "expression" + }, + { + "description": "Y position of the start", + "name": "Y1", + "type": "expression" + }, + { + "description": "X position of the end", + "name": "X2", + "type": "expression" + }, + { + "description": "Y position of the end", + "name": "Y2", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance from the line." + }, + { + "description": "Height to add (negative to lower the ground)", + "name": "Height", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Raise the ground along a line, for example for a ridge, or lower it with a negative height to dig a river bed or a valley.", + "group": "Sculpt" + }, + { + "fullName": "Flatten the ground", + "functionType": "Action", + "name": "FlattenGround", + "sentence": "Flatten the ground of _PARAM0_ to a Z of _PARAM4_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const z = eventsFunctionContext.getArgument(\"Z\");", + "object.__terrain3D.flatten(x, y, x, y, radius, z, 1);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Z position of the flat ground", + "name": "Z", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Flatten the ground around a position to a given height, for example to place a building.", + "group": "Sculpt" + }, + { + "fullName": "Smooth the ground", + "functionType": "Action", + "name": "SmoothGround", + "sentence": "Smooth the ground of _PARAM0_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_ (strength: _PARAM4_)", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const strength = eventsFunctionContext.getArgument(\"Strength\");", + "object.__terrain3D.smooth(x, y, x, y, radius, strength);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Strength (between 0 and 1)", + "name": "Strength", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Smooth the ground around a position, softening bumps and cliffs.", + "group": "Sculpt" + }, + { + "fullName": "Paint a layer", + "functionType": "Action", + "name": "PaintLayer", + "sentence": "Paint layer _PARAM4_ on _PARAM0_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const layer = eventsFunctionContext.getArgument(\"Layer\");", + "object.__terrain3D.paint(x, y, x, y, radius, layer, 1);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Layer (1 to 4)", + "name": "Layer", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Paint a layer (like grass, dirt, rock or sand) on the ground around a position.", + "group": "Paint" + }, + { + "fullName": "Paint a layer along a line", + "functionType": "Action", + "name": "PaintLayerAlongLine", + "sentence": "Paint layer _PARAM6_ on _PARAM0_ from _PARAM1_; _PARAM2_ to _PARAM3_; _PARAM4_ with a radius of _PARAM5_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x1 = eventsFunctionContext.getArgument(\"X1\");", + "const y1 = eventsFunctionContext.getArgument(\"Y1\");", + "const x2 = eventsFunctionContext.getArgument(\"X2\");", + "const y2 = eventsFunctionContext.getArgument(\"Y2\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const layer = eventsFunctionContext.getArgument(\"Layer\");", + "object.__terrain3D.paint(x1, y1, x2, y2, radius, layer, 1);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the start", + "name": "X1", + "type": "expression" + }, + { + "description": "Y position of the start", + "name": "Y1", + "type": "expression" + }, + { + "description": "X position of the end", + "name": "X2", + "type": "expression" + }, + { + "description": "Y position of the end", + "name": "Y2", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance from the line." + }, + { + "description": "Layer (1 to 4)", + "name": "Layer", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Paint a layer on the ground along a line, for example for a path or a road.", + "group": "Paint" + }, + { + "fullName": "Tint the ground", + "functionType": "Action", + "name": "TintGround", + "sentence": "Tint the ground of _PARAM0_ with _PARAM4_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const color = gdjs.rgbOrHexToRGBColor(eventsFunctionContext.getArgument(\"Color\"));", + "object.__terrain3D.tint(x, y, x, y, radius, color, 1);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Color", + "name": "Color", + "type": "color" + } + ], + "objectGroups": [], + "description": "Tint the ground around a position with a color multiplying its layers, like dry grass or darker mud. White removes the tint.", + "group": "Paint" + }, + { + "fullName": "Change the wetness of the ground", + "functionType": "Action", + "name": "WetGround", + "sentence": "Change the wetness of the ground of _PARAM0_ to _PARAM4_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const wetness = eventsFunctionContext.getArgument(\"Wetness\");", + "object.__terrain3D.wet(x, y, x, y, radius, wetness, 1);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Wetness (from 0 for dry to 1)", + "name": "Wetness", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Make the ground around a position wet (darker and shiny, like after rain or near water) or dry.", + "group": "Paint" + }, + { + "fullName": "Ground wetness", + "functionType": "ExpressionAndCondition", + "name": "GroundWetness", + "sentence": "the wetness of the ground at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.getWetnessAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the wetness of the ground at a position, from 0 (dry) to 1, painted or near the water (wet shore).", + "group": "Paint", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Ground tint", + "functionType": "StringExpression", + "name": "GroundTint", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.getTintAt(x, y).join(\";\");" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "The tint painted on the ground at a position, like \"255;255;255\" (white when there is none).", + "group": "Paint", + "expressionType": { + "type": "string" + } + }, + { + "fullName": "Make a hole", + "functionType": "Action", + "name": "MakeHole", + "sentence": "Make a hole in _PARAM0_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "object.__terrain3D.makeHole(x, y, x, y, radius);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Remove the ground in a circle, for the entrance of a tunnel, a cave or a well: there is nothing drawn and no collision there. Fill it again with the \"Fill holes\" action. A height field can't go under itself: put a 3D model (with a collision shape) for the tunnel or the cave under the ground. The physics engine removes the triangles touching a hole, so the hole in the collisions can be up to a cell larger than the one drawn: make the model go a bit further than the hole, or put rocks around the entrance.", + "group": "Holes" + }, + { + "fullName": "Fill holes", + "functionType": "Action", + "name": "FillHoles", + "sentence": "Fill the holes in _PARAM0_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "object.__terrain3D.fillHoles(x, y, x, y, radius);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Fill the holes in a circle with the layers of the ground around them: the ground is drawn and has collisions again.", + "group": "Holes" + }, + { + "fullName": "Carve an entrance", + "functionType": "Action", + "name": "CarveEntrance", + "sentence": "Carve an entrance in _PARAM0_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_, going in at _PARAM4_° with its floor at Z _PARAM5_ and _PARAM6_ pixels of flat ground in front", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const angle = eventsFunctionContext.getArgument(\"Angle\");", + "const floorZ = eventsFunctionContext.getArgument(\"FloorZ\");", + "const length = eventsFunctionContext.getArgument(\"Length\");", + "object.__terrain3D.carveEntrance(x, y, radius, angle, floorZ, length);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression" + }, + { + "description": "Direction going into the ground (in degrees)", + "name": "Angle", + "type": "expression" + }, + { + "description": "Z position of the floor", + "name": "FloorZ", + "type": "expression" + }, + { + "description": "Length of the flat ground in front of the entrance (in pixels)", + "name": "Length", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Make the entrance of a tunnel or a cave on a slope: a hole, with the ground in front of it flattened to the floor of the tunnel.", + "group": "Holes" + }, + { + "fullName": "Hole", + "functionType": "Condition", + "name": "IsHole", + "sentence": "There is a hole in _PARAM0_ at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.isHoleAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Check if there is a hole in the ground at a position.", + "group": "Holes" + }, + { + "fullName": "Ground Z position, with holes", + "functionType": "Expression", + "name": "GroundZWithHoles", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const terrain = object.__terrain3D;", + "eventsFunctionContext.returnValue = terrain.isHoleAt(x, y) ? eventsFunctionContext.getArgument(\"ZInHoles\") : terrain.getHeightAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + }, + { + "description": "Value in holes", + "name": "ZInHoles", + "type": "expression", + "longDescription": "For example, a Z position far under the ground." + } + ], + "objectGroups": [], + "description": "The Z position of the ground at a position, or the given value where there is a hole.", + "group": "Holes", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Generate a relief", + "functionType": "Action", + "name": "GenerateRelief", + "sentence": "Generate a relief of _PARAM1_ on _PARAM0_ with seed _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const relief = eventsFunctionContext.getArgument(\"Relief\");", + "const seed = eventsFunctionContext.getArgument(\"Seed\");", + "object.__terrain3D.generate(relief, seed);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "Relief", + "name": "Relief", + "type": "stringWithSelector", + "supplementaryInformation": "[\"Flat\", \"Hills\", \"Mountains\", \"Island\"]" + }, + { + "description": "Seed", + "name": "Seed", + "type": "expression", + "longDescription": "Each seed gives a different terrain with the same relief." + } + ], + "objectGroups": [], + "description": "Replace the ground with a generated relief. Painted layers are reset.", + "group": "Generation" + }, + { + "fullName": "Load a heightmap", + "functionType": "Action", + "name": "LoadHeightmap", + "sentence": "Load the heights of _PARAM0_ from _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const image = eventsFunctionContext.getArgument(\"Image\");", + "object.__terrain3D.loadHeightmap(image);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "Heightmap image", + "name": "Image", + "type": "imageResource", + "longDescription": "Black is the lowest, white the highest. For more precision, store the height on 16 bits in the red (most significant byte) and green channels." + } + ], + "objectGroups": [], + "description": "Replace the ground with the heights of an image. Painted layers are kept.", + "group": "Generation" + }, + { + "fullName": "Put objects on the ground", + "functionType": "Action", + "name": "PutOnGround", + "sentence": "Put _PARAM1_ on the ground of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrain3D.placeOnGround(eventsFunctionContext.getObjects(\"Objects\"));" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "Objects to put on the ground", + "name": "Objects", + "type": "objectList" + } + ], + "objectGroups": [], + "description": "Move objects up or down so that their bottom is on the ground, under their center. Useful to place trees, rocks or characters on a terrain.", + "group": "Position" + }, + { + "fullName": "Ground Z position", + "functionType": "ExpressionAndCondition", + "name": "GroundZ", + "sentence": "the ground Z position at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.getHeightAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the Z position of the ground at a position of the scene.", + "group": "Position", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Ground slope", + "functionType": "ExpressionAndCondition", + "name": "GroundSlope", + "sentence": "the ground slope at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.getSlopeAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the slope of the ground (angle with the horizontal, in degrees) at a position of the scene.", + "group": "Position", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Ground layer", + "functionType": "ExpressionAndCondition", + "name": "GroundLayer", + "sentence": "the ground layer at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.getLayerAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the layer (from 1 to 4) painted the most at a position of the scene. Useful to play footstep sounds or to slow down characters in water or mud.", + "group": "Paint", + "expressionType": { + "type": "expression" + } + } + ], + "propertyDescriptors": [ + { + "value": "Hills", + "type": "Choice", + "label": "Relief", + "description": "The initial shape, before sculpting.", + "group": "Shape", + "choices": [ + { + "label": "Flat", + "value": "Flat" + }, + { + "label": "Hills", + "value": "Hills" + }, + { + "label": "Mountains", + "value": "Mountains" + }, + { + "label": "Island", + "value": "Island" + } + ], + "name": "Relief" + }, + { + "value": "1", + "type": "Number", + "label": "Seed", + "description": "Each seed gives a different terrain with the same relief.", + "group": "Shape", + "name": "Seed" + }, + { + "value": "", + "type": "Resource", + "label": "Heightmap image", + "description": "Optional. Replaces the relief: black is the lowest, white the highest (the object depth).", + "group": "Shape", + "extraInformation": [ + "image" + ], + "name": "HeightmapImage" + }, + { + "value": "", + "type": "MultilineString", + "label": "Edits", + "description": "Changes of the relief or heightmap, applied in order, as a JSON list, for example [{\"tool\": \"raise\", \"x\": 0.3, \"y\": 0.4, \"radius\": 0.15, \"height\": 0.4}, {\"tool\": \"paint\", \"x\": 0.1, \"y\": 0.9, \"toX\": 0.8, \"toY\": 0.2, \"radius\": 0.02, \"layer\": 2}]. Tools: raise (height added to the ground, negative to lower), flatten (z: the ground is brought to this height, fully at the center, cutting what is above it and filling what is below; with toX and toY, toZ gives the height at the end of the line, for a road going up), smooth (strength), paint (layer, from 1 to 4, and strength), tint (color, like \"120;140;60\", multiplying the layers, white removing the tint, and strength), wetness (amount, from 0 for dry to 1 for wet, and strength), hole (no ground in the radius, for the entrances of tunnels and caves) and fill (the holes in the radius are filled with the layers of the ground around them). x, y and radius are fractions (0 to 1) of the terrain size (x is (scene X - terrain X) / terrain width). height and z are fractions of its depth: the ground at z is at Z = terrain Z + z * terrain depth. Add toX and toY to change the ground along a line (paths, rivers, ridges). Shown in the scene editor.", + "group": "Shape", + "advanced": true, + "name": "Edits" + }, + { + "value": "256", + "type": "Choice", + "label": "Resolution", + "description": "The number of cells on each side of the terrain. Higher is more detailed but slower.", + "group": "Shape", + "choices": [ + { + "label": "64 x 64", + "value": "64" + }, + { + "label": "128 x 128", + "value": "128" + }, + { + "label": "256 x 256", + "value": "256" + }, + { + "label": "512 x 512", + "value": "512" + }, + { + "label": "1024 x 1024", + "value": "1024" + } + ], + "name": "Resolution" + }, + { + "value": "96;160;64", + "type": "Color", + "label": "Layer 1 color (grass)", + "group": "Layers", + "name": "Layer1Color" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 1 texture", + "description": "Optional. Tinted by the layer color.", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer1Texture" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 1 normal map", + "description": "Optional. The bumps of the texture, lit by the lights (an OpenGL normal map, like the textures of ambientCG).", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer1NormalMap" + }, + { + "value": "150;112;72", + "type": "Color", + "label": "Layer 2 color (dirt)", + "group": "Layers", + "name": "Layer2Color" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 2 texture", + "description": "Optional. Tinted by the layer color.", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer2Texture" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 2 normal map", + "description": "Optional. The bumps of the texture, lit by the lights (an OpenGL normal map, like the textures of ambientCG).", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer2NormalMap" + }, + { + "value": "124;118;112", + "type": "Color", + "label": "Layer 3 color (rock)", + "group": "Layers", + "name": "Layer3Color" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 3 texture", + "description": "Optional. Tinted by the layer color.", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer3Texture" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 3 normal map", + "description": "Optional. The bumps of the texture, lit by the lights (an OpenGL normal map, like the textures of ambientCG).", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer3NormalMap" + }, + { + "value": "224;204;148", + "type": "Color", + "label": "Layer 4 color (sand)", + "group": "Layers", + "name": "Layer4Color" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 4 texture", + "description": "Optional. Tinted by the layer color.", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer4Texture" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 4 normal map", + "description": "Optional. The bumps of the texture, lit by the lights (an OpenGL normal map, like the textures of ambientCG).", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer4NormalMap" + }, + { + "value": "1", + "type": "Number", + "label": "Normal map strength", + "description": "How much the normal maps bump the ground: 0 for none, 1 as they are, more to exaggerate them.", + "group": "Layers", + "name": "NormalMapStrength" + }, + { + "value": "256", + "type": "Number", + "label": "Texture size", + "unit": "Pixel", + "description": "The size, in the scene, of one repetition of the layer textures.", + "group": "Layers", + "name": "TextureSize" + }, + { + "value": "0.5", + "type": "Number", + "label": "Height blending", + "description": "Where layers meet, the bright parts of textures (stones, tufts) cover the dark ones (gaps, sand) instead of fading into each other: 0 for none, 1 for the most.", + "group": "Layers", + "name": "HeightBlending" + }, + { + "value": "0.5", + "type": "Number", + "label": "Texture variation", + "description": "Makes the repetition of textures less visible, with patches of bigger and rotated textures and colors slowly varying: 0 for none, 1 for the most.", + "group": "Layers", + "name": "TextureVariation" + }, + { + "value": "false", + "type": "Boolean", + "label": "Wet shore", + "description": "Make the ground wet (darker and shiny) under a Z position, like the sand of a beach near the water.", + "group": "Wetness", + "name": "WetShore" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Wet below Z", + "description": "The ground is wet under this Z position (for example, the Z of a 3D water object).", + "group": "Wetness", + "name": "WetShoreZ" + }, + { + "value": "60", + "type": "Number", + "unit": "Pixel", + "label": "Wet shore fade", + "description": "The ground becomes dry over this height above \"Wet below Z\".", + "group": "Wetness", + "name": "WetShoreFade" + }, + { + "value": "3", + "type": "Choice", + "label": "Layer on steep slopes", + "description": "This layer is shown where the ground is steep, whatever is painted.", + "group": "Layers", + "choices": [ + { + "label": "None", + "value": "0" + }, + { + "label": "Layer 1", + "value": "1" + }, + { + "label": "Layer 2", + "value": "2" + }, + { + "label": "Layer 3", + "value": "3" + }, + { + "label": "Layer 4", + "value": "4" + } + ], + "name": "CliffLayer" + }, + { + "value": "45", + "type": "Number", + "label": "Steep slope angle", + "unit": "DegreeAngle", + "group": "Layers", + "name": "CliffAngle" + }, + { + "value": "true", + "type": "Boolean", + "label": "Cast shadows", + "group": "Rendering", + "name": "CastShadow" + }, + { + "value": "true", + "type": "Boolean", + "label": "Receive shadows", + "group": "Rendering", + "name": "ReceiveShadow" + }, + { + "value": "", + "type": "String", + "label": "Sculpted and painted data", + "description": "Written by the sculpt and paint tools of the scene editor, in a compact format not meant to be read or written by hand: use Edits instead. Clear it to go back to the relief, heightmap and edits.", + "advanced": true, + "name": "SculptData" + } + ] + }, + { + "areaMaxX": 1000, + "areaMaxY": 1000, + "areaMaxZ": 40, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "defaultName": "Grass", + "description": "A field of grass blades swaying in the wind and pushed aside by characters. They grow in the area of the object, on the highest 3D terrain at each point (one field can cover several terrains), or at the Z position of the object where there is no terrain.", + "fullName": "Terrain grass", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0zIDIxYzEtNSAxLTkgMC0xMyAyIDMgMyA3IDMgMTN6bTQgMGMwLTYgMS0xMSA0LTE2LTEgNS0xIDEwIDAgMTZ6bTUgMGMwLTUgMi05IDUtMTItMiA0LTIgOC0xIDEyem01IDBjMS00IDItNiA0LTgtMSAzLTEgNS0xIDh6Ii8+PC9zdmc+", + "is3D": true, + "isUsingLegacyInstancesRenderer": true, + "name": "TerrainGrass", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/732ef90b7fcf5dd9171fe95d0cf262e09159b487304915a4693baa893d9ce16c_grass.svg", + "objects": [ + { + "assetStoreId": "", + "name": "AreaPlaceholder", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 1000, + "height": 1000, + "depth": 40, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "Basic", + "tint": "255;255;255", + "frontFaceResourceName": "", + "frontFaceVisible": false, + "frontFaceResourceRepeat": false, + "backFaceResourceName": "", + "backFaceVisible": false, + "backFaceResourceRepeat": false, + "leftFaceResourceName": "", + "leftFaceVisible": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceName": "", + "rightFaceVisible": false, + "rightFaceResourceRepeat": false, + "topFaceResourceName": "", + "topFaceVisible": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceName": "", + "bottomFaceVisible": false, + "bottomFaceResourceRepeat": false + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "AreaPlaceholder" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [ + { + "angle": 0, + "customSize": true, + "width": 1000, + "height": 1000, + "depth": 40, + "keepRatio": true, + "layer": "", + "name": "AreaPlaceholder", + "persistentUuid": "5d7a8b1e-3c2f-4e6a-9b1d-001000001000", + "x": 0, + "y": 0, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": {}, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Terrain3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Terrain3D::DefineGrassHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "for (const child of object.getChildrenContainer().getAdhocListOfAllInstances()) {", + " child.deleteFromScene();", + "}", + "if (object.__terrainGrass) object.__terrainGrass.dispose();", + "object.__terrainGrass = new gdjs.__terrainGrassExtension.Grass(object);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::TerrainGrass" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDestroy", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrainGrass.dispose();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::TerrainGrass" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const game = runtimeScene.getGame();", + "// Time doesn't pass in the scene editor: real time is used to show the wind.", + "const isInGameEdition = game.isInGameEdition && game.isInGameEdition();", + "const elapsedTime = isInGameEdition ? 1 / 60 : object.getElapsedTime() / 1000;", + "const time = isInGameEdition", + " ? performance.now() / 1000", + " : object.getRuntimeScene().getTimeManager().getTimeFromStart() / 1000;", + "const layer = object.getInstanceContainer().getLayer(object.getLayer());", + "object.__terrainGrass.update(elapsedTime, time, layer.getRenderer().getThreeCamera());" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::TerrainGrass" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrainGrass.updateFromProperties();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::TerrainGrass" + } + ], + "objectGroups": [] + } + ], + "propertyDescriptors": [ + { + "value": "60", + "type": "Number", + "label": "Density", + "description": "The number of blades in a square of 100 x 100 pixels.", + "group": "Blades", + "name": "Density" + }, + { + "value": "36", + "type": "Number", + "label": "Blade height", + "unit": "Pixel", + "description": "The average height of blades (each blade is between 60% and 140% of it).", + "group": "Blades", + "name": "BladeHeight" + }, + { + "value": "5", + "type": "Number", + "label": "Blade width", + "unit": "Pixel", + "group": "Blades", + "name": "BladeWidth" + }, + { + "value": "52;104;36", + "type": "Color", + "label": "Base color", + "group": "Blades", + "name": "BaseColor" + }, + { + "value": "150;196;82", + "type": "Color", + "label": "Tip color", + "group": "Blades", + "name": "TipColor" + }, + { + "value": "1", + "type": "Number", + "label": "Seed", + "description": "Each seed places blades differently.", + "group": "Blades", + "name": "Seed" + }, + { + "value": "1", + "type": "Choice", + "label": "Grows on terrain layer", + "description": "On a 3D terrain, blades only grow where this layer is painted. A new terrain has layer 1 painted everywhere.", + "group": "Ground", + "choices": [ + { + "label": "Any", + "value": "0" + }, + { + "label": "Layer 1", + "value": "1" + }, + { + "label": "Layer 2", + "value": "2" + }, + { + "label": "Layer 3", + "value": "3" + }, + { + "label": "Layer 4", + "value": "4" + } + ], + "name": "GroundLayer" + }, + { + "value": "40", + "type": "Number", + "label": "Maximum slope", + "unit": "DegreeAngle", + "description": "No blades grow on steeper ground.", + "group": "Ground", + "name": "MaxSlope" + }, + { + "value": "1", + "type": "Number", + "label": "Wind strength", + "description": "0 for no wind.", + "group": "Wind", + "name": "WindStrength" + }, + { + "value": "2", + "type": "Number", + "label": "Wind speed", + "group": "Wind", + "name": "WindSpeed" + }, + { + "value": "", + "type": "String", + "label": "Object bending the grass", + "description": "The name of an object pushing blades aside when walking through the grass, like the player. The 8 first instances are used.", + "group": "Wind", + "name": "BendingObject" + }, + { + "value": "2500", + "type": "Number", + "label": "Fade distance", + "unit": "Pixel", + "description": "Blades are less and less dense up to this distance from the camera, and hidden after.", + "group": "Rendering", + "name": "FadeDistance" + } + ] + }, + { + "areaMaxX": 2000, + "areaMaxY": 2000, + "areaMaxZ": 200, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "defaultName": "ScatteredModels", + "assetStoreTag": "3d scattered models", + "description": "Many copies of a 3D model (trees, rocks, bushes, flowers...) placed in the area of the object and drawn all at once, which is much faster than an object for each one. They stand on the highest 3D terrain at each point, or at the Z position of the object where there is no terrain. Paint where they grow with the brushes of the scene editor.", + "fullName": "Scattered models (forests, rocks...)", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0xMCAyMXYtM0gzbDUtNUg1bDUtNUg3bDUtNSA1IDVoLTNsNSA1aC0zbDUgNWgtN3YzeiIvPjwvc3ZnPg==", + "is3D": true, + "isUsingLegacyInstancesRenderer": true, + "name": "ScatteredModels", + "previewIconUrl": "", + "objects": [ + { + "assetStoreId": "", + "name": "AreaPlaceholder", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 2000, + "height": 2000, + "depth": 200, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "Basic", + "tint": "255;255;255", + "frontFaceResourceName": "", + "frontFaceVisible": false, + "frontFaceResourceRepeat": false, + "backFaceResourceName": "", + "backFaceVisible": false, + "backFaceResourceRepeat": false, + "leftFaceResourceName": "", + "leftFaceVisible": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceName": "", + "rightFaceVisible": false, + "rightFaceResourceRepeat": false, + "topFaceResourceName": "", + "topFaceVisible": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceName": "", + "bottomFaceVisible": false, + "bottomFaceResourceRepeat": false + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "AreaPlaceholder" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [ + { + "angle": 0, + "customSize": true, + "width": 2000, + "height": 2000, + "depth": 200, + "keepRatio": true, + "layer": "", + "name": "AreaPlaceholder", + "persistentUuid": "5d7a8b1e-3c2f-4e6a-9b1d-002000002000", + "x": 0, + "y": 0, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": {}, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Terrain3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Terrain3D::DefineScatterHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "for (const child of object.getChildrenContainer().getAdhocListOfAllInstances()) {", + " child.deleteFromScene();", + "}", + "if (object.__terrainScatter) object.__terrainScatter.dispose();", + "object.__terrainScatter = new gdjs.__terrainScatterExtension.Scatter(object);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDestroy", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrainScatter.dispose();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const game = runtimeScene.getGame();", + "const isInGameEdition = game.isInGameEdition && game.isInGameEdition();", + "const elapsedTime = isInGameEdition ? 1 / 60 : object.getElapsedTime() / 1000;", + "const layer = object.getInstanceContainer().getLayer(object.getLayer());", + "object.__terrainScatter.update(elapsedTime, layer.getRenderer().getThreeCamera());" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrainScatter.onHotReloading();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + } + ], + "objectGroups": [] + }, + { + "fullName": "Add copies", + "functionType": "Action", + "name": "AddCopies", + "sentence": "Add copies of _PARAM0_ around _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "object.__terrainScatter.addCopies(x, y, radius);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Paint the density of copies around a position, up to the density of the object (copies removed with \"Erase copies\" come back). The effect fades out up to the radius.", + "group": "Paint" + }, + { + "fullName": "Erase copies", + "functionType": "Action", + "name": "EraseCopies", + "sentence": "Erase the copies of _PARAM0_ in a circle at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "object.__terrainScatter.removeCopies(x, y, radius);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Remove the copies standing in a circle, for example a tree cut by the player (use the position of the nearest copy and a small radius) or a forest cleared for a building. They come back when the scene is restarted.", + "group": "Paint" + }, + { + "fullName": "Number of copies", + "functionType": "ExpressionAndCondition", + "name": "CopyCount", + "sentence": "the number of copies", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "eventsFunctionContext.returnValue = object.__terrainScatter.getCopyCount();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + } + ], + "objectGroups": [], + "description": "the number of copies placed in the area.", + "group": "Copies", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "X position of the nearest copy", + "functionType": "Expression", + "name": "NearestCopyX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const copy = object.__terrainScatter.findNearestCopy(x, y);", + "eventsFunctionContext.returnValue = copy ? copy.x : x;" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "The X position of the base of the copy nearest to a position (the position itself if there is no copy).", + "group": "Copies", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Y position of the nearest copy", + "functionType": "Expression", + "name": "NearestCopyY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const copy = object.__terrainScatter.findNearestCopy(x, y);", + "eventsFunctionContext.returnValue = copy ? copy.y : y;" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "The Y position of the base of the copy nearest to a position (the position itself if there is no copy).", + "group": "Copies", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Z position of the nearest copy", + "functionType": "Expression", + "name": "NearestCopyZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const copy = object.__terrainScatter.findNearestCopy(x, y);", + "eventsFunctionContext.returnValue = copy ? copy.z : object.getZ();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "The Z position of the base of the copy nearest to a position (on the ground).", + "group": "Copies", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Distance to the nearest copy", + "functionType": "ExpressionAndCondition", + "name": "NearestCopyDistance", + "sentence": "the distance from _PARAM1_; _PARAM2_ to the nearest copy", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const copy = object.__terrainScatter.findNearestCopy(x, y);", + "eventsFunctionContext.returnValue = copy ? Math.hypot(copy.x - x, copy.y - y) : Number.MAX_VALUE;" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the distance (on X and Y) from a position to the base of the nearest copy. It's a very big number when there is no copy.", + "group": "Copies", + "expressionType": { + "type": "expression" + } + } + ], + "propertyDescriptors": [ + { + "value": "", + "type": "Resource", + "label": "3D model", + "description": "The model copied everywhere (a .glb file). Models standing on the Y axis, like most models of the asset store, are stood up along Z.", + "group": "Model", + "extraInformation": [ + "model3D" + ], + "name": "Model" + }, + { + "value": "300", + "type": "Number", + "label": "Height of a copy", + "unit": "Pixel", + "description": "Copies keep the proportions of the model.", + "group": "Model", + "name": "Size" + }, + { + "value": "0.8", + "type": "Number", + "label": "Smallest random scale", + "description": "Each copy is scaled at random between the smallest and the largest scale.", + "group": "Model", + "name": "MinScale" + }, + { + "value": "1.2", + "type": "Number", + "label": "Largest random scale", + "group": "Model", + "name": "MaxScale" + }, + { + "value": "true", + "type": "Boolean", + "label": "Random rotation", + "description": "Turn each copy around the Z axis at random.", + "group": "Model", + "name": "RandomRotation" + }, + { + "value": "255;255;255", + "type": "Color", + "label": "Random tint", + "description": "Each copy is tinted between white and this color, at random. White for no tint.", + "group": "Model", + "name": "RandomTint" + }, + { + "value": "0.3", + "type": "Number", + "label": "Density", + "description": "The number of copies in a square of 100 x 100 pixels where the density is fully painted (0.3 is about one copy every 180 pixels). Copies never overlap their neighbors much: they are placed at random in a grid.", + "group": "Placement", + "name": "Density" + }, + { + "value": "1", + "type": "Number", + "label": "Seed", + "description": "Each seed places copies differently.", + "group": "Placement", + "name": "Seed" + }, + { + "value": "Everywhere", + "type": "Choice", + "label": "Copies grow", + "description": "Everywhere in the area, or only where they are painted with the \"Add copies\" brush (or the edits).", + "group": "Placement", + "choices": [ + { + "label": "Everywhere (erase them with the brush)", + "value": "Everywhere" + }, + { + "label": "Only where painted", + "value": "Painted" + } + ], + "name": "Coverage" + }, + { + "value": "1", + "type": "Choice", + "label": "Grows on terrain layer", + "description": "On a 3D terrain, copies only grow where this layer is painted, away from its borders (so that trees don't stand on roads painted with another layer). A new terrain has layer 1 painted everywhere.", + "group": "Ground", + "choices": [ + { + "label": "Any", + "value": "0" + }, + { + "label": "Layer 1", + "value": "1" + }, + { + "label": "Layer 2", + "value": "2" + }, + { + "label": "Layer 3", + "value": "3" + }, + { + "label": "Layer 4", + "value": "4" + } + ], + "name": "GroundLayer" + }, + { + "value": "30", + "type": "Number", + "label": "Maximum slope", + "unit": "DegreeAngle", + "description": "No copies grow on steeper ground.", + "group": "Ground", + "name": "MaxSlope" + }, + { + "value": "-100000", + "type": "Number", + "label": "Minimum ground Z", + "unit": "Pixel", + "description": "No copies grow where the ground is lower (for example, under the water level).", + "group": "Ground", + "name": "MinGroundZ" + }, + { + "value": "100000", + "type": "Number", + "label": "Maximum ground Z", + "unit": "Pixel", + "description": "No copies grow where the ground is higher (for example, above the tree line).", + "group": "Ground", + "name": "MaxGroundZ" + }, + { + "value": "false", + "type": "Boolean", + "label": "Trunk collisions", + "description": "Give each copy a static collision shape around its trunk, for the 3D physics behavior (add it to this object, with the \"Static\" type) and navigation meshes. Without them, these behaviors have no effect on this object.", + "group": "Collisions", + "name": "TrunkCollisions" + }, + { + "value": "30", + "type": "Number", + "label": "Trunk diameter", + "unit": "Pixel", + "description": "The diameter of the collision shape of a copy at the height given in \"Height of a copy\" (it is scaled with the copy).", + "group": "Collisions", + "name": "TrunkDiameter" + }, + { + "value": "8000", + "type": "Number", + "label": "Draw distance", + "unit": "Pixel", + "description": "Copies shrink and are hidden at this distance from the camera.", + "group": "Rendering", + "name": "DrawDistance" + }, + { + "value": "true", + "type": "Boolean", + "label": "Cast shadows", + "group": "Rendering", + "name": "CastShadow" + }, + { + "value": "true", + "type": "Boolean", + "label": "Receive shadows", + "group": "Rendering", + "name": "ReceiveShadow" + }, + { + "value": "", + "type": "MultilineString", + "label": "Edits", + "description": "Where copies are added or erased, applied in order, as a JSON list, for example [{\"tool\": \"add\", \"x\": 0.3, \"y\": 0.4, \"radius\": 0.15}, {\"tool\": \"erase\", \"x\": 0.1, \"y\": 0.9, \"toX\": 0.8, \"toY\": 0.2, \"radius\": 0.02}]. Tools: add (density: from 0 to 1, 1 by default, how dense copies are compared to the Density property) and erase. strength (from 0 to 1) makes a stroke lighter. x, y and radius are fractions (0 to 1) of the object size (x is (scene X - object X) / object width). Add toX and toY to paint along a line (a row of trees, a path through a forest). With \"Copies grow: only where painted\", only the added areas have copies. Shown in the scene editor.", + "group": "Placement", + "advanced": true, + "name": "Edits" + }, + { + "value": "", + "type": "String", + "label": "Painted data", + "description": "Written by the brushes of the scene editor, in a compact format not meant to be read or written by hand: use Edits instead. Clear it to go back to the coverage and the edits.", + "advanced": true, + "name": "PaintData" + } + ] + } + ] + } + ], + "externalLayouts": [] +} diff --git a/examples/starting-expanded-3d-car-racing/theme-slots.json b/examples/starting-expanded-3d-car-racing/theme-slots.json new file mode 100644 index 000000000..5fb201fd0 --- /dev/null +++ b/examples/starting-expanded-3d-car-racing/theme-slots.json @@ -0,0 +1,64 @@ +{ + "version": 1, + "objects": { + "scene:Game Scene/PlayerCar": "vehicle.car", + "scene:Game Scene/TrafficCone": "prop.obstacle", + "scene:Game Scene/FinishLine_3D": { + "front": "env.finish", + "left": "env.finish", + "right": "env.finish", + "top": "env.finish", + "bottom": "env.finish" + }, + "scene:Game Scene/Barrier": "prop.obstacle", + "scene:Game Scene/BarrierRed": "prop.obstacle", + "scene:Game Scene/Trees": { + "Model": "nature.tree" + }, + "scene:Game Scene/PineTrees": { + "Model": "nature.tree" + }, + "scene:Game Scene/Rocks": { + "Model": "nature.rock.big" + }, + "scene:Game Scene/Terrain": { + "Layer1Texture": { + "slot": "terrain.ground", + "normalMap": "Layer1NormalMap" + }, + "Layer2Texture": { + "slot": "terrain.road", + "normalMap": "Layer2NormalMap" + }, + "Layer3Texture": { + "slot": "terrain.rock", + "normalMap": "Layer3NormalMap" + }, + "Layer4Texture": { + "slot": "terrain.dirt", + "normalMap": "Layer4NormalMap" + } + }, + "scene:Game Scene/Grass": { + "BaseColor": "terrain.grass.base", + "TipColor": "terrain.grass.tip" + } + }, + "effects": { + "effect:Game Scene//SkyBox": "sky.day" + }, + "ignoredObjects": [ + "scene:Game Scene/GrandStand", + "scene:Game Scene/PitsGarage", + "scene:Game Scene/PitsOffice", + "scene:Game Scene/OverheadLights", + "scene:Game Scene/Billboard", + "scene:Game Scene/Tent", + "scene:Game Scene/FlagCheckers", + "scene:Game Scene/MapLimit", + "scene:Game Scene/CheckpointArrow", + "object:Terrain3D::Terrain3D/AreaPlaceholder", + "object:Terrain3D::TerrainGrass/AreaPlaceholder", + "object:Terrain3D::ScatteredModels/AreaPlaceholder" + ] +} diff --git a/examples/starting-expanded-3d-driving/README.md b/examples/starting-expanded-3d-driving/README.md new file mode 100644 index 000000000..117abd061 --- /dev/null +++ b/examples/starting-expanded-3d-driving/README.md @@ -0,0 +1,3 @@ +# 3D Driving Expanded + +A 3D car driving game with a third-person camera view, on a large map made with a 3D terrain: a road circuit through the countryside and a village, climbing a hill, with a shortcut over ramps, forests and mountains around. The player accelerates and steers using keyboard or mobile controls. The car uses physics-based movement and can collide with other physics obstacles in the 3D environment. diff --git a/examples/starting-expanded-3d-driving/assets/Barrier White.glb b/examples/starting-expanded-3d-driving/assets/Barrier White.glb new file mode 100644 index 000000000..9a43f25a3 Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/Barrier White.glb differ diff --git a/examples/starting-expanded-3d-driving/assets/Flat dark joystick border horizontal.png b/examples/starting-expanded-3d-driving/assets/Flat dark joystick border horizontal.png new file mode 100644 index 000000000..4f772d873 Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/Flat dark joystick border horizontal.png differ diff --git a/examples/starting-expanded-3d-driving/assets/Flat dark joystick border vertical.png b/examples/starting-expanded-3d-driving/assets/Flat dark joystick border vertical.png new file mode 100644 index 000000000..ec315d4b6 Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/Flat dark joystick border vertical.png differ diff --git a/examples/starting-expanded-3d-driving/assets/Flat dark joystick thumb.png b/examples/starting-expanded-3d-driving/assets/Flat dark joystick thumb.png new file mode 100644 index 000000000..8acf179f8 Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/Flat dark joystick thumb.png differ diff --git a/examples/starting-expanded-3d-driving/assets/House Type 01.glb b/examples/starting-expanded-3d-driving/assets/House Type 01.glb new file mode 100644 index 000000000..434921994 Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/House Type 01.glb differ diff --git a/examples/starting-expanded-3d-driving/assets/House Type 05.glb b/examples/starting-expanded-3d-driving/assets/House Type 05.glb new file mode 100644 index 000000000..a5481cad4 Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/House Type 05.glb differ diff --git a/examples/starting-expanded-3d-driving/assets/House Type 09.glb b/examples/starting-expanded-3d-driving/assets/House Type 09.glb new file mode 100644 index 000000000..6a3e5b30b Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/House Type 09.glb differ diff --git a/examples/starting-expanded-3d-driving/assets/House Type 13.glb b/examples/starting-expanded-3d-driving/assets/House Type 13.glb new file mode 100644 index 000000000..a27466386 Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/House Type 13.glb differ diff --git a/examples/starting-expanded-3d-driving/assets/Light Post.glb b/examples/starting-expanded-3d-driving/assets/Light Post.glb new file mode 100644 index 000000000..0edf3d4d1 Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/Light Post.glb differ diff --git a/examples/starting-expanded-3d-driving/assets/Orange - Sedan Sports.glb b/examples/starting-expanded-3d-driving/assets/Orange - Sedan Sports.glb new file mode 100644 index 000000000..5acb9bb41 Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/Orange - Sedan Sports.glb differ diff --git a/examples/starting-expanded-3d-driving/assets/Rock Large A.glb b/examples/starting-expanded-3d-driving/assets/Rock Large A.glb new file mode 100644 index 000000000..7add51429 Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/Rock Large A.glb differ diff --git a/examples/starting-expanded-3d-driving/assets/Skybox_Back.png b/examples/starting-expanded-3d-driving/assets/Skybox_Back.png new file mode 100644 index 000000000..aa0891ef2 Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/Skybox_Back.png differ diff --git a/examples/starting-expanded-3d-driving/assets/Skybox_Bottom.png b/examples/starting-expanded-3d-driving/assets/Skybox_Bottom.png new file mode 100644 index 000000000..16e34f3da Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/Skybox_Bottom.png differ diff --git a/examples/starting-expanded-3d-driving/assets/Skybox_Front.png b/examples/starting-expanded-3d-driving/assets/Skybox_Front.png new file mode 100644 index 000000000..018d5faac Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/Skybox_Front.png differ diff --git a/examples/starting-expanded-3d-driving/assets/Skybox_Left.png b/examples/starting-expanded-3d-driving/assets/Skybox_Left.png new file mode 100644 index 000000000..e4053f388 Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/Skybox_Left.png differ diff --git a/examples/starting-expanded-3d-driving/assets/Skybox_Right.png b/examples/starting-expanded-3d-driving/assets/Skybox_Right.png new file mode 100644 index 000000000..d14c192b1 Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/Skybox_Right.png differ diff --git a/examples/starting-expanded-3d-driving/assets/Skybox_Top.png b/examples/starting-expanded-3d-driving/assets/Skybox_Top.png new file mode 100644 index 000000000..e83ef6a1b Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/Skybox_Top.png differ diff --git a/examples/starting-expanded-3d-driving/assets/StartingCheckeredPattern.png b/examples/starting-expanded-3d-driving/assets/StartingCheckeredPattern.png new file mode 100644 index 000000000..ba471626c Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/StartingCheckeredPattern.png differ diff --git a/examples/starting-expanded-3d-driving/assets/TerrainAsphalt.jpg b/examples/starting-expanded-3d-driving/assets/TerrainAsphalt.jpg new file mode 100644 index 000000000..29647465d Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/TerrainAsphalt.jpg differ diff --git a/examples/starting-expanded-3d-driving/assets/TerrainAsphaltNormal.jpg b/examples/starting-expanded-3d-driving/assets/TerrainAsphaltNormal.jpg new file mode 100644 index 000000000..e45282c97 Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/TerrainAsphaltNormal.jpg differ diff --git a/examples/starting-expanded-3d-driving/assets/TerrainDirt.jpg b/examples/starting-expanded-3d-driving/assets/TerrainDirt.jpg new file mode 100644 index 000000000..71221d59d Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/TerrainDirt.jpg differ diff --git a/examples/starting-expanded-3d-driving/assets/TerrainDirtNormal.jpg b/examples/starting-expanded-3d-driving/assets/TerrainDirtNormal.jpg new file mode 100644 index 000000000..f701da364 Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/TerrainDirtNormal.jpg differ diff --git a/examples/starting-expanded-3d-driving/assets/TerrainGrass.jpg b/examples/starting-expanded-3d-driving/assets/TerrainGrass.jpg new file mode 100644 index 000000000..220cddc0d Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/TerrainGrass.jpg differ diff --git a/examples/starting-expanded-3d-driving/assets/TerrainGrassNormal.jpg b/examples/starting-expanded-3d-driving/assets/TerrainGrassNormal.jpg new file mode 100644 index 000000000..cd5053235 Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/TerrainGrassNormal.jpg differ diff --git a/examples/starting-expanded-3d-driving/assets/TerrainRock.jpg b/examples/starting-expanded-3d-driving/assets/TerrainRock.jpg new file mode 100644 index 000000000..d838e88fb Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/TerrainRock.jpg differ diff --git a/examples/starting-expanded-3d-driving/assets/TerrainRockNormal.jpg b/examples/starting-expanded-3d-driving/assets/TerrainRockNormal.jpg new file mode 100644 index 000000000..7c8288e66 Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/TerrainRockNormal.jpg differ diff --git a/examples/starting-expanded-3d-driving/assets/Traffic Cone.glb b/examples/starting-expanded-3d-driving/assets/Traffic Cone.glb new file mode 100644 index 000000000..5c95979e1 Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/Traffic Cone.glb differ diff --git a/examples/starting-expanded-3d-driving/assets/Tree Default.glb b/examples/starting-expanded-3d-driving/assets/Tree Default.glb new file mode 100644 index 000000000..a9d653635 Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/Tree Default.glb differ diff --git a/examples/starting-expanded-3d-driving/assets/Tree Oak.glb b/examples/starting-expanded-3d-driving/assets/Tree Oak.glb new file mode 100644 index 000000000..1e41dc812 Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/Tree Oak.glb differ diff --git a/examples/starting-expanded-3d-driving/assets/Tree Pine Default A.glb b/examples/starting-expanded-3d-driving/assets/Tree Pine Default A.glb new file mode 100644 index 000000000..03b620f7f Binary files /dev/null and b/examples/starting-expanded-3d-driving/assets/Tree Pine Default A.glb differ diff --git a/examples/starting-expanded-3d-driving/preview.png b/examples/starting-expanded-3d-driving/preview.png new file mode 100644 index 000000000..f6af36c5b Binary files /dev/null and b/examples/starting-expanded-3d-driving/preview.png differ diff --git a/examples/starting-expanded-3d-driving/starting-expanded-3d-driving.json b/examples/starting-expanded-3d-driving/starting-expanded-3d-driving.json new file mode 100644 index 000000000..4a994910c --- /dev/null +++ b/examples/starting-expanded-3d-driving/starting-expanded-3d-driving.json @@ -0,0 +1,21684 @@ +{ + "firstLayout": "", + "gdVersion": { + "build": 283, + "major": 5, + "minor": 6, + "revision": 0 + }, + "properties": { + "adaptGameResolutionAtRuntime": true, + "antialiasingMode": "MSAA", + "antialisingEnabledOnMobile": false, + "folderProject": false, + "orientation": "landscape", + "packageName": "com.example.gamename", + "pixelsRounding": false, + "projectUuid": "457a481a-b18a-5a7c-8e1e-ed94d20b3f7c", + "scaleMode": "linear", + "sizeOnStartupMode": "", + "templateSlug": "", + "version": "1.0.0", + "name": "3D Driving Expanded", + "description": "A 3D car driving game on a map made with a 3D terrain: a road circuit through the countryside and a village, over hills and ramps.", + "author": "", + "windowWidth": 1280, + "windowHeight": 720, + "latestCompilationDirectory": "", + "maxFPS": 60, + "minFPS": 20, + "verticalSync": false, + "platformSpecificAssets": { + "android-icon-144": "", + "android-icon-192": "", + "android-icon-36": "", + "android-icon-48": "", + "android-icon-72": "", + "android-icon-96": "", + "android-windowSplashScreenAnimatedIcon": "", + "desktop-icon-512": "", + "ios-icon-100": "", + "ios-icon-1024": "", + "ios-icon-114": "", + "ios-icon-120": "", + "ios-icon-144": "", + "ios-icon-152": "", + "ios-icon-167": "", + "ios-icon-180": "", + "ios-icon-20": "", + "ios-icon-29": "", + "ios-icon-40": "", + "ios-icon-50": "", + "ios-icon-57": "", + "ios-icon-58": "", + "ios-icon-60": "", + "ios-icon-72": "", + "ios-icon-76": "", + "ios-icon-80": "", + "ios-icon-87": "" + }, + "loadingScreen": { + "backgroundColor": 0, + "backgroundFadeInDuration": 0.2, + "backgroundImageResourceName": "", + "gdevelopLogoStyle": "light", + "logoAndProgressFadeInDuration": 0.2, + "logoAndProgressLogoFadeInDelay": 0, + "minDuration": 1.5, + "progressBarColor": 16777215, + "progressBarHeight": 20, + "progressBarMaxWidth": 200, + "progressBarMinWidth": 40, + "progressBarWidthPercent": 30, + "showGDevelopSplash": true, + "showProgressBar": true + }, + "watermark": { + "placement": "bottom-left", + "showWatermark": true + }, + "authorIds": [], + "authorUsernames": [], + "categories": [], + "playableDevices": [ + "mobile" + ], + "extensionProperties": [], + "platforms": [ + { + "name": "GDevelop JS platform" + } + ], + "currentPlatform": "GDevelop JS platform" + }, + "resources": { + "resources": [ + { + "file": "assets/StartingCheckeredPattern.png", + "kind": "image", + "metadata": "", + "name": "assets\\StartingCheckeredPattern.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Flat dark joystick thumb.png", + "kind": "image", + "metadata": "", + "name": "assets\\Flat dark joystick thumb.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Orange - Sedan Sports.glb", + "kind": "model3D", + "metadata": "", + "name": "assets\\Orange - Sedan Sports.glb", + "userAdded": false + }, + { + "file": "assets/Traffic Cone.glb", + "kind": "model3D", + "metadata": "", + "name": "Traffic Cone.glb", + "userAdded": false, + "origin": { + "identifier": "https://asset-resources.gdevelop.io/public-resources/3D Toon Shooter/Prop/341d0630877a91adc1813baeb761051a5f9768cdbd5a083a7d8b1972a187ed34_Traffic Cone.glb", + "name": "Traffic Cone.glb" + } + }, + { + "file": "assets/Skybox_Front.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Front.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Back.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Back.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Top.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Top.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Bottom.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Bottom.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Left.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Left.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Right.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Right.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Flat dark joystick border vertical.png", + "kind": "image", + "metadata": "", + "name": "assets/Flat dark joystick border vertical.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Flat dark joystick border horizontal.png", + "kind": "image", + "metadata": "", + "name": "assets/Flat dark joystick border horizontal.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/TerrainGrass.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainGrass.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainGrassNormal.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainGrassNormal.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainAsphalt.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainAsphalt.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainAsphaltNormal.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainAsphaltNormal.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainRock.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainRock.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainRockNormal.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainRockNormal.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainDirt.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainDirt.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainDirtNormal.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainDirtNormal.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/House Type 01.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/House Type 01.glb", + "userAdded": true + }, + { + "file": "assets/House Type 05.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/House Type 05.glb", + "userAdded": true + }, + { + "file": "assets/House Type 09.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/House Type 09.glb", + "userAdded": true + }, + { + "file": "assets/House Type 13.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/House Type 13.glb", + "userAdded": true + }, + { + "file": "assets/Light Post.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Light Post.glb", + "userAdded": true + }, + { + "file": "assets/Barrier White.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Barrier White.glb", + "userAdded": true + }, + { + "file": "assets/Tree Default.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Tree Default.glb", + "userAdded": true + }, + { + "file": "assets/Tree Oak.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Tree Oak.glb", + "userAdded": true + }, + { + "file": "assets/Tree Pine Default A.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Tree Pine Default A.glb", + "userAdded": true + }, + { + "file": "assets/Rock Large A.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Rock Large A.glb", + "userAdded": true + } + ] + }, + "objects": [], + "objectsFolderStructure": { + "folderName": "__ROOT" + }, + "objectsGroups": [], + "variables": [], + "layouts": [ + { + "b": 255, + "disableInputWhenNotFocused": true, + "mangledName": "Game_32Scene", + "name": "Game Scene", + "r": 255, + "standardSortMethod": true, + "stopSoundsOnStartup": true, + "title": "", + "v": 255, + "uiSettings": { + "grid": false, + "gridType": "rectangular", + "gridWidth": 64, + "gridHeight": 64, + "gridDepth": 32, + "gridOffsetX": 0, + "gridOffsetY": 0, + "gridOffsetZ": 0, + "gridColor": 10401023, + "gridAlpha": 0.8, + "snap": false, + "zoomFactor": 0.4633111824514118, + "windowMask": false, + "selectedLayer": "", + "gameEditorMode": "embedded-game" + }, + "objectsGroups": [], + "variables": [], + "instances": [ + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "MobileControls", + "locked": true, + "name": "Speed_Joystick", + "persistentUuid": "98bcbea1-1b37-46a3-8686-ff716b1b1024", + "width": 0, + "x": 1072, + "y": 537, + "zOrder": 9, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "MobileControls", + "locked": true, + "name": "Steer_Joystick", + "persistentUuid": "6c0d6cbc-a8d1-4930-b2e4-a83ac76f2d7e", + "width": 0, + "x": 208, + "y": 537, + "zOrder": 10, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 8192, + "width": 8192, + "layer": "", + "locked": false, + "name": "Terrain", + "persistentUuid": "1fc6353b-271b-574b-9dd4-50b889a48122", + "x": -4096, + "y": -4096, + "z": 0, + "zOrder": 0, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 1600 + }, + { + "angle": 0, + "customSize": true, + "height": 200, + "width": 8592, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "9a1899bd-e594-5732-b159-c8fc00792908", + "x": -4296, + "y": -4296, + "z": -500, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 4000 + }, + { + "angle": 0, + "customSize": true, + "height": 200, + "width": 8592, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "85b910ee-e48a-5b75-9589-a87497109c94", + "x": -4296, + "y": 4096, + "z": -500, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 4000 + }, + { + "angle": 0, + "customSize": true, + "height": 8192, + "width": 200, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "e1328929-695a-5b19-828e-55fc6640dff4", + "x": -4296, + "y": -4096, + "z": -500, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 4000 + }, + { + "angle": 0, + "customSize": true, + "height": 8192, + "width": 200, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "0487d11e-6d5c-589c-ba64-724088c52940", + "x": 4096, + "y": -4096, + "z": -500, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 4000 + }, + { + "angle": 0, + "customSize": true, + "height": 8192, + "width": 8192, + "layer": "", + "locked": false, + "name": "Grass", + "persistentUuid": "10ad7603-bbda-5fd6-a4fc-bf61bddfc39d", + "x": -4096, + "y": -4096, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 40 + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "House_D", + "persistentUuid": "964ce12e-925f-5140-a0d9-1b9788b2147b", + "x": -900, + "y": -2980, + "z": 35.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "House_C", + "persistentUuid": "ce2952e0-ec9e-5caf-9697-10fa16fe439e", + "x": -480, + "y": -2980, + "z": 35.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "House_A", + "persistentUuid": "15da133c-4500-5211-9309-598c5a8d074d", + "x": 480, + "y": -2980, + "z": 35.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "House_B", + "persistentUuid": "da876a21-a415-5e60-be94-f4f94225e917", + "x": 900, + "y": -2980, + "z": 30.9, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "House_B", + "persistentUuid": "54df2243-0151-539d-aa02-67a49f7076e8", + "x": -900, + "y": -2120, + "z": 30.8, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "House_A", + "persistentUuid": "53fa4ca3-f32d-57a4-a790-f95ca13955ef", + "x": -480, + "y": -2120, + "z": 31.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "House_C", + "persistentUuid": "fffa8387-b9ef-5fcf-84e5-18f2a5e5e8bf", + "x": 480, + "y": -2120, + "z": 35.5, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "House_D", + "persistentUuid": "e5e936c3-0a66-53ad-8be0-5629c8e20434", + "x": 900, + "y": -2120, + "z": 35.7, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "StreetLight", + "persistentUuid": "826e527c-267e-5120-9c37-9901486bda86", + "x": -1000, + "y": -2790, + "z": 39.8, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "StreetLight", + "persistentUuid": "f9d28718-a694-569f-9aa4-3407f67555b5", + "x": -580, + "y": -2790, + "z": 40, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "StreetLight", + "persistentUuid": "fcf92b53-26a3-56b1-827a-d674449fab8e", + "x": -200, + "y": -2790, + "z": 40, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "StreetLight", + "persistentUuid": "88671eb1-f0d1-51e6-b2d6-6d549473e5f6", + "x": 200, + "y": -2790, + "z": 40, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "StreetLight", + "persistentUuid": "c155b968-87b0-5f5f-ad2a-2bd40dfa4c60", + "x": 580, + "y": -2790, + "z": 40, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "StreetLight", + "persistentUuid": "1734b728-2613-58fe-95e8-2c3dd210c7ec", + "x": 1000, + "y": -2790, + "z": 39.9, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -90.3, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "9f93b1c1-3572-529a-b022-8cf9fc819d31", + "x": 3344.7, + "y": 579.8, + "z": 83.8, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -90.3, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "d2a16962-13c5-5cc7-8f26-2a4278fc7024", + "x": 3344.2, + "y": 479.8, + "z": 94.2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -94.6, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "cc1f3056-4521-5498-9a58-167965a1f9e8", + "x": 3339.2, + "y": 361.2, + "z": 105.9, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -94.6, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "4ad75c9b-3f2b-55f5-9253-389e4dc5e640", + "x": 3331.2, + "y": 261.7, + "z": 117, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -96.9, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "dd8bc472-30b8-5e36-b1c7-902e1112fcbe", + "x": 3320.2, + "y": 152.3, + "z": 128.2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -96.9, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "5c73a8fe-7e06-5183-89df-a95978ce0efe", + "x": 3308.2, + "y": 52.8, + "z": 138.8, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -98.3, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "50995a1e-b242-5c44-9da1-ce067e3f857e", + "x": 3294.1, + "y": -52.7, + "z": 150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -98.3, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "f733475a-1468-56ef-a581-8912ee1508b4", + "x": 3279.6, + "y": -151.7, + "z": 157.8, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -101.5, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "b7a3be7c-6182-5a6c-89b7-638516ef8786", + "x": 3260, + "y": -264, + "z": 166.9, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -101.5, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "7c5a0326-a0ba-5811-9533-b602ee42b640", + "x": 3240, + "y": -362, + "z": 174.3, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -104.2, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "0ae65da1-9ce8-51ba-b563-cf79d351a5d7", + "x": 3215.1, + "y": -470.7, + "z": 175.9, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -104.2, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "9bde0edb-c841-50e2-bcde-c5791ae0ef52", + "x": 3190.6, + "y": -567.7, + "z": 174.5, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -106.6, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "d88cf7c1-58e9-5d18-a094-62b2e69b3528", + "x": 3161.3, + "y": -674.2, + "z": 169.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -106.6, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "5187a6bf-837a-5376-832c-3ba0f92bfe13", + "x": 3132.8, + "y": -769.7, + "z": 161.2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -109.2, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "7c94725c-0b98-5dde-86df-96d2c6b6a096", + "x": 3098.5, + "y": -875.7, + "z": 150.7, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -109.2, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "c3edefe8-7d0f-56f6-8335-940af16443f7", + "x": 3065.5, + "y": -970.2, + "z": 137.3, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -112.3, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "22e3fe21-2387-54c4-93f3-c071ce18b99f", + "x": 3025.2, + "y": -1076.2, + "z": 121.8, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -112.3, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barrier", + "persistentUuid": "941154b3-f266-51e2-bb44-f14d96ef05a5", + "x": 2987.2, + "y": -1168.7, + "z": 106.7, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "PlayerCar", + "persistentUuid": "bb17e7e7-676e-5cc2-925c-03e748396321", + "x": -2300, + "y": 2450, + "z": 0, + "zOrder": 8, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "TrafficCone", + "persistentUuid": "ac9fd555-751c-59d0-ae39-e86fc6c6e6dd", + "x": -1300, + "y": 2300, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "TrafficCone", + "persistentUuid": "0fd0eaa9-d4b6-5b56-ac36-577e10cc4554", + "x": -1300, + "y": 2337, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "TrafficCone", + "persistentUuid": "2aa73a87-73d2-5a4b-9e10-73b41b198b19", + "x": -1300, + "y": 2374, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "TrafficCone", + "persistentUuid": "eef8fc30-8a31-553c-b0e1-485500323c24", + "x": -1300, + "y": 2411, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "TrafficCone", + "persistentUuid": "de8c080f-93c9-59a6-862c-c4e789d6a400", + "x": -1300, + "y": 2448, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "TrafficCone", + "persistentUuid": "e7ece8b7-b4f2-54e5-add5-77a609cfa4e7", + "x": -1300, + "y": 2485, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "TrafficCone", + "persistentUuid": "224dc7d8-0845-591c-970e-c44543bd9462", + "x": -1300, + "y": 2522, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "TrafficCone", + "persistentUuid": "4ce9b471-eb3d-5822-82e2-2b69a7674d18", + "x": -1300, + "y": 2559, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "TrafficCone", + "persistentUuid": "fc868dfc-87cf-57f8-87f2-04a7d8db1925", + "x": -1300, + "y": 2596, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 72, + "customSize": true, + "height": 320, + "width": 320, + "layer": "", + "locked": false, + "name": "Road_3D", + "persistentUuid": "5f371d5f-c27f-5122-9c03-94c760b35772", + "x": -68, + "y": -944, + "z": -364.1, + "zOrder": 3, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 584, + "rotationY": 75 + }, + { + "angle": 102, + "customSize": true, + "height": 320, + "width": 320, + "layer": "", + "locked": false, + "name": "Road_3D", + "persistentUuid": "75f5fe3d-5561-58d8-a28f-bd7e63a48111", + "x": -54, + "y": 1412, + "z": -356.9, + "zOrder": 3, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 584, + "rotationY": 75 + }, + { + "angle": 0, + "customSize": true, + "height": 8192, + "width": 8192, + "layer": "", + "locked": true, + "name": "Trees", + "persistentUuid": "72ee0477-62d2-5b11-84a7-d2c96a463dfb", + "x": -4096, + "y": -4096, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 8192, + "width": 8192, + "layer": "", + "locked": true, + "name": "OakTrees", + "persistentUuid": "26037d6d-5fa8-57c1-a861-2ec5b9279b40", + "x": -4096, + "y": -4096, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 8192, + "width": 8192, + "layer": "", + "locked": true, + "name": "PineTrees", + "persistentUuid": "962c4be1-b0bb-5ca8-96bc-7e0a5195c814", + "x": -4096, + "y": -4096, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 8192, + "width": 8192, + "layer": "", + "locked": true, + "name": "Rocks", + "persistentUuid": "a0b8e715-2553-5057-955b-0333fd5a1915", + "x": -4096, + "y": -4096, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + } + ], + "objects": [ + { + "assetStoreId": "", + "name": "Road_3D", + "persistentUuid": "4aaecc79-6584-413a-9774-06f6b3b3ca3a", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "density": 1, + "friction": 7, + "restitution": 0, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0 + } + ], + "content": { + "width": 128, + "height": 128, + "depth": 128, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "frontFaceResourceName": "assets\\StartingCheckeredPattern.png", + "backFaceResourceName": "assets\\StartingCheckeredPattern.png", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "assets\\StartingCheckeredPattern.png", + "rightFaceResourceName": "assets\\StartingCheckeredPattern.png", + "topFaceResourceName": "assets\\StartingCheckeredPattern.png", + "bottomFaceResourceName": "assets\\StartingCheckeredPattern.png", + "frontFaceVisible": true, + "backFaceVisible": true, + "leftFaceVisible": true, + "rightFaceVisible": true, + "topFaceVisible": true, + "bottomFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceRepeat": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceRepeat": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceRepeat": true, + "materialType": "StandardWithoutMetalness", + "tint": "255;255;255", + "isCastingShadow": true, + "isReceivingShadow": true, + "tileScale": 0.2 + } + }, + { + "assetStoreId": "cddab55130dbd5a0b36f763200e876fb514aa608e1b057a136ebdb5f80006e2c", + "name": "PlayerCar", + "persistentUuid": "79ad5b66-80a6-493b-8ebf-9d239b83b776", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Dynamic", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "shapeOrientation": "Z", + "shapeDimensionA": 74, + "shapeDimensionB": 39, + "shapeDimensionC": 27, + "density": 1, + "friction": 10, + "restitution": 0.5, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 6, + "layers": 17, + "masks": 17, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 6, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": -25, + "massOverride": 1500 + }, + { + "name": "PhysicsCar3D", + "type": "Physics3D::PhysicsCar3D", + "physics3D": "Physics3D", + "steerAngleMax": 70, + "beginningSteerSpeed": 140, + "endSteerSpeed": 10, + "mass": 1500, + "engineTorqueMax": 8000, + "engineSpeedMax": 6000, + "engineInertia": 0.1, + "reverseGearRatio1": -2.9, + "gearRatio1": 2.66, + "gearRatio2": 1.78, + "gearRatio3": 1.3, + "gearRatio4": 1, + "gearRatio5": 0.74, + "gearRatio6": 0, + "backWheelOffsetX": -6, + "frontWheelOffsetX": 10, + "wheelOffsetY": 3, + "wheelOffsetZ": 0, + "wheelRadius": 6, + "wheelWidth": 3, + "brakeTorqueMax": 3000, + "handBrakeTorqueMax": 6000, + "hasBackWheelDrive": false, + "hasFrontWheelDrive": true, + "pitchRollAngleMax": 89 + }, + { + "name": "PhysicsCar3DKeyboardMapper", + "type": "PhysicsCar3DKeyMapper::PhysicsCar3DKeyboardMapper", + "PhysicsCar3D": "PhysicsCar3D", + "HandBrake": "Space", + "Up": "Up", + "Left": "Left", + "Down": "Down", + "Right": "Right" + }, + { + "name": "PhysicsCar3DMultitouchMapper", + "type": "SpriteMultitouchJoystick::PhysicsCar3DMultitouchMapper", + "PhysicsCar3D": "PhysicsCar3D", + "ControllerIdentifier": 1, + "SteerJoystickIdentifier": "Primary", + "SpeedJoystickIdentifier": "Secondary", + "HandBrakeButton": "B" + }, + { + "name": "ThirdPersonCamera", + "type": "ThirdPersonCamera::ThirdPersonCamera", + "Object3D": "Object3D", + "RotationHalfwayDuration": 0.125, + "TranslationZHalfwayDuration": 0.25, + "Distance": 450, + "LateralOffset": 0, + "AheadOffset": 200, + "OffsetZ": 0, + "RotationAngleOffset": 0, + "ElevationAngleOffset": 10, + "FollowFreeAreaZMax": 0, + "FollowFreeAreaZMin": 0, + "RotationLogSpeed": 2.0247e-320, + "TranslationZLogSpeed": 2.0247e-320, + "IsCalledManually": false, + "CameraZ": 0, + "LocalOffsetY": 200, + "LocalOffsetX": 0, + "OffsetY": 200, + "ElevationHalfwayDuration": 1, + "IsElevatingWithObject": false + } + ], + "content": { + "centerLocation": "ObjectCenter", + "crossfadeDuration": 0, + "depth": 33, + "height": 39, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets\\Orange - Sedan Sports.glb", + "originLocation": "ModelOrigin", + "rotationX": 90, + "rotationY": 0, + "rotationZ": -90, + "width": 74, + "animations": [] + } + }, + { + "assetStoreId": "e71bd69f896d6c7531b48c65ceb5da25071d4fbdeb518aeceecba8d21f34ed8d", + "name": "Speed_Joystick", + "persistentUuid": "9cec9083-d43d-4a9d-9017-7f44615dc54b", + "type": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "variant": "", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "DeadZoneRadius": 0.2, + "JoystickIdentifier": "Secondary" + }, + "childrenContent": { + "Border": { + "adaptCollisionMaskAutomatically": false, + "updateIfNotVisible": false, + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": false, + "image": "assets/Flat dark joystick border vertical.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 0, + "y": 0 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [] + } + ] + } + ] + } + ] + }, + "Thumb": { + "adaptCollisionMaskAutomatically": false, + "updateIfNotVisible": false, + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": false, + "image": "assets\\Flat dark joystick thumb.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 0, + "y": 0 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [] + } + ] + } + ] + } + ] + } + } + }, + { + "assetStoreId": "e71bd69f896d6c7531b48c65ceb5da25071d4fbdeb518aeceecba8d21f34ed8d", + "name": "Steer_Joystick", + "persistentUuid": "c2b50763-9100-4f85-97fa-c2d22ed2fa6f", + "type": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "variant": "", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "JoystickIdentifier": "Primary", + "DeadZoneRadius": 0.2 + }, + "childrenContent": { + "Border": { + "adaptCollisionMaskAutomatically": false, + "updateIfNotVisible": false, + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": false, + "image": "assets/Flat dark joystick border horizontal.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 0, + "y": 0 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [] + } + ] + } + ] + } + ] + }, + "Thumb": { + "adaptCollisionMaskAutomatically": false, + "updateIfNotVisible": false, + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": false, + "image": "assets\\Flat dark joystick thumb.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 0, + "y": 0 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [] + } + ] + } + ] + } + ] + } + } + }, + { + "assetStoreId": "8f75165255eac00288d9b81c0063f2a3bdb26a7748fc7b2e17963a426f0a0aa4", + "name": "TrafficCone", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Dynamic", + "bullet": false, + "fixedRotation": false, + "shape": "Cylinder", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 2, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 2, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "ObjectCenter", + "crossfadeDuration": 0.10000000149011612, + "depth": 32, + "height": 32, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "Traffic Cone.glb", + "originLocation": "ModelOrigin", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 90, + "width": 32, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "Terrain", + "type": "Terrain3D::Terrain3D", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 7, + "restitution": 0, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "Relief": "Flat", + "Seed": 1, + "Resolution": "256", + "TextureSize": 350, + "CliffLayer": "3", + "CliffAngle": 38, + "NormalMapStrength": 1, + "HeightBlending": 0.8, + "TextureVariation": 0.5, + "Edits": "[{\"tool\":\"raise\",\"x\":0,\"y\":0,\"toX\":1,\"toY\":0,\"radius\":0.1099,\"height\":0.4063},{\"tool\":\"raise\",\"x\":0,\"y\":1,\"toX\":1,\"toY\":1,\"radius\":0.1099,\"height\":0.4063},{\"tool\":\"raise\",\"x\":0,\"y\":0,\"toX\":0,\"toY\":1,\"radius\":0.1099,\"height\":0.4063},{\"tool\":\"raise\",\"x\":1,\"y\":0,\"toX\":1,\"toY\":1,\"radius\":0.1099,\"height\":0.4063},{\"tool\":\"raise\",\"x\":0.1826,\"y\":0,\"radius\":0.0854,\"height\":0.3125},{\"tool\":\"raise\",\"x\":0.5732,\"y\":0,\"radius\":0.1099,\"height\":0.4063},{\"tool\":\"raise\",\"x\":0.8662,\"y\":0,\"radius\":0.0977,\"height\":0.2813},{\"tool\":\"raise\",\"x\":1,\"y\":0.2925,\"radius\":0.0977,\"height\":0.375},{\"tool\":\"raise\",\"x\":1,\"y\":0.6831,\"radius\":0.0854,\"height\":0.2625},{\"tool\":\"raise\",\"x\":0.7686,\"y\":1,\"radius\":0.1099,\"height\":0.35},{\"tool\":\"raise\",\"x\":0.3535,\"y\":1,\"radius\":0.0977,\"height\":0.2625},{\"tool\":\"raise\",\"x\":0,\"y\":0.8174,\"radius\":0.0977,\"height\":0.325},{\"tool\":\"raise\",\"x\":0,\"y\":0.3901,\"radius\":0.0854,\"height\":0.2375},{\"tool\":\"raise\",\"x\":0.7991,\"y\":0.4634,\"radius\":0.1831,\"height\":0.2063},{\"tool\":\"raise\",\"x\":0.7441,\"y\":0.3901,\"radius\":0.0977,\"height\":0.0563},{\"tool\":\"raise\",\"x\":0.2681,\"y\":0.3901,\"radius\":0.1343,\"height\":0.1063},{\"tool\":\"raise\",\"x\":0.5366,\"y\":0.5732,\"radius\":0.1099,\"height\":0.0688},{\"tool\":\"raise\",\"x\":0.3779,\"y\":0.6587,\"radius\":0.0732,\"height\":0.0375},{\"tool\":\"raise\",\"x\":0.6831,\"y\":0.8784,\"radius\":0.0854,\"height\":0.0563},{\"tool\":\"smooth\",\"x\":0.5,\"y\":0.5,\"radius\":0.4395,\"strength\":0.3},{\"tool\":\"flatten\",\"x\":0.3535,\"y\":0.1887,\"toX\":0.6465,\"toY\":0.1887,\"radius\":0.1099,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.3535,\"y\":0.1887,\"toX\":0.6465,\"toY\":0.1887,\"radius\":0.1099,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.3535,\"y\":0.1887,\"toX\":0.6465,\"toY\":0.1887,\"radius\":0.1099,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.3901,\"y\":0.1362,\"radius\":0.0317,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.3901,\"y\":0.1362,\"radius\":0.0317,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.4414,\"y\":0.1362,\"radius\":0.0317,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.4414,\"y\":0.1362,\"radius\":0.0317,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.5586,\"y\":0.1362,\"radius\":0.0317,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.5586,\"y\":0.1362,\"radius\":0.0317,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.6099,\"y\":0.1362,\"radius\":0.0317,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.6099,\"y\":0.1362,\"radius\":0.0317,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.3901,\"y\":0.2412,\"radius\":0.0317,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.3901,\"y\":0.2412,\"radius\":0.0317,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.4414,\"y\":0.2412,\"radius\":0.0317,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.4414,\"y\":0.2412,\"radius\":0.0317,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.5586,\"y\":0.2412,\"radius\":0.0317,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.5586,\"y\":0.2412,\"radius\":0.0317,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.6099,\"y\":0.2412,\"radius\":0.0317,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.6099,\"y\":0.2412,\"radius\":0.0317,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.146,\"y\":0.7991,\"toX\":0.5732,\"toY\":0.7991,\"radius\":0.061,\"z\":0},{\"tool\":\"flatten\",\"x\":0.146,\"y\":0.7991,\"toX\":0.5732,\"toY\":0.7991,\"radius\":0.061,\"z\":0},{\"tool\":\"flatten\",\"x\":0.146,\"y\":0.7991,\"toX\":0.5732,\"toY\":0.7991,\"radius\":0.061,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1826,\"y\":0.7991,\"toX\":0.2069,\"toY\":0.801,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.2069,\"y\":0.801,\"toX\":0.2313,\"toY\":0.8018,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.2313,\"y\":0.8018,\"toX\":0.2557,\"toY\":0.802,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.2557,\"y\":0.802,\"toX\":0.2802,\"toY\":0.8019,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.2802,\"y\":0.8019,\"toX\":0.3046,\"toY\":0.8016,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.3046,\"y\":0.8016,\"toX\":0.329,\"toY\":0.8011,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.329,\"y\":0.8011,\"toX\":0.3534,\"toY\":0.8007,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.3534,\"y\":0.8007,\"toX\":0.3778,\"toY\":0.8,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.3778,\"y\":0.8,\"toX\":0.4022,\"toY\":0.7996,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4022,\"y\":0.7996,\"toX\":0.4266,\"toY\":0.7991,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4266,\"y\":0.7991,\"toX\":0.451,\"toY\":0.7988,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.451,\"y\":0.7988,\"toX\":0.4755,\"toY\":0.7988,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4755,\"y\":0.7988,\"toX\":0.4999,\"toY\":0.7988,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4999,\"y\":0.7988,\"toX\":0.5243,\"toY\":0.7987,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.5243,\"y\":0.7987,\"toX\":0.5487,\"toY\":0.7981,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.5487,\"y\":0.7981,\"toX\":0.5731,\"toY\":0.7969,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.5731,\"y\":0.7969,\"toX\":0.5974,\"toY\":0.7946,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.5974,\"y\":0.7946,\"toX\":0.6216,\"toY\":0.7913,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.6216,\"y\":0.7913,\"toX\":0.6458,\"toY\":0.7877,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.6458,\"y\":0.7877,\"toX\":0.6698,\"toY\":0.7836,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.6698,\"y\":0.7836,\"toX\":0.6937,\"toY\":0.7787,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.6937,\"y\":0.7787,\"toX\":0.7173,\"toY\":0.7725,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.7173,\"y\":0.7725,\"toX\":0.7404,\"toY\":0.7644,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.7404,\"y\":0.7644,\"toX\":0.7623,\"toY\":0.7539,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.7623,\"y\":0.7539,\"toX\":0.7828,\"toY\":0.7405,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.7828,\"y\":0.7405,\"toX\":0.8015,\"toY\":0.7249,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.8015,\"y\":0.7249,\"toX\":0.8187,\"toY\":0.7075,\"radius\":0.0417,\"z\":0.0001},{\"tool\":\"flatten\",\"x\":0.8187,\"y\":0.7075,\"toX\":0.8342,\"toY\":0.6887,\"radius\":0.0417,\"z\":0.0009},{\"tool\":\"flatten\",\"x\":0.8342,\"y\":0.6887,\"toX\":0.8481,\"toY\":0.6687,\"radius\":0.0417,\"z\":0.0038},{\"tool\":\"flatten\",\"x\":0.8481,\"y\":0.6687,\"toX\":0.8602,\"toY\":0.6475,\"radius\":0.0417,\"z\":0.0097},{\"tool\":\"flatten\",\"x\":0.8602,\"y\":0.6475,\"toX\":0.8696,\"toY\":0.625,\"radius\":0.0417,\"z\":0.0192},{\"tool\":\"flatten\",\"x\":0.8696,\"y\":0.625,\"toX\":0.8755,\"toY\":0.6013,\"radius\":0.0417,\"z\":0.031},{\"tool\":\"flatten\",\"x\":0.8755,\"y\":0.6013,\"toX\":0.8778,\"toY\":0.577,\"radius\":0.0417,\"z\":0.0435},{\"tool\":\"flatten\",\"x\":0.8778,\"y\":0.577,\"toX\":0.8777,\"toY\":0.5526,\"radius\":0.0417,\"z\":0.056},{\"tool\":\"flatten\",\"x\":0.8777,\"y\":0.5526,\"toX\":0.8757,\"toY\":0.5283,\"radius\":0.0417,\"z\":0.0685},{\"tool\":\"flatten\",\"x\":0.8757,\"y\":0.5283,\"toX\":0.8728,\"toY\":0.504,\"radius\":0.0417,\"z\":0.081},{\"tool\":\"flatten\",\"x\":0.8728,\"y\":0.504,\"toX\":0.8693,\"toY\":0.4799,\"radius\":0.0417,\"z\":0.0935},{\"tool\":\"flatten\",\"x\":0.8693,\"y\":0.4799,\"toX\":0.8644,\"toY\":0.4559,\"radius\":0.0417,\"z\":0.106},{\"tool\":\"flatten\",\"x\":0.8644,\"y\":0.4559,\"toX\":0.8584,\"toY\":0.4323,\"radius\":0.0417,\"z\":0.1173},{\"tool\":\"flatten\",\"x\":0.8584,\"y\":0.4323,\"toX\":0.8514,\"toY\":0.4089,\"radius\":0.0417,\"z\":0.1183},{\"tool\":\"flatten\",\"x\":0.8514,\"y\":0.4089,\"toX\":0.8434,\"toY\":0.3859,\"radius\":0.0417,\"z\":0.1081},{\"tool\":\"flatten\",\"x\":0.8434,\"y\":0.3859,\"toX\":0.8341,\"toY\":0.3633,\"radius\":0.0417,\"z\":0.0956},{\"tool\":\"flatten\",\"x\":0.8341,\"y\":0.3633,\"toX\":0.8236,\"toY\":0.3412,\"radius\":0.0417,\"z\":0.0831},{\"tool\":\"flatten\",\"x\":0.8236,\"y\":0.3412,\"toX\":0.8124,\"toY\":0.3196,\"radius\":0.0417,\"z\":0.0706},{\"tool\":\"flatten\",\"x\":0.8124,\"y\":0.3196,\"toX\":0.8003,\"toY\":0.2983,\"radius\":0.0417,\"z\":0.0581},{\"tool\":\"flatten\",\"x\":0.8003,\"y\":0.2983,\"toX\":0.7871,\"toY\":0.2778,\"radius\":0.0417,\"z\":0.0456},{\"tool\":\"flatten\",\"x\":0.7871,\"y\":0.2778,\"toX\":0.7722,\"toY\":0.2584,\"radius\":0.0417,\"z\":0.0331},{\"tool\":\"flatten\",\"x\":0.7722,\"y\":0.2584,\"toX\":0.7554,\"toY\":0.2408,\"radius\":0.0417,\"z\":0.0206},{\"tool\":\"flatten\",\"x\":0.7554,\"y\":0.2408,\"toX\":0.7362,\"toY\":0.2258,\"radius\":0.0417,\"z\":0.0128},{\"tool\":\"flatten\",\"x\":0.7362,\"y\":0.2258,\"toX\":0.715,\"toY\":0.2137,\"radius\":0.0417,\"z\":0.0123},{\"tool\":\"flatten\",\"x\":0.715,\"y\":0.2137,\"toX\":0.6924,\"toY\":0.2046,\"radius\":0.0417,\"z\":0.0146},{\"tool\":\"flatten\",\"x\":0.6924,\"y\":0.2046,\"toX\":0.6688,\"toY\":0.1981,\"radius\":0.0417,\"z\":0.0174},{\"tool\":\"flatten\",\"x\":0.6688,\"y\":0.1981,\"toX\":0.6448,\"toY\":0.1936,\"radius\":0.0417,\"z\":0.0219},{\"tool\":\"flatten\",\"x\":0.6448,\"y\":0.1936,\"toX\":0.6207,\"toY\":0.1901,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.6207,\"y\":0.1901,\"toX\":0.5964,\"toY\":0.1873,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.5964,\"y\":0.1873,\"toX\":0.572,\"toY\":0.1855,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.572,\"y\":0.1855,\"toX\":0.5477,\"toY\":0.1844,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.5477,\"y\":0.1844,\"toX\":0.5233,\"toY\":0.1838,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.5233,\"y\":0.1838,\"toX\":0.4989,\"toY\":0.1837,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.4989,\"y\":0.1837,\"toX\":0.4745,\"toY\":0.1841,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.4745,\"y\":0.1841,\"toX\":0.4501,\"toY\":0.1848,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.4501,\"y\":0.1848,\"toX\":0.4257,\"toY\":0.186,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.4257,\"y\":0.186,\"toX\":0.4014,\"toY\":0.1877,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.4014,\"y\":0.1877,\"toX\":0.377,\"toY\":0.1899,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.377,\"y\":0.1899,\"toX\":0.3527,\"toY\":0.1925,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.3527,\"y\":0.1925,\"toX\":0.3285,\"toY\":0.1956,\"radius\":0.0417,\"z\":0.0218},{\"tool\":\"flatten\",\"x\":0.3285,\"y\":0.1956,\"toX\":0.3044,\"toY\":0.1996,\"radius\":0.0417,\"z\":0.0172},{\"tool\":\"flatten\",\"x\":0.3044,\"y\":0.1996,\"toX\":0.2806,\"toY\":0.2051,\"radius\":0.0417,\"z\":0.0144},{\"tool\":\"flatten\",\"x\":0.2806,\"y\":0.2051,\"toX\":0.2574,\"toY\":0.2126,\"radius\":0.0417,\"z\":0.0116},{\"tool\":\"flatten\",\"x\":0.2574,\"y\":0.2126,\"toX\":0.2354,\"toY\":0.223,\"radius\":0.0417,\"z\":0.0089},{\"tool\":\"flatten\",\"x\":0.2354,\"y\":0.223,\"toX\":0.215,\"toY\":0.2363,\"radius\":0.0417,\"z\":0.0061},{\"tool\":\"flatten\",\"x\":0.215,\"y\":0.2363,\"toX\":0.1962,\"toY\":0.252,\"radius\":0.0417,\"z\":0.0034},{\"tool\":\"flatten\",\"x\":0.1962,\"y\":0.252,\"toX\":0.1792,\"toY\":0.2695,\"radius\":0.0417,\"z\":0.0012},{\"tool\":\"flatten\",\"x\":0.1792,\"y\":0.2695,\"toX\":0.1638,\"toY\":0.2885,\"radius\":0.0417,\"z\":0.0001},{\"tool\":\"flatten\",\"x\":0.1638,\"y\":0.2885,\"toX\":0.1504,\"toY\":0.3088,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1504,\"y\":0.3088,\"toX\":0.1388,\"toY\":0.3303,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1388,\"y\":0.3303,\"toX\":0.1295,\"toY\":0.3529,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1295,\"y\":0.3529,\"toX\":0.1234,\"toY\":0.3765,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1234,\"y\":0.3765,\"toX\":0.1198,\"toY\":0.4006,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1198,\"y\":0.4006,\"toX\":0.1177,\"toY\":0.4249,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1177,\"y\":0.4249,\"toX\":0.1167,\"toY\":0.4493,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1167,\"y\":0.4493,\"toX\":0.1162,\"toY\":0.4738,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1162,\"y\":0.4738,\"toX\":0.1156,\"toY\":0.4982,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1156,\"y\":0.4982,\"toX\":0.115,\"toY\":0.5226,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.115,\"y\":0.5226,\"toX\":0.1152,\"toY\":0.547,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1152,\"y\":0.547,\"toX\":0.1161,\"toY\":0.5714,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1161,\"y\":0.5714,\"toX\":0.1173,\"toY\":0.5957,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1173,\"y\":0.5957,\"toX\":0.1189,\"toY\":0.6201,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1189,\"y\":0.6201,\"toX\":0.1206,\"toY\":0.6445,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1206,\"y\":0.6445,\"toX\":0.1222,\"toY\":0.6688,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1222,\"y\":0.6688,\"toX\":0.1235,\"toY\":0.6932,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1235,\"y\":0.6932,\"toX\":0.125,\"toY\":0.7175,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.125,\"y\":0.7175,\"toX\":0.1279,\"toY\":0.7418,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1279,\"y\":0.7418,\"toX\":0.1349,\"toY\":0.7651,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1349,\"y\":0.7651,\"toX\":0.144,\"toY\":0.7876,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.144,\"y\":0.7876,\"toX\":0.1663,\"toY\":0.7966,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1663,\"y\":0.7966,\"toX\":0.1826,\"toY\":0.7991,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1826,\"y\":0.7991,\"toX\":0.2069,\"toY\":0.801,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.2069,\"y\":0.801,\"toX\":0.2313,\"toY\":0.8018,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.2313,\"y\":0.8018,\"toX\":0.2557,\"toY\":0.802,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.2557,\"y\":0.802,\"toX\":0.2802,\"toY\":0.8019,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.2802,\"y\":0.8019,\"toX\":0.3046,\"toY\":0.8016,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.3046,\"y\":0.8016,\"toX\":0.329,\"toY\":0.8011,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.329,\"y\":0.8011,\"toX\":0.3534,\"toY\":0.8007,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.3534,\"y\":0.8007,\"toX\":0.3778,\"toY\":0.8,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.3778,\"y\":0.8,\"toX\":0.4022,\"toY\":0.7996,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4022,\"y\":0.7996,\"toX\":0.4266,\"toY\":0.7991,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4266,\"y\":0.7991,\"toX\":0.451,\"toY\":0.7988,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.451,\"y\":0.7988,\"toX\":0.4755,\"toY\":0.7988,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4755,\"y\":0.7988,\"toX\":0.4999,\"toY\":0.7988,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.4999,\"y\":0.7988,\"toX\":0.5243,\"toY\":0.7987,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.5243,\"y\":0.7987,\"toX\":0.5487,\"toY\":0.7981,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.5487,\"y\":0.7981,\"toX\":0.5731,\"toY\":0.7969,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.5731,\"y\":0.7969,\"toX\":0.5974,\"toY\":0.7946,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.5974,\"y\":0.7946,\"toX\":0.6216,\"toY\":0.7913,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.6216,\"y\":0.7913,\"toX\":0.6458,\"toY\":0.7877,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.6458,\"y\":0.7877,\"toX\":0.6698,\"toY\":0.7836,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.6698,\"y\":0.7836,\"toX\":0.6937,\"toY\":0.7787,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.6937,\"y\":0.7787,\"toX\":0.7173,\"toY\":0.7725,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.7173,\"y\":0.7725,\"toX\":0.7404,\"toY\":0.7644,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.7404,\"y\":0.7644,\"toX\":0.7623,\"toY\":0.7539,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.7623,\"y\":0.7539,\"toX\":0.7828,\"toY\":0.7405,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.7828,\"y\":0.7405,\"toX\":0.8015,\"toY\":0.7249,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.8015,\"y\":0.7249,\"toX\":0.8187,\"toY\":0.7075,\"radius\":0.0417,\"z\":0.0001},{\"tool\":\"flatten\",\"x\":0.8187,\"y\":0.7075,\"toX\":0.8342,\"toY\":0.6887,\"radius\":0.0417,\"z\":0.0009},{\"tool\":\"flatten\",\"x\":0.8342,\"y\":0.6887,\"toX\":0.8481,\"toY\":0.6687,\"radius\":0.0417,\"z\":0.0038},{\"tool\":\"flatten\",\"x\":0.8481,\"y\":0.6687,\"toX\":0.8602,\"toY\":0.6475,\"radius\":0.0417,\"z\":0.0097},{\"tool\":\"flatten\",\"x\":0.8602,\"y\":0.6475,\"toX\":0.8696,\"toY\":0.625,\"radius\":0.0417,\"z\":0.0192},{\"tool\":\"flatten\",\"x\":0.8696,\"y\":0.625,\"toX\":0.8755,\"toY\":0.6013,\"radius\":0.0417,\"z\":0.031},{\"tool\":\"flatten\",\"x\":0.8755,\"y\":0.6013,\"toX\":0.8778,\"toY\":0.577,\"radius\":0.0417,\"z\":0.0435},{\"tool\":\"flatten\",\"x\":0.8778,\"y\":0.577,\"toX\":0.8777,\"toY\":0.5526,\"radius\":0.0417,\"z\":0.056},{\"tool\":\"flatten\",\"x\":0.8777,\"y\":0.5526,\"toX\":0.8757,\"toY\":0.5283,\"radius\":0.0417,\"z\":0.0685},{\"tool\":\"flatten\",\"x\":0.8757,\"y\":0.5283,\"toX\":0.8728,\"toY\":0.504,\"radius\":0.0417,\"z\":0.081},{\"tool\":\"flatten\",\"x\":0.8728,\"y\":0.504,\"toX\":0.8693,\"toY\":0.4799,\"radius\":0.0417,\"z\":0.0935},{\"tool\":\"flatten\",\"x\":0.8693,\"y\":0.4799,\"toX\":0.8644,\"toY\":0.4559,\"radius\":0.0417,\"z\":0.106},{\"tool\":\"flatten\",\"x\":0.8644,\"y\":0.4559,\"toX\":0.8584,\"toY\":0.4323,\"radius\":0.0417,\"z\":0.1173},{\"tool\":\"flatten\",\"x\":0.8584,\"y\":0.4323,\"toX\":0.8514,\"toY\":0.4089,\"radius\":0.0417,\"z\":0.1183},{\"tool\":\"flatten\",\"x\":0.8514,\"y\":0.4089,\"toX\":0.8434,\"toY\":0.3859,\"radius\":0.0417,\"z\":0.1081},{\"tool\":\"flatten\",\"x\":0.8434,\"y\":0.3859,\"toX\":0.8341,\"toY\":0.3633,\"radius\":0.0417,\"z\":0.0956},{\"tool\":\"flatten\",\"x\":0.8341,\"y\":0.3633,\"toX\":0.8236,\"toY\":0.3412,\"radius\":0.0417,\"z\":0.0831},{\"tool\":\"flatten\",\"x\":0.8236,\"y\":0.3412,\"toX\":0.8124,\"toY\":0.3196,\"radius\":0.0417,\"z\":0.0706},{\"tool\":\"flatten\",\"x\":0.8124,\"y\":0.3196,\"toX\":0.8003,\"toY\":0.2983,\"radius\":0.0417,\"z\":0.0581},{\"tool\":\"flatten\",\"x\":0.8003,\"y\":0.2983,\"toX\":0.7871,\"toY\":0.2778,\"radius\":0.0417,\"z\":0.0456},{\"tool\":\"flatten\",\"x\":0.7871,\"y\":0.2778,\"toX\":0.7722,\"toY\":0.2584,\"radius\":0.0417,\"z\":0.0331},{\"tool\":\"flatten\",\"x\":0.7722,\"y\":0.2584,\"toX\":0.7554,\"toY\":0.2408,\"radius\":0.0417,\"z\":0.0206},{\"tool\":\"flatten\",\"x\":0.7554,\"y\":0.2408,\"toX\":0.7362,\"toY\":0.2258,\"radius\":0.0417,\"z\":0.0128},{\"tool\":\"flatten\",\"x\":0.7362,\"y\":0.2258,\"toX\":0.715,\"toY\":0.2137,\"radius\":0.0417,\"z\":0.0123},{\"tool\":\"flatten\",\"x\":0.715,\"y\":0.2137,\"toX\":0.6924,\"toY\":0.2046,\"radius\":0.0417,\"z\":0.0146},{\"tool\":\"flatten\",\"x\":0.6924,\"y\":0.2046,\"toX\":0.6688,\"toY\":0.1981,\"radius\":0.0417,\"z\":0.0174},{\"tool\":\"flatten\",\"x\":0.6688,\"y\":0.1981,\"toX\":0.6448,\"toY\":0.1936,\"radius\":0.0417,\"z\":0.0219},{\"tool\":\"flatten\",\"x\":0.6448,\"y\":0.1936,\"toX\":0.6207,\"toY\":0.1901,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.6207,\"y\":0.1901,\"toX\":0.5964,\"toY\":0.1873,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.5964,\"y\":0.1873,\"toX\":0.572,\"toY\":0.1855,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.572,\"y\":0.1855,\"toX\":0.5477,\"toY\":0.1844,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.5477,\"y\":0.1844,\"toX\":0.5233,\"toY\":0.1838,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.5233,\"y\":0.1838,\"toX\":0.4989,\"toY\":0.1837,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.4989,\"y\":0.1837,\"toX\":0.4745,\"toY\":0.1841,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.4745,\"y\":0.1841,\"toX\":0.4501,\"toY\":0.1848,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.4501,\"y\":0.1848,\"toX\":0.4257,\"toY\":0.186,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.4257,\"y\":0.186,\"toX\":0.4014,\"toY\":0.1877,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.4014,\"y\":0.1877,\"toX\":0.377,\"toY\":0.1899,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.377,\"y\":0.1899,\"toX\":0.3527,\"toY\":0.1925,\"radius\":0.0417,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.3527,\"y\":0.1925,\"toX\":0.3285,\"toY\":0.1956,\"radius\":0.0417,\"z\":0.0218},{\"tool\":\"flatten\",\"x\":0.3285,\"y\":0.1956,\"toX\":0.3044,\"toY\":0.1996,\"radius\":0.0417,\"z\":0.0172},{\"tool\":\"flatten\",\"x\":0.3044,\"y\":0.1996,\"toX\":0.2806,\"toY\":0.2051,\"radius\":0.0417,\"z\":0.0144},{\"tool\":\"flatten\",\"x\":0.2806,\"y\":0.2051,\"toX\":0.2574,\"toY\":0.2126,\"radius\":0.0417,\"z\":0.0116},{\"tool\":\"flatten\",\"x\":0.2574,\"y\":0.2126,\"toX\":0.2354,\"toY\":0.223,\"radius\":0.0417,\"z\":0.0089},{\"tool\":\"flatten\",\"x\":0.2354,\"y\":0.223,\"toX\":0.215,\"toY\":0.2363,\"radius\":0.0417,\"z\":0.0061},{\"tool\":\"flatten\",\"x\":0.215,\"y\":0.2363,\"toX\":0.1962,\"toY\":0.252,\"radius\":0.0417,\"z\":0.0034},{\"tool\":\"flatten\",\"x\":0.1962,\"y\":0.252,\"toX\":0.1792,\"toY\":0.2695,\"radius\":0.0417,\"z\":0.0012},{\"tool\":\"flatten\",\"x\":0.1792,\"y\":0.2695,\"toX\":0.1638,\"toY\":0.2885,\"radius\":0.0417,\"z\":0.0001},{\"tool\":\"flatten\",\"x\":0.1638,\"y\":0.2885,\"toX\":0.1504,\"toY\":0.3088,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1504,\"y\":0.3088,\"toX\":0.1388,\"toY\":0.3303,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1388,\"y\":0.3303,\"toX\":0.1295,\"toY\":0.3529,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1295,\"y\":0.3529,\"toX\":0.1234,\"toY\":0.3765,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1234,\"y\":0.3765,\"toX\":0.1198,\"toY\":0.4006,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1198,\"y\":0.4006,\"toX\":0.1177,\"toY\":0.4249,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1177,\"y\":0.4249,\"toX\":0.1167,\"toY\":0.4493,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1167,\"y\":0.4493,\"toX\":0.1162,\"toY\":0.4738,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1162,\"y\":0.4738,\"toX\":0.1156,\"toY\":0.4982,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1156,\"y\":0.4982,\"toX\":0.115,\"toY\":0.5226,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.115,\"y\":0.5226,\"toX\":0.1152,\"toY\":0.547,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1152,\"y\":0.547,\"toX\":0.1161,\"toY\":0.5714,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1161,\"y\":0.5714,\"toX\":0.1173,\"toY\":0.5957,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1173,\"y\":0.5957,\"toX\":0.1189,\"toY\":0.6201,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1189,\"y\":0.6201,\"toX\":0.1206,\"toY\":0.6445,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1206,\"y\":0.6445,\"toX\":0.1222,\"toY\":0.6688,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1222,\"y\":0.6688,\"toX\":0.1235,\"toY\":0.6932,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1235,\"y\":0.6932,\"toX\":0.125,\"toY\":0.7175,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.125,\"y\":0.7175,\"toX\":0.1279,\"toY\":0.7418,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1279,\"y\":0.7418,\"toX\":0.1349,\"toY\":0.7651,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1349,\"y\":0.7651,\"toX\":0.144,\"toY\":0.7876,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.144,\"y\":0.7876,\"toX\":0.1663,\"toY\":0.7966,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.1663,\"y\":0.7966,\"toX\":0.1826,\"toY\":0.7991,\"radius\":0.0417,\"z\":0},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.1887,\"toX\":0.4965,\"toY\":0.2129,\"radius\":0.0355,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.4965,\"y\":0.2129,\"toX\":0.4927,\"toY\":0.2369,\"radius\":0.0355,\"z\":0.0193},{\"tool\":\"flatten\",\"x\":0.4927,\"y\":0.2369,\"toX\":0.4895,\"toY\":0.2612,\"radius\":0.0355,\"z\":0.0127},{\"tool\":\"flatten\",\"x\":0.4895,\"y\":0.2612,\"toX\":0.4874,\"toY\":0.2855,\"radius\":0.0355,\"z\":0.0112},{\"tool\":\"flatten\",\"x\":0.4874,\"y\":0.2855,\"toX\":0.4873,\"toY\":0.3099,\"radius\":0.0355,\"z\":0.0091},{\"tool\":\"flatten\",\"x\":0.4873,\"y\":0.3099,\"toX\":0.4904,\"toY\":0.3341,\"radius\":0.0355,\"z\":0.0063},{\"tool\":\"flatten\",\"x\":0.4904,\"y\":0.3341,\"toX\":0.4962,\"toY\":0.3578,\"radius\":0.0355,\"z\":0.0036},{\"tool\":\"flatten\",\"x\":0.4962,\"y\":0.3578,\"toX\":0.5034,\"toY\":0.3811,\"radius\":0.0355,\"z\":0.0015},{\"tool\":\"flatten\",\"x\":0.5034,\"y\":0.3811,\"toX\":0.5112,\"toY\":0.4043,\"radius\":0.0355,\"z\":0.0017},{\"tool\":\"flatten\",\"x\":0.5112,\"y\":0.4043,\"toX\":0.5186,\"toY\":0.4275,\"radius\":0.0355,\"z\":0.005},{\"tool\":\"flatten\",\"x\":0.5186,\"y\":0.4275,\"toX\":0.5244,\"toY\":0.4513,\"radius\":0.0355,\"z\":0.0108},{\"tool\":\"flatten\",\"x\":0.5244,\"y\":0.4513,\"toX\":0.5288,\"toY\":0.4752,\"radius\":0.0355,\"z\":0.018},{\"tool\":\"flatten\",\"x\":0.5288,\"y\":0.4752,\"toX\":0.5326,\"toY\":0.4994,\"radius\":0.0355,\"z\":0.0252},{\"tool\":\"flatten\",\"x\":0.5326,\"y\":0.4994,\"toX\":0.5354,\"toY\":0.5237,\"radius\":0.0355,\"z\":0.0311},{\"tool\":\"flatten\",\"x\":0.5354,\"y\":0.5237,\"toX\":0.5369,\"toY\":0.548,\"radius\":0.0355,\"z\":0.0348},{\"tool\":\"flatten\",\"x\":0.5369,\"y\":0.548,\"toX\":0.5366,\"toY\":0.5724,\"radius\":0.0355,\"z\":0.0362},{\"tool\":\"flatten\",\"x\":0.5366,\"y\":0.5724,\"toX\":0.5343,\"toY\":0.5967,\"radius\":0.0355,\"z\":0.0363},{\"tool\":\"flatten\",\"x\":0.5343,\"y\":0.5967,\"toX\":0.5299,\"toY\":0.6207,\"radius\":0.0355,\"z\":0.0349},{\"tool\":\"flatten\",\"x\":0.5299,\"y\":0.6207,\"toX\":0.5245,\"toY\":0.6445,\"radius\":0.0355,\"z\":0.0314},{\"tool\":\"flatten\",\"x\":0.5245,\"y\":0.6445,\"toX\":0.5187,\"toY\":0.6682,\"radius\":0.0355,\"z\":0.0256},{\"tool\":\"flatten\",\"x\":0.5187,\"y\":0.6682,\"toX\":0.5129,\"toY\":0.6919,\"radius\":0.0355,\"z\":0.0185},{\"tool\":\"flatten\",\"x\":0.5129,\"y\":0.6919,\"toX\":0.5077,\"toY\":0.7158,\"radius\":0.0355,\"z\":0.0113},{\"tool\":\"flatten\",\"x\":0.5077,\"y\":0.7158,\"toX\":0.5021,\"toY\":0.7395,\"radius\":0.0355,\"z\":0.0055},{\"tool\":\"flatten\",\"x\":0.5021,\"y\":0.7395,\"toX\":0.4965,\"toY\":0.7633,\"radius\":0.0355,\"z\":0.0019},{\"tool\":\"flatten\",\"x\":0.4965,\"y\":0.7633,\"toX\":0.4907,\"toY\":0.787,\"radius\":0.0355,\"z\":0.0003},{\"tool\":\"flatten\",\"x\":0.4907,\"y\":0.787,\"toX\":0.4878,\"toY\":0.7991,\"radius\":0.0355,\"z\":0},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.1887,\"toX\":0.4965,\"toY\":0.2129,\"radius\":0.0355,\"z\":0.025},{\"tool\":\"flatten\",\"x\":0.4965,\"y\":0.2129,\"toX\":0.4927,\"toY\":0.2369,\"radius\":0.0355,\"z\":0.0193},{\"tool\":\"flatten\",\"x\":0.4927,\"y\":0.2369,\"toX\":0.4895,\"toY\":0.2612,\"radius\":0.0355,\"z\":0.0127},{\"tool\":\"flatten\",\"x\":0.4895,\"y\":0.2612,\"toX\":0.4874,\"toY\":0.2855,\"radius\":0.0355,\"z\":0.0112},{\"tool\":\"flatten\",\"x\":0.4874,\"y\":0.2855,\"toX\":0.4873,\"toY\":0.3099,\"radius\":0.0355,\"z\":0.0091},{\"tool\":\"flatten\",\"x\":0.4873,\"y\":0.3099,\"toX\":0.4904,\"toY\":0.3341,\"radius\":0.0355,\"z\":0.0063},{\"tool\":\"flatten\",\"x\":0.4904,\"y\":0.3341,\"toX\":0.4962,\"toY\":0.3578,\"radius\":0.0355,\"z\":0.0036},{\"tool\":\"flatten\",\"x\":0.4962,\"y\":0.3578,\"toX\":0.5034,\"toY\":0.3811,\"radius\":0.0355,\"z\":0.0015},{\"tool\":\"flatten\",\"x\":0.5034,\"y\":0.3811,\"toX\":0.5112,\"toY\":0.4043,\"radius\":0.0355,\"z\":0.0017},{\"tool\":\"flatten\",\"x\":0.5112,\"y\":0.4043,\"toX\":0.5186,\"toY\":0.4275,\"radius\":0.0355,\"z\":0.005},{\"tool\":\"flatten\",\"x\":0.5186,\"y\":0.4275,\"toX\":0.5244,\"toY\":0.4513,\"radius\":0.0355,\"z\":0.0108},{\"tool\":\"flatten\",\"x\":0.5244,\"y\":0.4513,\"toX\":0.5288,\"toY\":0.4752,\"radius\":0.0355,\"z\":0.018},{\"tool\":\"flatten\",\"x\":0.5288,\"y\":0.4752,\"toX\":0.5326,\"toY\":0.4994,\"radius\":0.0355,\"z\":0.0252},{\"tool\":\"flatten\",\"x\":0.5326,\"y\":0.4994,\"toX\":0.5354,\"toY\":0.5237,\"radius\":0.0355,\"z\":0.0311},{\"tool\":\"flatten\",\"x\":0.5354,\"y\":0.5237,\"toX\":0.5369,\"toY\":0.548,\"radius\":0.0355,\"z\":0.0348},{\"tool\":\"flatten\",\"x\":0.5369,\"y\":0.548,\"toX\":0.5366,\"toY\":0.5724,\"radius\":0.0355,\"z\":0.0362},{\"tool\":\"flatten\",\"x\":0.5366,\"y\":0.5724,\"toX\":0.5343,\"toY\":0.5967,\"radius\":0.0355,\"z\":0.0363},{\"tool\":\"flatten\",\"x\":0.5343,\"y\":0.5967,\"toX\":0.5299,\"toY\":0.6207,\"radius\":0.0355,\"z\":0.0349},{\"tool\":\"flatten\",\"x\":0.5299,\"y\":0.6207,\"toX\":0.5245,\"toY\":0.6445,\"radius\":0.0355,\"z\":0.0314},{\"tool\":\"flatten\",\"x\":0.5245,\"y\":0.6445,\"toX\":0.5187,\"toY\":0.6682,\"radius\":0.0355,\"z\":0.0256},{\"tool\":\"flatten\",\"x\":0.5187,\"y\":0.6682,\"toX\":0.5129,\"toY\":0.6919,\"radius\":0.0355,\"z\":0.0185},{\"tool\":\"flatten\",\"x\":0.5129,\"y\":0.6919,\"toX\":0.5077,\"toY\":0.7158,\"radius\":0.0355,\"z\":0.0113},{\"tool\":\"flatten\",\"x\":0.5077,\"y\":0.7158,\"toX\":0.5021,\"toY\":0.7395,\"radius\":0.0355,\"z\":0.0055},{\"tool\":\"flatten\",\"x\":0.5021,\"y\":0.7395,\"toX\":0.4965,\"toY\":0.7633,\"radius\":0.0355,\"z\":0.0019},{\"tool\":\"flatten\",\"x\":0.4965,\"y\":0.7633,\"toX\":0.4907,\"toY\":0.787,\"radius\":0.0355,\"z\":0.0003},{\"tool\":\"flatten\",\"x\":0.4907,\"y\":0.787,\"toX\":0.4878,\"toY\":0.7991,\"radius\":0.0355,\"z\":0},{\"tool\":\"smooth\",\"x\":0.1826,\"y\":0.7991,\"toX\":0.2557,\"toY\":0.802,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.2557,\"y\":0.802,\"toX\":0.329,\"toY\":0.8011,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.329,\"y\":0.8011,\"toX\":0.4022,\"toY\":0.7996,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.4022,\"y\":0.7996,\"toX\":0.4755,\"toY\":0.7988,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.4755,\"y\":0.7988,\"toX\":0.5487,\"toY\":0.7981,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.5487,\"y\":0.7981,\"toX\":0.6216,\"toY\":0.7913,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.6216,\"y\":0.7913,\"toX\":0.6937,\"toY\":0.7787,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.6937,\"y\":0.7787,\"toX\":0.7623,\"toY\":0.7539,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.7623,\"y\":0.7539,\"toX\":0.8187,\"toY\":0.7075,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.8187,\"y\":0.7075,\"toX\":0.8602,\"toY\":0.6475,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.8602,\"y\":0.6475,\"toX\":0.8778,\"toY\":0.577,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.8778,\"y\":0.577,\"toX\":0.8728,\"toY\":0.504,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.8728,\"y\":0.504,\"toX\":0.8584,\"toY\":0.4323,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.8584,\"y\":0.4323,\"toX\":0.8341,\"toY\":0.3633,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.8341,\"y\":0.3633,\"toX\":0.8003,\"toY\":0.2983,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.8003,\"y\":0.2983,\"toX\":0.7554,\"toY\":0.2408,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.7554,\"y\":0.2408,\"toX\":0.6924,\"toY\":0.2046,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.6924,\"y\":0.2046,\"toX\":0.6207,\"toY\":0.1901,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.6207,\"y\":0.1901,\"toX\":0.5477,\"toY\":0.1844,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.5477,\"y\":0.1844,\"toX\":0.4745,\"toY\":0.1841,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.4745,\"y\":0.1841,\"toX\":0.4014,\"toY\":0.1877,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.4014,\"y\":0.1877,\"toX\":0.3285,\"toY\":0.1956,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.3285,\"y\":0.1956,\"toX\":0.2574,\"toY\":0.2126,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.2574,\"y\":0.2126,\"toX\":0.1962,\"toY\":0.252,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.1962,\"y\":0.252,\"toX\":0.1504,\"toY\":0.3088,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.1504,\"y\":0.3088,\"toX\":0.1234,\"toY\":0.3765,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.1234,\"y\":0.3765,\"toX\":0.1167,\"toY\":0.4493,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.1167,\"y\":0.4493,\"toX\":0.115,\"toY\":0.5226,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.115,\"y\":0.5226,\"toX\":0.1173,\"toY\":0.5957,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.1173,\"y\":0.5957,\"toX\":0.1222,\"toY\":0.6688,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.1222,\"y\":0.6688,\"toX\":0.1279,\"toY\":0.7418,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.1279,\"y\":0.7418,\"toX\":0.1663,\"toY\":0.7966,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.1663,\"y\":0.7966,\"toX\":0.2313,\"toY\":0.8018,\"radius\":0.0348,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.5,\"y\":0.1887,\"toX\":0.4895,\"toY\":0.2612,\"radius\":0.0296,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.4895,\"y\":0.2612,\"toX\":0.4904,\"toY\":0.3341,\"radius\":0.0296,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.4904,\"y\":0.3341,\"toX\":0.5112,\"toY\":0.4043,\"radius\":0.0296,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.5112,\"y\":0.4043,\"toX\":0.5288,\"toY\":0.4752,\"radius\":0.0296,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.5288,\"y\":0.4752,\"toX\":0.5369,\"toY\":0.548,\"radius\":0.0296,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.5369,\"y\":0.548,\"toX\":0.5299,\"toY\":0.6207,\"radius\":0.0296,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.5299,\"y\":0.6207,\"toX\":0.5129,\"toY\":0.6919,\"radius\":0.0296,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.5129,\"y\":0.6919,\"toX\":0.4965,\"toY\":0.7633,\"radius\":0.0296,\"strength\":0.4},{\"tool\":\"smooth\",\"x\":0.4965,\"y\":0.7633,\"toX\":0.4878,\"toY\":0.7991,\"radius\":0.0296,\"strength\":0.4},{\"tool\":\"paint\",\"x\":0.1826,\"y\":0.7991,\"toX\":0.2557,\"toY\":0.802,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.2557,\"y\":0.802,\"toX\":0.329,\"toY\":0.8011,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.329,\"y\":0.8011,\"toX\":0.4022,\"toY\":0.7996,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.4022,\"y\":0.7996,\"toX\":0.4755,\"toY\":0.7988,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.4755,\"y\":0.7988,\"toX\":0.5487,\"toY\":0.7981,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.5487,\"y\":0.7981,\"toX\":0.6216,\"toY\":0.7913,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.6216,\"y\":0.7913,\"toX\":0.6937,\"toY\":0.7787,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.6937,\"y\":0.7787,\"toX\":0.7623,\"toY\":0.7539,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.7623,\"y\":0.7539,\"toX\":0.8187,\"toY\":0.7075,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.8187,\"y\":0.7075,\"toX\":0.8602,\"toY\":0.6475,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.8602,\"y\":0.6475,\"toX\":0.8778,\"toY\":0.577,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.8778,\"y\":0.577,\"toX\":0.8728,\"toY\":0.504,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.8728,\"y\":0.504,\"toX\":0.8584,\"toY\":0.4323,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.8584,\"y\":0.4323,\"toX\":0.8341,\"toY\":0.3633,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.8341,\"y\":0.3633,\"toX\":0.8003,\"toY\":0.2983,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.8003,\"y\":0.2983,\"toX\":0.7554,\"toY\":0.2408,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.7554,\"y\":0.2408,\"toX\":0.6924,\"toY\":0.2046,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.6924,\"y\":0.2046,\"toX\":0.6207,\"toY\":0.1901,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.6207,\"y\":0.1901,\"toX\":0.5477,\"toY\":0.1844,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.5477,\"y\":0.1844,\"toX\":0.4745,\"toY\":0.1841,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.4745,\"y\":0.1841,\"toX\":0.4014,\"toY\":0.1877,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.4014,\"y\":0.1877,\"toX\":0.3285,\"toY\":0.1956,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.3285,\"y\":0.1956,\"toX\":0.2574,\"toY\":0.2126,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.2574,\"y\":0.2126,\"toX\":0.1962,\"toY\":0.252,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.1962,\"y\":0.252,\"toX\":0.1504,\"toY\":0.3088,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.1504,\"y\":0.3088,\"toX\":0.1234,\"toY\":0.3765,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.1234,\"y\":0.3765,\"toX\":0.1167,\"toY\":0.4493,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.1167,\"y\":0.4493,\"toX\":0.115,\"toY\":0.5226,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.115,\"y\":0.5226,\"toX\":0.1173,\"toY\":0.5957,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.1173,\"y\":0.5957,\"toX\":0.1222,\"toY\":0.6688,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.1222,\"y\":0.6688,\"toX\":0.1279,\"toY\":0.7418,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.1279,\"y\":0.7418,\"toX\":0.1663,\"toY\":0.7966,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.1663,\"y\":0.7966,\"toX\":0.2313,\"toY\":0.8018,\"radius\":0.0348,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.5,\"y\":0.1887,\"toX\":0.4895,\"toY\":0.2612,\"radius\":0.0296,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.4895,\"y\":0.2612,\"toX\":0.4904,\"toY\":0.3341,\"radius\":0.0296,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.4904,\"y\":0.3341,\"toX\":0.5112,\"toY\":0.4043,\"radius\":0.0296,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.5112,\"y\":0.4043,\"toX\":0.5288,\"toY\":0.4752,\"radius\":0.0296,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.5288,\"y\":0.4752,\"toX\":0.5369,\"toY\":0.548,\"radius\":0.0296,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.5369,\"y\":0.548,\"toX\":0.5299,\"toY\":0.6207,\"radius\":0.0296,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.5299,\"y\":0.6207,\"toX\":0.5129,\"toY\":0.6919,\"radius\":0.0296,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.5129,\"y\":0.6919,\"toX\":0.4965,\"toY\":0.7633,\"radius\":0.0296,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.4965,\"y\":0.7633,\"toX\":0.4878,\"toY\":0.7991,\"radius\":0.0296,\"layer\":4,\"strength\":0.6},{\"tool\":\"paint\",\"x\":0.1826,\"y\":0.7991,\"toX\":0.2557,\"toY\":0.802,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.2557,\"y\":0.802,\"toX\":0.329,\"toY\":0.8011,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.329,\"y\":0.8011,\"toX\":0.4022,\"toY\":0.7996,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.4022,\"y\":0.7996,\"toX\":0.4755,\"toY\":0.7988,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.4755,\"y\":0.7988,\"toX\":0.5487,\"toY\":0.7981,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5487,\"y\":0.7981,\"toX\":0.6216,\"toY\":0.7913,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.6216,\"y\":0.7913,\"toX\":0.6937,\"toY\":0.7787,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.6937,\"y\":0.7787,\"toX\":0.7623,\"toY\":0.7539,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.7623,\"y\":0.7539,\"toX\":0.8187,\"toY\":0.7075,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8187,\"y\":0.7075,\"toX\":0.8602,\"toY\":0.6475,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8602,\"y\":0.6475,\"toX\":0.8778,\"toY\":0.577,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8778,\"y\":0.577,\"toX\":0.8728,\"toY\":0.504,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8728,\"y\":0.504,\"toX\":0.8584,\"toY\":0.4323,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8584,\"y\":0.4323,\"toX\":0.8341,\"toY\":0.3633,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8341,\"y\":0.3633,\"toX\":0.8003,\"toY\":0.2983,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8003,\"y\":0.2983,\"toX\":0.7554,\"toY\":0.2408,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.7554,\"y\":0.2408,\"toX\":0.6924,\"toY\":0.2046,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.6924,\"y\":0.2046,\"toX\":0.6207,\"toY\":0.1901,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.6207,\"y\":0.1901,\"toX\":0.5477,\"toY\":0.1844,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5477,\"y\":0.1844,\"toX\":0.4745,\"toY\":0.1841,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.4745,\"y\":0.1841,\"toX\":0.4014,\"toY\":0.1877,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.4014,\"y\":0.1877,\"toX\":0.3285,\"toY\":0.1956,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3285,\"y\":0.1956,\"toX\":0.2574,\"toY\":0.2126,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.2574,\"y\":0.2126,\"toX\":0.1962,\"toY\":0.252,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1962,\"y\":0.252,\"toX\":0.1504,\"toY\":0.3088,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1504,\"y\":0.3088,\"toX\":0.1234,\"toY\":0.3765,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1234,\"y\":0.3765,\"toX\":0.1167,\"toY\":0.4493,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1167,\"y\":0.4493,\"toX\":0.115,\"toY\":0.5226,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.115,\"y\":0.5226,\"toX\":0.1173,\"toY\":0.5957,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1173,\"y\":0.5957,\"toX\":0.1222,\"toY\":0.6688,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1222,\"y\":0.6688,\"toX\":0.1279,\"toY\":0.7418,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1279,\"y\":0.7418,\"toX\":0.1663,\"toY\":0.7966,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1663,\"y\":0.7966,\"toX\":0.2313,\"toY\":0.8018,\"radius\":0.0255,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5,\"y\":0.1887,\"toX\":0.4895,\"toY\":0.2612,\"radius\":0.0217,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.4895,\"y\":0.2612,\"toX\":0.4904,\"toY\":0.3341,\"radius\":0.0217,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.4904,\"y\":0.3341,\"toX\":0.5112,\"toY\":0.4043,\"radius\":0.0217,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5112,\"y\":0.4043,\"toX\":0.5288,\"toY\":0.4752,\"radius\":0.0217,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5288,\"y\":0.4752,\"toX\":0.5369,\"toY\":0.548,\"radius\":0.0217,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5369,\"y\":0.548,\"toX\":0.5299,\"toY\":0.6207,\"radius\":0.0217,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5299,\"y\":0.6207,\"toX\":0.5129,\"toY\":0.6919,\"radius\":0.0217,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5129,\"y\":0.6919,\"toX\":0.4965,\"toY\":0.7633,\"radius\":0.0217,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.4965,\"y\":0.7633,\"toX\":0.4878,\"toY\":0.7991,\"radius\":0.0217,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.4146,\"y\":0.1399,\"toX\":0.5854,\"toY\":0.1399,\"radius\":0.0317,\"layer\":4,\"strength\":0.8}]", + "Layer1Color": "255;255;255", + "Layer1Texture": "assets/TerrainGrass.jpg", + "Layer1NormalMap": "assets/TerrainGrassNormal.jpg", + "Layer2Color": "255;255;255", + "Layer2Texture": "assets/TerrainAsphalt.jpg", + "Layer2NormalMap": "assets/TerrainAsphaltNormal.jpg", + "Layer3Color": "255;255;255", + "Layer3Texture": "assets/TerrainRock.jpg", + "Layer3NormalMap": "assets/TerrainRockNormal.jpg", + "Layer4Color": "255;255;255", + "Layer4Texture": "assets/TerrainDirt.jpg", + "Layer4NormalMap": "assets/TerrainDirtNormal.jpg" + } + }, + { + "assetStoreId": "", + "name": "MapLimit", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "width": 100, + "height": 100, + "depth": 100, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "frontFaceResourceName": "", + "backFaceResourceName": "", + "leftFaceResourceName": "", + "rightFaceResourceName": "", + "topFaceResourceName": "", + "bottomFaceResourceName": "", + "backFaceUpThroughWhichAxisRotation": "X", + "frontFaceVisible": false, + "backFaceVisible": false, + "leftFaceVisible": false, + "rightFaceVisible": false, + "topFaceVisible": false, + "bottomFaceVisible": false, + "frontFaceResourceRepeat": true, + "backFaceResourceRepeat": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceRepeat": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceRepeat": false, + "materialType": "StandardWithoutMetalness", + "isCastingShadow": false, + "isReceivingShadow": true + } + }, + { + "assetStoreId": "", + "name": "Grass", + "type": "Terrain3D::TerrainGrass", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Density": 20, + "BendingObject": "PlayerCar", + "BladeHeight": 14, + "BladeWidth": 2, + "BaseColor": "40;62;22", + "TipColor": "112;130;52", + "FadeDistance": 2500 + } + }, + { + "assetStoreId": "cb95303eb996636066a01c48f6e2621e706dfdc26415d76654366d3335fc3eef", + "name": "House_A", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 170.2, + "height": 211.4, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/House Type 01.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 300, + "animations": [] + } + }, + { + "assetStoreId": "6b9ceb198b4260bf7689289957aea4d0daeb5a7365bee4fdeb30c2e844e03d64", + "name": "House_B", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 169.2, + "height": 300, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/House Type 05.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 300, + "animations": [] + } + }, + { + "assetStoreId": "979ae2af71fbaa21874014cf85a6554d6b55128dfb8193d3fdcecf0cfdac6208", + "name": "House_C", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 192.4, + "height": 211.4, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/House Type 09.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 300, + "animations": [] + } + }, + { + "assetStoreId": "fcbfd662c8c7ed23bb3556373a53c818b3b77ad487fd5601569577201e578616", + "name": "House_D", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 222.2, + "height": 203.6, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/House Type 13.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 300, + "animations": [] + } + }, + { + "assetStoreId": "794808182aee5d922d11d9bdb5d6338ac0055c9353a1b470b039743505b40ed6", + "name": "StreetLight", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Cylinder", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 5, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 170, + "height": 20.7, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Light Post.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 42.7, + "animations": [] + } + }, + { + "assetStoreId": "5517aa7e8bd5de5bc367d44b14c31983a9da0c4acc10d4c62dd559f23c9aa253", + "name": "Barrier", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 47.2, + "height": 44.3, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Barrier White.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 90, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "Trees", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "Model": "assets/Tree Default.glb", + "Size": 280, + "Density": 0.14, + "Seed": 1, + "Coverage": "Painted", + "Edits": "[{\"tool\":\"add\",\"x\":0.5096,\"y\":0.5249,\"radius\":0.0649},{\"tool\":\"add\",\"x\":0.5748,\"y\":0.7911,\"radius\":0.0624},{\"tool\":\"add\",\"x\":0.1514,\"y\":0.5253,\"radius\":0.0649},{\"tool\":\"add\",\"x\":0.4587,\"y\":0.4834,\"radius\":0.0736},{\"tool\":\"add\",\"x\":0.4336,\"y\":0.6412,\"radius\":0.0676},{\"tool\":\"add\",\"x\":0.2185,\"y\":0.2332,\"radius\":0.0529},{\"tool\":\"add\",\"x\":0.4136,\"y\":0.3978,\"radius\":0.0641},{\"tool\":\"add\",\"x\":0.1908,\"y\":0.5806,\"radius\":0.0638},{\"tool\":\"add\",\"x\":0.4323,\"y\":0.7858,\"radius\":0.0723},{\"tool\":\"add\",\"x\":0.2938,\"y\":0.1316,\"radius\":0.0698},{\"tool\":\"add\",\"x\":0.8965,\"y\":0.3313,\"radius\":0.0481},{\"tool\":\"erase\",\"x\":0.5,\"y\":0.1887,\"radius\":0.1587},{\"tool\":\"erase\",\"x\":0.2559,\"y\":0.7991,\"radius\":0.061},{\"tool\":\"erase\",\"x\":0.3901,\"y\":0.1362,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.4414,\"y\":0.1362,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.5586,\"y\":0.1362,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.6099,\"y\":0.1362,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.3901,\"y\":0.2412,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.4414,\"y\":0.2412,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.5586,\"y\":0.2412,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.6099,\"y\":0.2412,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.9083,\"y\":0.5708,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9082,\"y\":0.5586,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9076,\"y\":0.5441,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9066,\"y\":0.5319,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9053,\"y\":0.5186,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9038,\"y\":0.5064,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9021,\"y\":0.4936,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9003,\"y\":0.4815,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8979,\"y\":0.4678,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8955,\"y\":0.4558,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8925,\"y\":0.4425,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8895,\"y\":0.4307,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8859,\"y\":0.4177,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8824,\"y\":0.406,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8782,\"y\":0.3931,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8742,\"y\":0.3816,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8693,\"y\":0.3686,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8647,\"y\":0.3573,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.5112,\"y\":0.4043,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.5129,\"y\":0.6919,\"radius\":0.0439}]", + "MaxGroundZ": 120, + "MaxSlope": 25, + "TrunkCollisions": true, + "TrunkDiameter": 28 + } + }, + { + "assetStoreId": "", + "name": "OakTrees", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "Model": "assets/Tree Oak.glb", + "Size": 300, + "Density": 0.1, + "Seed": 2, + "Coverage": "Painted", + "Edits": "[{\"tool\":\"add\",\"x\":0.3241,\"y\":0.1478,\"radius\":0.0543},{\"tool\":\"add\",\"x\":0.8663,\"y\":0.2118,\"radius\":0.037},{\"tool\":\"add\",\"x\":0.6627,\"y\":0.6163,\"radius\":0.0536},{\"tool\":\"add\",\"x\":0.6963,\"y\":0.1102,\"radius\":0.0649},{\"tool\":\"add\",\"x\":0.356,\"y\":0.5127,\"radius\":0.0594},{\"tool\":\"add\",\"x\":0.6266,\"y\":0.526,\"radius\":0.061},{\"tool\":\"add\",\"x\":0.2803,\"y\":0.1945,\"radius\":0.0488},{\"tool\":\"add\",\"x\":0.13,\"y\":0.3109,\"radius\":0.0457},{\"tool\":\"erase\",\"x\":0.5,\"y\":0.1887,\"radius\":0.1587},{\"tool\":\"erase\",\"x\":0.2559,\"y\":0.7991,\"radius\":0.061},{\"tool\":\"erase\",\"x\":0.3901,\"y\":0.1362,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.4414,\"y\":0.1362,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.5586,\"y\":0.1362,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.6099,\"y\":0.1362,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.3901,\"y\":0.2412,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.4414,\"y\":0.2412,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.5586,\"y\":0.2412,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.6099,\"y\":0.2412,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.9083,\"y\":0.5708,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9082,\"y\":0.5586,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9076,\"y\":0.5441,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9066,\"y\":0.5319,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9053,\"y\":0.5186,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9038,\"y\":0.5064,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9021,\"y\":0.4936,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9003,\"y\":0.4815,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8979,\"y\":0.4678,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8955,\"y\":0.4558,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8925,\"y\":0.4425,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8895,\"y\":0.4307,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8859,\"y\":0.4177,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8824,\"y\":0.406,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8782,\"y\":0.3931,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8742,\"y\":0.3816,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8693,\"y\":0.3686,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8647,\"y\":0.3573,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.5112,\"y\":0.4043,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.5129,\"y\":0.6919,\"radius\":0.0439}]", + "MaxGroundZ": 120, + "MaxSlope": 25, + "TrunkCollisions": true, + "TrunkDiameter": 32 + } + }, + { + "assetStoreId": "", + "name": "PineTrees", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "Model": "assets/Tree Pine Default A.glb", + "Size": 340, + "Density": 0.16, + "Seed": 3, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.5,\"y\":0.1887,\"radius\":0.1587},{\"tool\":\"erase\",\"x\":0.2559,\"y\":0.7991,\"radius\":0.061},{\"tool\":\"erase\",\"x\":0.3901,\"y\":0.1362,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.4414,\"y\":0.1362,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.5586,\"y\":0.1362,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.6099,\"y\":0.1362,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.3901,\"y\":0.2412,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.4414,\"y\":0.2412,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.5586,\"y\":0.2412,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.6099,\"y\":0.2412,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.9083,\"y\":0.5708,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9082,\"y\":0.5586,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9076,\"y\":0.5441,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9066,\"y\":0.5319,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9053,\"y\":0.5186,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9038,\"y\":0.5064,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9021,\"y\":0.4936,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9003,\"y\":0.4815,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8979,\"y\":0.4678,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8955,\"y\":0.4558,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8925,\"y\":0.4425,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8895,\"y\":0.4307,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8859,\"y\":0.4177,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8824,\"y\":0.406,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8782,\"y\":0.3931,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8742,\"y\":0.3816,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8693,\"y\":0.3686,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8647,\"y\":0.3573,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.5112,\"y\":0.4043,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.5129,\"y\":0.6919,\"radius\":0.0439}]", + "MinGroundZ": 140, + "MaxGroundZ": 500, + "MaxSlope": 26, + "TrunkCollisions": true, + "TrunkDiameter": 24 + } + }, + { + "assetStoreId": "", + "name": "Rocks", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "Model": "assets/Rock Large A.glb", + "Size": 46, + "Density": 0.04, + "Seed": 4, + "Coverage": "Painted", + "Edits": "[{\"tool\":\"add\",\"x\":0.4542,\"y\":0.4348,\"radius\":0.04},{\"tool\":\"add\",\"x\":0.6514,\"y\":0.0699,\"radius\":0.053},{\"tool\":\"add\",\"x\":0.8303,\"y\":0.3781,\"radius\":0.0264},{\"tool\":\"add\",\"x\":0.7279,\"y\":0.0699,\"radius\":0.0294},{\"tool\":\"add\",\"x\":0.3566,\"y\":0.4344,\"radius\":0.0529},{\"tool\":\"add\",\"x\":0.1202,\"y\":0.6268,\"radius\":0.0322},{\"tool\":\"add\",\"x\":0.7395,\"y\":0.4053,\"radius\":0.0535},{\"tool\":\"add\",\"x\":0.632,\"y\":0.8627,\"radius\":0.0323},{\"tool\":\"add\",\"x\":0.2545,\"y\":0.105,\"radius\":0.0442},{\"tool\":\"add\",\"x\":0.2661,\"y\":0.1963,\"radius\":0.0424},{\"tool\":\"add\",\"x\":0.3165,\"y\":0.1058,\"radius\":0.0533},{\"tool\":\"add\",\"x\":0.558,\"y\":0.7451,\"radius\":0.0481},{\"tool\":\"erase\",\"x\":0.5,\"y\":0.1887,\"radius\":0.1587},{\"tool\":\"erase\",\"x\":0.2559,\"y\":0.7991,\"radius\":0.061},{\"tool\":\"erase\",\"x\":0.3901,\"y\":0.1362,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.4414,\"y\":0.1362,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.5586,\"y\":0.1362,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.6099,\"y\":0.1362,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.3901,\"y\":0.2412,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.4414,\"y\":0.2412,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.5586,\"y\":0.2412,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.6099,\"y\":0.2412,\"radius\":0.0232},{\"tool\":\"erase\",\"x\":0.9083,\"y\":0.5708,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9082,\"y\":0.5586,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9076,\"y\":0.5441,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9066,\"y\":0.5319,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9053,\"y\":0.5186,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9038,\"y\":0.5064,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9021,\"y\":0.4936,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.9003,\"y\":0.4815,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8979,\"y\":0.4678,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8955,\"y\":0.4558,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8925,\"y\":0.4425,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8895,\"y\":0.4307,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8859,\"y\":0.4177,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8824,\"y\":0.406,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8782,\"y\":0.3931,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8742,\"y\":0.3816,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8693,\"y\":0.3686,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.8647,\"y\":0.3573,\"radius\":0.0104},{\"tool\":\"erase\",\"x\":0.5112,\"y\":0.4043,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.5129,\"y\":0.6919,\"radius\":0.0439}]", + "MaxSlope": 30, + "MinScale": 0.6, + "MaxScale": 1.3, + "TrunkCollisions": true, + "TrunkDiameter": 110 + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Road_3D" + }, + { + "objectName": "PlayerCar" + }, + { + "objectName": "TrafficCone" + }, + { + "folderName": "Mobile Controls", + "children": [ + { + "objectName": "Speed_Joystick" + }, + { + "objectName": "Steer_Joystick" + } + ] + }, + { + "folderName": "World", + "children": [ + { + "objectName": "Terrain" + }, + { + "objectName": "MapLimit" + }, + { + "objectName": "Grass" + } + ] + }, + { + "folderName": "Decoration", + "children": [ + { + "objectName": "House_A" + }, + { + "objectName": "House_B" + }, + { + "objectName": "House_C" + }, + { + "objectName": "House_D" + }, + { + "objectName": "StreetLight" + }, + { + "objectName": "Barrier" + } + ] + }, + { + "folderName": "Nature", + "children": [ + { + "objectName": "Trees" + }, + { + "objectName": "OakTrees" + }, + { + "objectName": "PineTrees" + }, + { + "objectName": "Rocks" + } + ] + } + ] + }, + "events": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 12000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [ + { + "effectType": "Scene3D::HemisphereLight", + "name": "3D Light", + "doubleParameters": { + "elevation": 40, + "intensity": 0.33, + "rotation": 300 + }, + "stringParameters": { + "groundColor": "127;127;127", + "skyColor": "255;255;255", + "top": "Z+" + }, + "booleanParameters": {} + }, + { + "effectType": "Scene3D::DirectionalLight", + "name": "Effect", + "doubleParameters": { + "distanceFromCamera": 1500, + "elevation": 40, + "frustumSize": 2500, + "intensity": 0.75, + "minimumShadowBias": 0.00001, + "rotation": 300, + "shadowDistance": 3000 + }, + "stringParameters": { + "color": "255;255;255", + "shadowQuality": "medium", + "shadowRenderingMode": "fittedToCameraView", + "top": "Z+" + }, + "booleanParameters": { + "isCastingShadow": true + } + }, + { + "effectType": "Scene3D::Skybox", + "name": "SkyBox", + "doubleParameters": {}, + "stringParameters": { + "backFaceResourceName": "assets/Skybox_Back.png", + "bottomFaceResourceName": "assets/Skybox_Bottom.png", + "frontFaceResourceName": "assets/Skybox_Front.png", + "leftFaceResourceName": "assets/Skybox_Left.png", + "rightFaceResourceName": "assets/Skybox_Right.png", + "top": "Z+", + "topFaceResourceName": "assets/Skybox_Top.png" + }, + "booleanParameters": {} + } + ] + }, + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 12000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "MobileControls", + "renderingType": "", + "visibility": true, + "cameras": [], + "effects": [] + } + ], + "behaviorsSharedData": [ + { + "name": "Animation", + "type": "AnimatableCapability::AnimatableBehavior" + }, + { + "name": "Effect", + "type": "EffectCapability::EffectBehavior" + }, + { + "name": "Flippable", + "type": "FlippableCapability::FlippableBehavior" + }, + { + "name": "Object3D", + "type": "Scene3D::Base3DBehavior" + }, + { + "name": "Opacity", + "type": "OpacityCapability::OpacityBehavior" + }, + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "gravityX": 0, + "gravityY": 0, + "gravityZ": -9.8, + "worldScale": 20 + }, + { + "name": "PhysicsCar3D", + "type": "Physics3D::PhysicsCar3D" + }, + { + "name": "PhysicsCar3DKeyboardMapper", + "type": "PhysicsCar3DKeyMapper::PhysicsCar3DKeyboardMapper" + }, + { + "name": "PhysicsCar3DMultitouchMapper", + "type": "SpriteMultitouchJoystick::PhysicsCar3DMultitouchMapper" + }, + { + "name": "Resizable", + "type": "ResizableCapability::ResizableBehavior" + }, + { + "name": "Scale", + "type": "ScalableCapability::ScalableBehavior" + }, + { + "name": "ThirdPersonCamera", + "type": "ThirdPersonCamera::ThirdPersonCamera" + } + ] + } + ], + "externalEvents": [], + "tests": [ + { + "description": "Holding the accelerator revs the engine and drives the car forward along its heading; it stays put when nothing is pressed.", + "name": "Accelerating drives the car forward", + "type": "gameplay", + "source": [ + "// The core of the game: the accelerator drives the car forward along its", + "// heading (\"Up\", per PhysicsCar3DKeyboardMapper).", + "await harness.goToScene('Game Scene');", + "// Faster in the software renderer used to run tests (grass is thousands of blades, trees are drawn in batches it is slow with).", + "harness.setGameResolutionSize(160, 90);", + "harness.getCurrentRuntimeScene().getLayer('').setEffectBooleanParameter('Effect', 'isCastingShadow', false);", + "for (const grass of harness.getCurrentRuntimeScene().getObjects('Grass')) grass.hide(true);", + "for (const object of harness.getCurrentRuntimeScene().getAdhocListOfAllInstances()) if (object.type === 'Terrain3D::ScatteredModels') object.hide(true);", + "harness.setTimeoutMs(60000);", + "harness.watch('PlayerCar');", + "", + "const getCar = () => harness.getObjects('PlayerCar')[0];", + "", + "// Let the car settle on the road.", + "await harness.stepFrames(8);", + "const start = getCar();", + "harness.assert(", + " start.behaviors.PhysicsCar3D.state.IsOnFloor === true,", + " 'The car rests on the road.'", + ");", + "const headingRadians = (start.angle * Math.PI) / 180;", + "", + "// Without any input the car does not drive away by itself.", + "await harness.stepFrames(8);", + "const idle = getCar();", + "const idleDistance = Math.hypot(idle.centerX - start.centerX, idle.centerY - start.centerY);", + "harness.assert(", + " idleDistance < 10,", + " 'The car stays put while no key is pressed (drifted ' + idleDistance.toFixed(1) + 'px).'", + ");", + "", + "// Accelerate.", + "harness.setKeyPressed('Up', true);", + "let maxEngineSpeed = 0;", + "await harness.stepFrames(32, {", + " onFrame: () => {", + " maxEngineSpeed = Math.max(", + " maxEngineSpeed,", + " getCar().behaviors.PhysicsCar3D.state.EngineSpeed", + " );", + " },", + "});", + "harness.releaseAllInputs();", + "const after = getCar();", + "", + "const travelX = after.centerX - idle.centerX;", + "const travelY = after.centerY - idle.centerY;", + "const travelled = Math.hypot(travelX, travelY);", + "const forwardDistance =", + " travelX * Math.cos(headingRadians) + travelY * Math.sin(headingRadians);", + "console.log(", + " 'travelled=' + Math.round(travelled) +", + " ' forward=' + Math.round(forwardDistance) +", + " ' maxEngineSpeed=' + Math.round(maxEngineSpeed) +", + " ' gear=' + after.behaviors.PhysicsCar3D.state.CurrentGear +", + " ' angleDelta=' + Math.round(after.angle - start.angle)", + ");", + "", + "harness.assert(", + " maxEngineSpeed > start.behaviors.PhysicsCar3D.state.EngineSpeed,", + " 'The engine revs up while accelerating (reached ' + Math.round(maxEngineSpeed) + ').'", + ");", + "harness.assert(", + " forwardDistance > 30,", + " 'Holding the accelerator drives the car forward (drove ' +", + " Math.round(forwardDistance) + 'px along its heading).'", + ");", + "harness.assert(", + " forwardDistance > 0.9 * travelled,", + " 'The car drives along its heading rather than sideways.'", + ");", + "harness.assert(", + " Math.abs(after.angle - start.angle) < 15,", + " 'The car keeps going straight while no steering key is pressed (turned by ' +", + " Math.round(after.angle - start.angle) + ' degrees).'", + ");" + ] + }, + { + "description": "The car is driven into a traffic cone standing on the road: the cone is knocked out of the way.", + "name": "Running a traffic cone over knocks it away", + "type": "gameplay", + "source": [ + "// Driving into a traffic cone must send it flying: the cones are dynamic", + "// physics bodies the car collides with.", + "await harness.goToScene('Game Scene');", + "// Faster in the software renderer used to run tests (grass is thousands of blades, trees are drawn in batches it is slow with).", + "harness.setGameResolutionSize(160, 90);", + "harness.getCurrentRuntimeScene().getLayer('').setEffectBooleanParameter('Effect', 'isCastingShadow', false);", + "for (const grass of harness.getCurrentRuntimeScene().getObjects('Grass')) grass.hide(true);", + "for (const object of harness.getCurrentRuntimeScene().getAdhocListOfAllInstances()) if (object.type === 'Terrain3D::ScatteredModels') object.hide(true);", + "harness.setTimeoutMs(60000);", + "harness.watch('TrafficCone');", + "", + "const getCar = () => harness.getObjects('PlayerCar')[0];", + "await harness.stepFrames(6);", + "const car = getCar();", + "", + "// The nearest cone standing on the road ahead of the car.", + "const conesAhead = harness", + " .getObjects('TrafficCone')", + " .filter(", + " cone =>", + " cone.centerX > car.centerX && Math.abs(cone.centerZ - car.centerZ) < 60", + " )", + " .sort((a, b) => a.centerX - b.centerX);", + "harness.assert(", + " conesAhead.length > 0,", + " 'There are traffic cones standing on the road ahead.'", + ");", + "const targetCone = conesAhead[0];", + "", + "// Arrange: line the car up a short run-up away from that cone. This scene is", + "// the slowest of all the starters to render (about a third of a second of", + "// wall clock per stepped frame), so the run-up is kept as short as the car", + "// needs to get going. Knocking the cone over is still up to the game.", + "const runUp = 90;", + "harness.setObjectPosition(", + " car.id,", + " car.x + (targetCone.centerX - runUp - car.centerX),", + " car.y + (targetCone.centerY - car.centerY),", + " car.z", + ");", + "await harness.stepFrames(6);", + "", + "const coneBefore = harness", + " .getObjects('TrafficCone')", + " .find(cone => cone.id === targetCone.id);", + "harness.assert(!!coneBefore, 'The cone to hit is still standing on the road.');", + "", + "/** How far the cone has been pushed from where it was standing. */", + "const coneDisplacement = () => {", + " const cone = harness", + " .getObjects('TrafficCone')", + " .find(one => one.id === targetCone.id);", + " if (!cone) return null;", + " return Math.hypot(", + " cone.centerX - coneBefore.centerX,", + " cone.centerY - coneBefore.centerY,", + " (cone.centerZ || 0) - (coneBefore.centerZ || 0)", + " );", + "};", + "", + "// Accelerate into it, and stop as soon as it has been knocked away.", + "harness.setKeyPressed('Up', true);", + "const knockedAway = await harness.stepUntil(() => coneDisplacement() > 25, {", + " maxFrames: 45,", + "});", + "harness.releaseAllInputs();", + "await harness.stepFrames(3);", + "", + "const coneAfter = harness", + " .getObjects('TrafficCone')", + " .find(cone => cone.id === targetCone.id);", + "harness.assert(!!coneAfter, 'The cone is still in the scene after the impact.');", + "console.log(", + " 'displacement=' + Math.round(coneDisplacement()) +", + " ' carX=' + Math.round(getCar().centerX) +", + " ' coneX=' + Math.round(coneBefore.centerX)", + ");", + "harness.assert(", + " getCar().centerX > coneBefore.centerX - runUp + 20,", + " 'The car drove toward the cone (it reached x=' +", + " Math.round(getCar().centerX) + ').'", + ");", + "harness.assert(", + " knockedAway,", + " 'Running the cone over knocks it out of the way (it moved ' +", + " Math.round(coneDisplacement()) + 'px).'", + ");" + ] + } + ], + "eventsFunctionsExtensions": [ + { + "author": "", + "category": "Camera", + "dimension": "3D", + "extensionNamespace": "", + "fullName": "Third person camera", + "gdevelopVersion": ">=5.5.269", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSI3LDEwIDEsMTMgNywxNiAxMywxMyAiLz4NCjxwb2x5bGluZSBjbGFzcz0ic3QwIiBwb2ludHM9IjEsMTMgMSwyMCA3LDIzIDEzLDIwIDEzLDEzICIvPg0KPGxpbmUgY2xhc3M9InN0MCIgeDE9IjciIHkxPSIxNiIgeDI9IjciIHkyPSIyMyIvPg0KPGxpbmUgY2xhc3M9InN0MCIgeDE9IjMxIiB5MT0iMTYiIHgyPSIyMSIgeTI9IjgiLz4NCjxsaW5lIGNsYXNzPSJzdDAiIHgxPSIyMSIgeTE9IjI0IiB4Mj0iMzEiIHkyPSIxNiIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTIyLjcsMjIuNkMyMCwyMS43LDE4LDE5LjEsMTgsMTZjMC0zLjEsMi01LjcsNC43LTYuNiIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTE5LjgsMTEuM2MxLjQsMS4xLDIuMiwyLjgsMi4yLDQuN2MwLDEuOS0wLjksMy42LTIuMiw0LjciLz4NCjwvc3ZnPg0K", + "name": "ThirdPersonCamera", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/Line Hero Pack/Master/SVG/Virtual Reality/94e95d2c318e1f3dc7151a351024e13c574e1e44669c6696aa107d60230073f6_Virtual Reality_3d_vision_eye_vr.svg", + "shortDescription": "Third-person camera orbiting an object at configurable distance, elevation, and rotation.", + "version": "1.9.0", + "description": [ + "Move the camera to look at an object from a given distance with a rotation and an elevation angles.", + "", + "It can be useful for:", + "- 3D platformer games ([open the project online](https://editor.gdevelop.io/?project=example://3d-platformer))", + "- 3D racing games ([open the project online](https://editor.gdevelop.io/?project=example://3d-car-coin-hunt))", + "- Orbit camera controls ([open the project online](https://editor.gdevelop.io/?project=example://3d-tile-based-city-builder))", + "- Isometric-like point of view ([open the project online](https://editor.gdevelop.io/?project=example://3d-road-crosser))" + ], + "origin": { + "identifier": "ThirdPersonCamera", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d", + "camera" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "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": [ + "//@ts-ignore", + "if (gdjs.__thirdPersonCameraExtension) {", + " //@ts-ignore", + " return;", + "}", + "", + "/**", + " * It can be built the follow:", + " * ", + " * `objectDefaultRotation.setFromAxisAngle(new THREE.Vector3(0, 0, 1), -Math.PI/2);`", + " * ", + " * `objectDefaultRotation.multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), Math.PI/2));`", + " * ", + " */", + "const objectDefaultRotation = new THREE.Quaternion(0.5, -0.5, -0.5, 0.5);", + "const targetQuaternion = new THREE.Quaternion();", + "const yAxis = new THREE.Vector3(0, 1, 0);", + "const zAxis = new THREE.Vector3(0, 0, 1);", + "const quaternion = new THREE.Quaternion();", + "const objectRotation = new THREE.Euler();", + "", + "/**", + " * @param object {gdjs.RuntimeObject}", + " * @param ratio {number}", + " * @param elevationAngleOffset {number}", + " */", + "function slerpCamera(object, ratio, rotationAngleOffset, elevationAngleOffset) {", + " const layer = object.getInstanceContainer().getLayer(object.getLayer());", + " const threeCamera = layer.getRenderer().getThreeCamera();", + " const threeObject = object.get3DRendererObject();", + "", + " objectRotation.copy(threeObject.rotation);", + " objectRotation.z = -objectRotation.z;", + " objectRotation.x = -objectRotation.x;", + " targetQuaternion.setFromEuler(objectRotation);", + " targetQuaternion.multiply(quaternion.setFromAxisAngle(zAxis, -gdjs.toRad(rotationAngleOffset)));", + " targetQuaternion.multiply(quaternion.setFromAxisAngle(yAxis, gdjs.toRad(elevationAngleOffset)));", + " targetQuaternion.multiply(objectDefaultRotation);", + "", + " threeCamera.quaternion.slerp(targetQuaternion, ratio);", + "", + " // The layer angle takes over the 3D camera Z rotation.", + " layer.setCameraRotation(gdjs.toDegrees(-threeCamera.rotation.z));", + "}", + "", + "//@ts-ignore", + "gdjs.__thirdPersonCameraExtension = {", + " slerpCamera,", + "};", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Move the camera to look at a position from a distance.", + "fullName": "Look at a position from a distance (deprecated)", + "functionType": "Action", + "name": "LookFromDistanceAtPosition", + "private": true, + "sentence": "Move the camera of _PARAM6_ to look at _PARAM1_; _PARAM2_ from _PARAM3_ pixels with a rotation of _PARAM4_° and an elevation of _PARAM5_°", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "=", + "PositionX + Distance * cos(ToRad(RotationAngle + 90)) * cos(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "=", + "PositionY + Distance * sin(ToRad(RotationAngle + 90)) * cos(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "Distance * sin(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::TurnCameraTowardPosition" + }, + "parameters": [ + "", + "PositionX", + "PositionY", + "0", + "Layer", + "", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Position on X axis", + "name": "PositionX", + "type": "expression" + }, + { + "description": "Position on Y axis", + "name": "PositionY", + "type": "expression" + }, + { + "description": "Distance", + "name": "Distance", + "type": "expression" + }, + { + "description": "Rotation angle (around Z axis)", + "name": "RotationAngle", + "type": "expression" + }, + { + "description": "Elevation angle (around Y axis)", + "name": "ElevationAngle", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera to look at an object from a distance.", + "fullName": "Look at an object from a distance (deprecated)", + "functionType": "Action", + "name": "LookFromDistanceAtObject", + "private": true, + "sentence": "Move the camera of _PARAM5_ to look at _PARAM1_ from _PARAM2_ pixels with a rotation of _PARAM3_° and an elevation of _PARAM4_°", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition" + }, + "parameters": [ + "", + "Object.CenterX()", + "Object.CenterY()", + "Distance", + "RotationAngle", + "ElevationAngle", + "Layer", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "Distance", + "name": "Distance", + "type": "expression" + }, + { + "description": "Rotation angle (around Z axis)", + "name": "RotationAngle", + "type": "expression" + }, + { + "description": "Elevation angle (around Y axis)", + "name": "ElevationAngle", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera to look at a position from a distance.", + "fullName": "Look at a position from a distance", + "functionType": "Action", + "name": "LookFromDistanceAtPosition3D", + "sentence": "Move the camera of _PARAM7_ to look at _PARAM1_; _PARAM2_; _PARAM3_ from _PARAM4_ pixels with a rotation of _PARAM5_° and an elevation of _PARAM6_°", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "=", + "PositionX + Distance * cos(ToRad(RotationAngle + 90)) * cos(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "=", + "PositionY + Distance * sin(ToRad(RotationAngle + 90)) * cos(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "PositionZ + Distance * sin(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationX" + }, + "parameters": [ + "", + "=", + "90 - ElevationAngle", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationY" + }, + "parameters": [ + "", + "=", + "0", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraAngle" + }, + "parameters": [ + "", + "=", + "RotationAngle", + "Layer", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Position on X axis", + "name": "PositionX", + "type": "expression" + }, + { + "description": "Position on Y axis", + "name": "PositionY", + "type": "expression" + }, + { + "description": "Position on Z axis", + "name": "PositionZ", + "type": "expression" + }, + { + "description": "Distance", + "name": "Distance", + "type": "expression" + }, + { + "description": "Rotation angle (around Z axis)", + "name": "RotationAngle", + "type": "expression" + }, + { + "description": "Elevation angle (around Y axis)", + "name": "ElevationAngle", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera to look at an object from a distance.", + "fullName": "Look at an object from a distance", + "functionType": "Action", + "group": "Layers and cameras", + "name": "LookFromDistanceAtObject3D", + "sentence": "Move the camera of _PARAM6_ to look at _PARAM1_ from _PARAM3_ pixels with a rotation of _PARAM4_° and an elevation of _PARAM5_°", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition3D" + }, + "parameters": [ + "", + "Object.CenterX()", + "Object.CenterY()", + "Object.Object3D::CenterZ()", + "Distance", + "RotationAngle", + "ElevationAngle", + "Layer", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Distance", + "name": "Distance", + "type": "expression" + }, + { + "description": "Rotation angle (around Z axis)", + "name": "RotationAngle", + "type": "expression" + }, + { + "description": "Elevation angle (around Y axis)", + "name": "ElevationAngle", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Expression", + "name": "RotatedX", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Cos", + "=", + "cos(ToRad(RotationAngle))" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Sin", + "=", + "sin(ToRad(RotationAngle))" + ] + }, + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Cos * X - Sin * Y" + ] + } + ], + "variables": [ + { + "folded": true, + "name": "Cos", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "Sin", + "type": "number", + "value": 0 + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Rotation angle", + "name": "RotationAngle", + "supplementaryInformation": "Tank::Tank", + "type": "expression" + }, + { + "description": "", + "name": "X", + "type": "expression" + }, + { + "description": "", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Expression", + "name": "RotatedY", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Cos", + "=", + "cos(ToRad(RotationAngle))" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Sin", + "=", + "sin(ToRad(RotationAngle))" + ] + }, + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Sin * X + Cos * Y" + ] + } + ], + "variables": [ + { + "folded": true, + "name": "Cos", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "Sin", + "type": "number", + "value": 0 + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Rotation angle", + "name": "RotationAngle", + "supplementaryInformation": "Tank::Tank", + "type": "expression" + }, + { + "description": "", + "name": "X", + "type": "expression" + }, + { + "description": "", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "DefineHelperClasses" + }, + { + "functionName": "LookFromDistanceAtPosition" + }, + { + "functionName": "LookFromDistanceAtObject" + }, + { + "functionName": "LookFromDistanceAtPosition3D" + }, + { + "folderName": "Layers and cameras", + "children": [ + { + "functionName": "LookFromDistanceAtObject3D" + } + ] + }, + { + "functionName": "RotatedX" + }, + { + "functionName": "RotatedY" + } + ] + }, + "eventsBasedBehaviors": [ + { + "description": "Smoothly follow an object at a distance.", + "fullName": "Third person camera", + "helpPath": "", + "iconUrl": "", + "name": "ThirdPersonCamera", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Update private properties through setters to check their values and initialize state." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetRotationHalfwayDuration" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "RotationHalfwayDuration", + "" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetElevationHalfwayDuration" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "ElevationHalfwayDuration", + "" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetTranslationZHalfwayDuration" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "TranslationZHalfwayDuration", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "HasJustBeenCreated", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "HasJustBeenCreated", + "False", + "" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraZ", + "=", + "Object.Object3D::CenterZ()" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::JumpToTargetedRotation" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "IsCalledManually", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::DoMoveCameraCloser" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera closer to the object. This action must be called after the object has moved for the frame.", + "fullName": "Move the camera closer", + "functionType": "Action", + "name": "MoveCameraCloser", + "sentence": "Move the camera closer to _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "The camera following is called with an action, the call from doStepPreEvents must be disabled to avoid to do it twice." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BehaviorActivated" + }, + "parameters": [ + "Object", + "Behavior" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsCalledManually", + "False", + "" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::DoMoveCameraCloser" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera closer to the object.", + "fullName": "Do move the camera closer", + "functionType": "Action", + "name": "DoMoveCameraCloser", + "private": true, + "sentence": "Do move the camera closer _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "At each frame, the camera must catchup the target by a given ratio (speed)\ncameraX(t) - targetX = (cameraX(t - 1) - targetX) * (1 - speed)\n\nThe frame rate must not impact on the catch-up speed, we don't want a speed in ratio per frame but a speed ratio per second, like this:\ncameraX(t) - targetX = (cameraX(t - 1s) - targetX) * (1 - speed)\n\nOk, but we still need to process each frame, we can use a exponent for this:\ncameraX(t) - targetX = (cameraX(t - timeDelta) - targetX) * (1 - speed)^timeDelta\ncameraX(t) = targetX + (cameraX(t - timeDelta) - targetX) * exp(timeDelta * ln((1 - speed)))\n\npow is probably more efficient than precalculated log if the speed is changed continuously but this might be rare enough." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "RotationMode", + "=", + "\"ZYX\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::Slerp" + }, + "parameters": [ + "Object", + "Behavior", + "1 - exp(TimeDelta() * RotationLogSpeed)", + "RotationAngleOffset", + "ElevationAngleOffset", + "" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::MoveToObject" + }, + "parameters": [ + "Object", + "Behavior", + "OffsetY", + "OffsetX", + "OffsetZ", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "-", + "Distance * Scene3D::CameraForwardX(Object.Layer())", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "-", + "Distance * Scene3D::CameraForwardY(Object.Layer())", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "-", + "Distance * Scene3D::CameraForwardZ(Object.Layer())", + "Object.Layer()", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "RotationMode", + "!=", + "\"ZYX\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "False", + "" + ] + } + ] + } + ], + "actions": [], + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Z translation", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CameraZ", + "<", + "Object.Behavior::FreeAreaZMin()" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraZ", + "=", + "Object.Behavior::FreeAreaZMin() + (CameraZ - Object.Behavior::FreeAreaZMin()) * exp(TimeDelta() * TranslationZLogSpeed)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CameraZ", + ">", + "Object.Behavior::FreeAreaZMax()" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraZ", + "=", + "Object.Behavior::FreeAreaZMax() + (CameraZ - Object.Behavior::FreeAreaZMax()) * exp(TimeDelta() * TranslationZLogSpeed)" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Z rotation", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedRotationAngle", + "=", + "Object.Angle()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "RotationMode", + "!=", + "\"Z\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsElevatingWithObject", + "True", + "" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedElevationAngle", + "=", + "Object.Object3D::RotationY()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraRotationAngle", + "=", + "TargetedRotationAngle + 90 + RotationAngleOffset + AngleDifference(CameraAngle(Object.Layer()), TargetedRotationAngle + 90 + RotationAngleOffset) * exp(TimeDelta() * RotationLogSpeed)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraElevationAngle", + "=", + "TargetedElevationAngle + ElevationAngleOffset + AngleDifference(90 - Scene3D::CameraRotationX(Object.Layer()), TargetedElevationAngle + ElevationAngleOffset) * exp(TimeDelta() * ElevationLogSpeed)" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition3D" + }, + "parameters": [ + "", + "Object.CenterX() + ThirdPersonCamera::RotatedX(CameraRotationAngle, OffsetX, -OffsetY)", + "Object.CenterY() + ThirdPersonCamera::RotatedY(CameraRotationAngle, OffsetX, -OffsetY)", + "CameraZ + OffsetZ", + "Distance", + "CameraRotationAngle", + "CameraElevationAngle", + "Object.Layer()", + "" + ] + } + ], + "variables": [ + { + "folded": true, + "name": "CameraRotationAngle", + "type": "number", + "value": 0 + }, + { + "name": "CameraElevationAngle", + "type": "number", + "value": 0 + } + ] + } + ], + "parameters": [] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Lerp camera", + "functionType": "Action", + "name": "Slerp", + "private": true, + "sentence": "Lerp the camera rotation to _PARAM0_ with a ratio of _PARAM2_ with rotation offset _PARAM3_° and elevation offset _PARAM4_°", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.RuntimeObject3D} */", + "const object = objects[0];", + "const ratio = eventsFunctionContext.getArgument(\"Ratio\");", + "const rotationAngleOffset = eventsFunctionContext.getArgument(\"NewRotationAngleOffset\");", + "const elevationAngleOffset = eventsFunctionContext.getArgument(\"NewElevationAngleOffset\");", + "", + "gdjs.__thirdPersonCameraExtension.slerpCamera(object, ratio, rotationAngleOffset, elevationAngleOffset);", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + }, + { + "description": "Ratio", + "name": "Ratio", + "type": "expression" + }, + { + "description": "Rotation angle offset", + "name": "NewRotationAngleOffset", + "type": "expression" + }, + { + "description": "Elevation angle offset", + "name": "NewElevationAngleOffset", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "fullName": "Move to object", + "functionType": "Action", + "name": "MoveToObject", + "private": true, + "sentence": "Change the camera position to _PARAM0_ with offset _PARAM2_, _PARAM3_, _PARAM4_ ", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "=", + "Object.CenterX() + LocalOffsetX * Object.Object3D::ForwardX() + LocalOffsetY * Object.Object3D::RightX() + LocalOffsetZ * Object.Object3D::UpX()", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "=", + "Object.CenterY() + LocalOffsetX * Object.Object3D::ForwardY() + LocalOffsetY * Object.Object3D::RightY() + LocalOffsetZ * Object.Object3D::UpY()", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "Object.Object3D::CenterZ() + LocalOffsetX * Object.Object3D::ForwardZ() + LocalOffsetY * Object.Object3D::RightZ() + LocalOffsetZ * Object.Object3D::UpZ()", + "Object.Layer()", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + }, + { + "description": "X offset", + "name": "LocalOffsetX", + "type": "expression" + }, + { + "description": "Y offset", + "name": "LocalOffsetY", + "type": "expression" + }, + { + "description": "Z offset", + "name": "LocalOffsetZ", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Rotate the camera all the way to the targeted angle.", + "fullName": "Rotate the camera all the way", + "functionType": "Action", + "name": "JumpToTargetedRotation", + "sentence": "Rotate the camera all the way to the targeted angle of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedRotationAngle", + "=", + "Object.Angle()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsElevatingWithObject", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedElevationAngle", + "=", + "Object.Object3D::RotationY()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraAngle", + "=", + "TargetedRotationAngle + 90 + RotationAngleOffset" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition3D" + }, + "parameters": [ + "", + "Object.CenterX() + ThirdPersonCamera::RotatedX(CameraAngle, OffsetX, -OffsetY)", + "Object.CenterY() + ThirdPersonCamera::RotatedY(CameraAngle, OffsetX, -OffsetY)", + "CameraZ + OffsetZ", + "Distance", + "CameraAngle", + "Object.Object3D::RotationY() + ElevationAngleOffset", + "", + "" + ] + } + ], + "variables": [ + { + "folded": true, + "name": "CameraAngle", + "type": "number", + "value": 0 + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the camera rotation.", + "fullName": "Camera rotation", + "functionType": "ExpressionAndCondition", + "name": "RotationAngle", + "private": true, + "sentence": "the camera rotation", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "CameraAngle(Object.Layer())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Rotate the camera all the way to the targeted angle.", + "fullName": "Rotate the camera all the way", + "functionType": "ActionWithOperator", + "getterName": "RotationAngle", + "name": "SetCameraRotation", + "sentence": "Rotate the camera all the way to the targeted angle of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition3D" + }, + "parameters": [ + "", + "Object.CenterX() + ThirdPersonCamera::RotatedX(Value, OffsetX, -OffsetY)", + "Object.CenterY() + ThirdPersonCamera::RotatedY(Value, OffsetX, -OffsetY)", + "CameraZ + OffsetZ", + "Distance", + "Value", + "Object.Object3D::RotationY() + ElevationAngleOffset", + "", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the halfway time for rotation of the object.", + "fullName": "Halfway time for rotation", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "RotationHalfwayDuration", + "sentence": "the halfway time for rotation", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "RotationHalfwayDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "RotationHalfwayDuration", + "group": "Third person camera configuration", + "name": "SetRotationHalfwayDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "ln((1 - speed)) = ln(1 / 2) / halfwatTime" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "RotationHalfwayDuration", + "=", + "Value" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "RotationLogSpeed", + "=", + "log(0.5) / Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the halfway time for elevation rotation of the object.", + "fullName": "Halfway time for elevation rotation", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "ElevationHalfwayDuration", + "sentence": "the halfway time for elevation rotation", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ElevationHalfwayDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ElevationHalfwayDuration", + "group": "Third person camera configuration", + "name": "SetElevationHalfwayDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "ln((1 - speed)) = ln(1 / 2) / halfwatTime" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ElevationHalfwayDuration", + "=", + "Value" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ElevationLogSpeed", + "=", + "log(0.5) / Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the halfway time on Z axis of the object.", + "fullName": "Halfway time on Z axis", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "TranslationZHalfwayDuration", + "sentence": "the halfway time on Z axis", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "TranslationZHalfwayDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "TranslationZHalfwayDuration", + "group": "Third person camera configuration", + "name": "SetTranslationZHalfwayDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "ln((1 - speed)) = ln(1 / 2) / halfwatTime" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TranslationZHalfwayDuration", + "=", + "Value" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TranslationZLogSpeed", + "=", + "log(0.5) / Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Return follow free area bottom border Z.", + "fullName": "Free area Z min", + "functionType": "Expression", + "group": "Private", + "name": "FreeAreaZMin", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Object3D::CenterZ() + OffsetZ - FollowFreeAreaZMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Return follow free area top border Z.", + "fullName": "Free area Z max", + "functionType": "Expression", + "group": "Private", + "name": "FreeAreaZMax", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Object3D::CenterZ() + OffsetZ + FollowFreeAreaZMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the follow free area top border on Z axis of the object.", + "fullName": "Follow free area top border on Z axis", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "FollowFreeAreaZMax", + "sentence": "the follow free area top border on Z axis", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FollowFreeAreaZMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FollowFreeAreaZMax", + "group": "Third person camera configuration", + "name": "SetFollowFreeAreaZMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FollowFreeAreaZMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the follow free area bottom border on Z axis of the object.", + "fullName": "Follow free area bottom border on Z axis", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "FollowFreeAreaZMin", + "sentence": "the follow free area bottom border on Z axis", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FollowFreeAreaZMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FollowFreeAreaZMin", + "group": "Third person camera configuration", + "name": "SetFollowFreeAreaZMin", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FollowFreeAreaZMin", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the camera distance of the object.", + "fullName": "Camera distance", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "Distance", + "sentence": "the camera distance", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Distance" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Distance", + "group": "Third person camera configuration", + "name": "SetDistance", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Distance", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the lateral distance offset of the object.", + "fullName": "Lateral distance offset", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "OffsetX", + "sentence": "the lateral distance offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "OffsetX" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "OffsetX", + "group": "Third person camera configuration", + "name": "SetOffsetX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OffsetX", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the ahead distance offset of the object.", + "fullName": "Ahead distance offset", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "OffsetY", + "sentence": "the ahead distance offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "OffsetY" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "OffsetY", + "group": "Third person camera configuration", + "name": "SetOffsetY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OffsetY", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the z offset of the object.", + "fullName": "Z offset", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "OffsetZ", + "sentence": "the z offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "OffsetZ" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "OffsetZ", + "group": "Third person camera configuration", + "name": "SetOffsetZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OffsetZ", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the rotation angle offset of the object.", + "fullName": "Rotation angle offset", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "RotationAngleOffset", + "sentence": "the rotation angle offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "RotationAngleOffset" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "RotationAngleOffset", + "group": "Third person camera configuration", + "name": "SetRotationAngleOffset", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "RotationAngleOffset", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the elevation angle offset of the object.", + "fullName": "Elevation angle offset", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "ElevationAngleOffset", + "sentence": "the elevation angle offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ElevationAngleOffset" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ElevationAngleOffset", + "group": "Third person camera configuration", + "name": "SetElevationAngleOffset", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ElevationAngleOffset", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the targeted camera rotation angle of the object. When this angle is set, the camera follow this value instead of the object angle.", + "fullName": "Targeted rotation angle", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "TargetedRotationAngle", + "sentence": "the targeted camera rotation angle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "TargetedRotationAngle" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "TargetedRotationAngle", + "group": "Third person camera configuration", + "name": "SetTargetedRotationAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedRotationAngle", + "=", + "Value" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "False", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "MoveCameraCloser" + }, + { + "functionName": "DoMoveCameraCloser" + }, + { + "functionName": "Slerp" + }, + { + "functionName": "MoveToObject" + }, + { + "functionName": "JumpToTargetedRotation" + }, + { + "functionName": "RotationAngle" + }, + { + "functionName": "SetCameraRotation" + }, + { + "folderName": "Third person camera configuration", + "children": [ + { + "functionName": "RotationHalfwayDuration" + }, + { + "functionName": "SetRotationHalfwayDuration" + }, + { + "functionName": "ElevationHalfwayDuration" + }, + { + "functionName": "SetElevationHalfwayDuration" + }, + { + "functionName": "TranslationZHalfwayDuration" + }, + { + "functionName": "SetTranslationZHalfwayDuration" + }, + { + "functionName": "FollowFreeAreaZMax" + }, + { + "functionName": "SetFollowFreeAreaZMax" + }, + { + "functionName": "FollowFreeAreaZMin" + }, + { + "functionName": "SetFollowFreeAreaZMin" + }, + { + "functionName": "Distance" + }, + { + "functionName": "SetDistance" + }, + { + "functionName": "OffsetX" + }, + { + "functionName": "SetOffsetX" + }, + { + "functionName": "OffsetY" + }, + { + "functionName": "SetOffsetY" + }, + { + "functionName": "OffsetZ" + }, + { + "functionName": "SetOffsetZ" + }, + { + "functionName": "RotationAngleOffset" + }, + { + "functionName": "SetRotationAngleOffset" + }, + { + "functionName": "ElevationAngleOffset" + }, + { + "functionName": "SetElevationAngleOffset" + }, + { + "functionName": "TargetedRotationAngle" + }, + { + "functionName": "SetTargetedRotationAngle" + } + ] + }, + { + "folderName": "Private", + "children": [ + { + "functionName": "FreeAreaZMin" + }, + { + "functionName": "FreeAreaZMax" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D capability", + "extraInformation": [ + "Scene3D::Base3DBehavior" + ], + "choices": [], + "name": "Object3D" + }, + { + "value": "0.125", + "type": "Number", + "unit": "Second", + "label": "Halfway time for rotation", + "group": "Catch-up speed", + "quickCustomizationVisibility": "hidden", + "name": "RotationHalfwayDuration" + }, + { + "value": "0", + "type": "Number", + "unit": "Second", + "label": "Halfway time for elevation rotation", + "description": "Only used by Z and ZY rotation modes", + "group": "Catch-up speed", + "quickCustomizationVisibility": "hidden", + "name": "ElevationHalfwayDuration" + }, + { + "value": "0.125", + "type": "Number", + "unit": "Second", + "label": "Halfway time on Z axis", + "description": "Only used by Z and ZY rotation modes", + "group": "Catch-up speed", + "quickCustomizationVisibility": "hidden", + "name": "TranslationZHalfwayDuration" + }, + { + "value": "500", + "type": "Number", + "unit": "Pixel", + "label": "Camera distance", + "group": "Position", + "name": "Distance" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Lateral distance offset", + "group": "Position", + "name": "OffsetX" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Ahead distance offset", + "group": "Position", + "name": "OffsetY" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Z offset", + "group": "Position", + "name": "OffsetZ" + }, + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "Rotation angle offset", + "group": "Position", + "advanced": true, + "name": "RotationAngleOffset" + }, + { + "value": "20", + "type": "Number", + "unit": "DegreeAngle", + "label": "Elevation angle offset", + "group": "Position", + "name": "ElevationAngleOffset" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Follow free area top border on Z axis", + "group": "Position", + "advanced": true, + "quickCustomizationVisibility": "hidden", + "name": "FollowFreeAreaZMax" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Follow free area bottom border on Z axis", + "group": "Position", + "advanced": true, + "quickCustomizationVisibility": "hidden", + "name": "FollowFreeAreaZMin" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "RotationLogSpeed" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "ElevationLogSpeed" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "TranslationZLogSpeed" + }, + { + "value": "", + "type": "Boolean", + "label": "", + "hidden": true, + "name": "IsCalledManually" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "", + "hidden": true, + "name": "CameraZ" + }, + { + "value": "true", + "type": "Boolean", + "label": "", + "hidden": true, + "name": "HasJustBeenCreated" + }, + { + "value": "true", + "type": "Boolean", + "label": "Automatically rotate the camera with the object", + "quickCustomizationVisibility": "hidden", + "name": "IsRotatingWithObject" + }, + { + "value": "false", + "type": "Boolean", + "label": "Automatically rotate the camera with the object (elevation)", + "description": "Best left unchecked, use the rotation mode instead", + "deprecated": true, + "quickCustomizationVisibility": "hidden", + "name": "IsElevatingWithObject" + }, + { + "value": "Z", + "type": "Choice", + "label": "Rotation mode", + "choices": [ + { + "label": "Horizontal rotation (good for characters)", + "value": "Z" + }, + { + "label": "Vertical and horizontal rotations (good for cars)", + "value": "ZY" + }, + { + "label": "All rotations (good for spaceships)", + "value": "ZYX" + } + ], + "name": "RotationMode" + }, + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "Targeted camera rotation angle", + "hidden": true, + "name": "TargetedRotationAngle" + }, + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "Targeted camera rotation angle", + "hidden": true, + "name": "TargetedElevationAngle" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Forward X", + "hidden": true, + "name": "ForwardX" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Forward Y", + "hidden": true, + "name": "ForwardY" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Forward Z", + "hidden": true, + "name": "ForwardZ" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Object3D" + }, + { + "folderName": "Catch-up speed", + "children": [ + { + "propertyName": "RotationHalfwayDuration" + }, + { + "propertyName": "ElevationHalfwayDuration" + }, + { + "propertyName": "TranslationZHalfwayDuration" + } + ] + }, + { + "folderName": "Position", + "children": [ + { + "propertyName": "Distance" + }, + { + "propertyName": "OffsetX" + }, + { + "propertyName": "OffsetY" + }, + { + "propertyName": "OffsetZ" + }, + { + "propertyName": "RotationAngleOffset" + }, + { + "propertyName": "ElevationAngleOffset" + }, + { + "propertyName": "FollowFreeAreaZMax" + }, + { + "propertyName": "FollowFreeAreaZMin" + } + ] + }, + { + "propertyName": "RotationLogSpeed" + }, + { + "propertyName": "ElevationLogSpeed" + }, + { + "propertyName": "TranslationZLogSpeed" + }, + { + "propertyName": "IsCalledManually" + }, + { + "propertyName": "CameraZ" + }, + { + "propertyName": "HasJustBeenCreated" + }, + { + "propertyName": "IsRotatingWithObject" + }, + { + "propertyName": "IsElevatingWithObject" + }, + { + "propertyName": "RotationMode" + }, + { + "propertyName": "TargetedRotationAngle" + }, + { + "propertyName": "TargetedElevationAngle" + }, + { + "propertyName": "ForwardX" + }, + { + "propertyName": "ForwardY" + }, + { + "propertyName": "ForwardZ" + } + ] + } + } + ], + "eventsBasedObjects": [] + }, + { + "author": "", + "category": "Input", + "dimension": "", + "extensionNamespace": "", + "fullName": "Multitouch joystick and buttons (sprite)", + "gdevelopVersion": ">=5.5.222", + "helpPath": "/objects/multitouch-joystick", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPGNpcmNsZSBjbGFzcz0ic3QwIiBjeD0iMTYiIGN5PSIxNiIgcj0iMTMiLz4NCjxwb2x5bGluZSBjbGFzcz0ic3QwIiBwb2ludHM9IjI4LjQsMTIgMjAsMTIgMjAsMy42ICIvPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMjAsMjguNCAyMCwyMCAyOC40LDIwICIvPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMy42LDIwIDEyLDIwIDEyLDI4LjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSIxMiwzLjYgMTIsMTIgMy42LDEyICIvPg0KPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSIxNiw2IDE2LjcsNyAxNS4zLDcgIi8+DQo8cG9seWdvbiBjbGFzcz0ic3QwIiBwb2ludHM9IjE2LDI2IDE1LjMsMjUgMTYuNywyNSAiLz4NCjxwb2x5Z29uIGNsYXNzPSJzdDAiIHBvaW50cz0iNiwxNiA3LDE1LjMgNywxNi43ICIvPg0KPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSIyNiwxNiAyNSwxNi43IDI1LDE1LjMgIi8+DQo8L3N2Zz4NCg==", + "name": "SpriteMultitouchJoystick", + "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Videogames/Videogames_controller_joystick_arrows_direction.svg", + "shortDescription": "On-screen multitouch joystick and buttons.", + "version": "1.9.2", + "description": [ + "Multitouch joysticks are objects showing a joystick on the screen, useful for mobile. They work like a physical gamepad:", + "- 4 or 8 directions", + "- Analogus pads", + "- Player selection", + "- Automatic \"mapper\" behaviors for 2D and 3D movement behaviors (platformer characters, top-down movement, 3D character etc...). Add one of these to the object with the movement behavior (i.e: the player most of the time) and the behavior will then be controlled automatically by the virtual joystick (it works by reading the multitouch joystick state and simulating controls). No need for additional events to make it work once the behaviors are set up.", + "", + "There are ready-to-use joysticks in the asset-store [multitouch joysticks pack](https://editor.gdevelop.io/?initial-dialog=asset-store&asset-pack=multitouch-joysticks-multitouch-joysticks)." + ], + "origin": { + "identifier": "SpriteMultitouchJoystick", + "name": "gdevelop-extension-store" + }, + "tags": [ + "multitouch", + "joystick", + "thumbstick", + "controller", + "touchscreen", + "twin stick", + "shooter", + "virtual", + "platformer", + "platform", + "top-down" + ], + "authorIds": [ + "gqDaZjCfevOOxBYkK6zlhtZnXCg1", + "1OgYzWp5UeVPbiWGJwI6vqfgZLC3", + "v0YRpdAnIucZFgiRCCecqVnGKno2", + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [ + { + "name": "Controllers", + "type": "array", + "children": [ + { + "type": "structure", + "children": [ + { + "name": "Buttons", + "type": "array", + "children": [ + { + "type": "structure", + "children": [ + { + "name": "State", + "type": "string", + "value": "Idle" + } + ] + } + ] + }, + { + "name": "Joystick", + "type": "structure", + "children": [] + } + ] + } + ] + } + ], + "eventsFunctions": [ + { + "description": "Check if the events are running for the editor.", + "fullName": "Editor is running", + "functionType": "Condition", + "name": "IsInGameEdition", + "private": true, + "sentence": "Events are running for the editor", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const game = runtimeScene.getGame();\r", + "eventsFunctionContext.returnValue = game.isInGameEdition && game.isInGameEdition();" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "fullName": "Accelerated speed", + "functionType": "Expression", + "name": "AcceleratedSpeed", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "CurrentSpeed" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TargetedSpeed", + "<", + "0" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Reduce the speed to match the stick force." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + "<", + "TargetedSpeed" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "min(TargetedSpeed, CurrentSpeed + Acceleration * TimeDelta())" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + ">", + "TargetedSpeed" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + "<", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "-", + "Acceleration * TimeDelta()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Turn back at least as fast as it would stop." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + ">=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "max(TargetedSpeed, CurrentSpeed - max(Acceleration , Deceleration) * TimeDelta())" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TargetedSpeed", + ">", + "0" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Reduce the speed to match the stick force." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + ">", + "TargetedSpeed" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "max(TargetedSpeed, CurrentSpeed - Acceleration * TimeDelta())" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + "<", + "TargetedSpeed" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "+", + "Acceleration * TimeDelta()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Turn back at least as fast as it would stop." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + "<=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "min(TargetedSpeed, CurrentSpeed + max(Acceleration , Deceleration) * TimeDelta())" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TargetedSpeed", + "=", + "0" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + "<", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "min(0, CurrentSpeed + Acceleration * TimeDelta())" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "max(0, CurrentSpeed - Acceleration * TimeDelta())" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "clamp(AcceleratedSpeed, -SpeedMax, SpeedMax)" + ] + } + ] + } + ], + "variables": [ + { + "name": "AcceleratedSpeed", + "type": "number", + "value": 0 + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Current speed", + "name": "CurrentSpeed", + "type": "expression" + }, + { + "description": "Targeted speed", + "name": "TargetedSpeed", + "type": "expression" + }, + { + "description": "Max speed", + "name": "SpeedMax", + "type": "expression" + }, + { + "description": "Acceleration", + "name": "Acceleration", + "type": "expression" + }, + { + "description": "Deceleration", + "name": "Deceleration", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a button was just pressed on a multitouch controller.", + "fullName": "Multitouch controller button just pressed", + "functionType": "Condition", + "name": "IsButtonJustPressed", + "sentence": "Button _PARAM2_ of multitouch controller _PARAM1_ was just pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Buttons[Button].State", + "=", + "\"JustPressed\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Button name", + "name": "Button", + "supplementaryInformation": "[\"A\",\"CROSS\",\"B\",\"CIRCLE\",\"X\",\"SQUARE\",\"Y\",\"TRIANGLE\",\"LB\",\"L1\",\"RB\",\"R1\",\"LT\",\"L2\",\"RT\",\"R2\",\"UP\",\"DOWN\",\"LEFT\",\"RIGHT\",\"BACK\",\"SHARE\",\"START\",\"OPTIONS\",\"CLICK_STICK_LEFT\",\"CLICK_STICK_RIGHT\",\"PS_BUTTON\",\"CLICK_TOUCHPAD\"]", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a button is pressed on a multitouch controller.", + "fullName": "Multitouch controller button pressed", + "functionType": "Condition", + "name": "IsButtonPressed", + "sentence": "Button _PARAM2_ of multitouch controller _PARAM1_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Buttons[Button].State", + "=", + "\"Pressed\"" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Buttons[Button].State", + "=", + "\"JustPressed\"" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Button name", + "name": "Button", + "supplementaryInformation": "[\"A\",\"CROSS\",\"B\",\"CIRCLE\",\"X\",\"SQUARE\",\"Y\",\"TRIANGLE\",\"LB\",\"L1\",\"RB\",\"R1\",\"LT\",\"L2\",\"RT\",\"R2\",\"UP\",\"DOWN\",\"LEFT\",\"RIGHT\",\"BACK\",\"SHARE\",\"START\",\"OPTIONS\",\"CLICK_STICK_LEFT\",\"CLICK_STICK_RIGHT\",\"PS_BUTTON\",\"CLICK_TOUCHPAD\"]", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a button is released on a multitouch controller.", + "fullName": "Multitouch controller button released", + "functionType": "Condition", + "name": "IsButtonReleased", + "sentence": "Button _PARAM2_ of multitouch controller _PARAM1_ is released", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Buttons[Button].State", + "=", + "\"Released\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Button name", + "name": "Button", + "supplementaryInformation": "[\"A\",\"CROSS\",\"B\",\"CIRCLE\",\"X\",\"SQUARE\",\"Y\",\"TRIANGLE\",\"LB\",\"L1\",\"RB\",\"R1\",\"LT\",\"L2\",\"RT\",\"R2\",\"UP\",\"DOWN\",\"LEFT\",\"RIGHT\",\"BACK\",\"SHARE\",\"START\",\"OPTIONS\",\"CLICK_STICK_LEFT\",\"CLICK_STICK_RIGHT\",\"PS_BUTTON\",\"CLICK_TOUCHPAD\"]", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Change a button state for a multitouch controller.", + "fullName": "Button state", + "functionType": "Action", + "name": "SetButtonState", + "private": true, + "sentence": "Mark _PARAM2_ button as _PARAM3_ for multitouch controller _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Buttons[Button].State", + "=", + "ButtonState" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Button name", + "name": "Button", + "type": "string" + }, + { + "description": "Button state", + "name": "ButtonState", + "supplementaryInformation": "[\"Idle\",\"Pressed\",\"JustPressed\",\"Released\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Change the dead zone radius of a joystick. The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved).", + "fullName": "Dead zone radius", + "functionType": "Action", + "name": "SetDeadZone", + "private": true, + "sentence": "Change the dead zone of multitouch joystick _PARAM2_ of multitouch controller _PARAM1_ to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].DeadZone", + "=", + "DeadZoneRadius" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "type": "string" + }, + { + "description": "Dead zone radius", + "name": "DeadZoneRadius", + "supplementaryInformation": "[]", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the dead zone radius of a joystick. The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved).", + "fullName": "Dead zone radius", + "functionType": "Expression", + "name": "DeadZone", + "private": true, + "sentence": "Change multitouch joystick _PARAM2_ of multitouch controller _PARAM1_ dead zone to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].DeadZone" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "the direction index (right = 0, bottom = 1, left = 2, top = 3) for an angle (in degrees).", + "fullName": "Angle to 4-way index", + "functionType": "ExpressionAndCondition", + "name": "AngleTo4Way", + "private": true, + "sentence": "The angle _PARAM1_ 4-way index", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "mod(round(Angle * 4 / 360), 4)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Angle", + "name": "Angle", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the direction index (right = 0, bottom-right = 1... top-right = 7) for an angle (in degrees).", + "fullName": "Angle to 8-way index", + "functionType": "ExpressionAndCondition", + "name": "AngleTo8Way", + "private": true, + "sentence": "The angle _PARAM1_ 8-way index", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "mod(round(Angle * 8 / 360), 8)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Angle", + "name": "Angle", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Check if angle is in a given direction.", + "fullName": "Angle 4-way direction", + "functionType": "Condition", + "name": "IsAngleIn4WayDirection", + "private": true, + "sentence": "The angle _PARAM1_ is the 4-way direction _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Right\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo4Way" + }, + "parameters": [ + "", + "=", + "0", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Down\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo4Way" + }, + "parameters": [ + "", + "=", + "1", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Left\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo4Way" + }, + "parameters": [ + "", + "=", + "2", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Up\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo4Way" + }, + "parameters": [ + "", + "=", + "3", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Angle", + "name": "Angle", + "type": "expression" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if angle is in a given direction.", + "fullName": "Angle 8-way direction", + "functionType": "Condition", + "name": "IsAngleIn8WayDirection", + "private": true, + "sentence": "The angle _PARAM1_ is the 8-way direction _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Right\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "0", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"DownRight\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "1", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Down\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "2", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"DownLeft\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "3", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Left\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "4", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"UpLeft\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "5", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Up\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "6", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"UpRight\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "7", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Angle", + "name": "Angle", + "type": "expression" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\",\"UpLeft\",\"UpRight\",\"DownLeft\",\"DownRight\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (4-way)", + "functionType": "Condition", + "name": "IsDirectionPushed4Way", + "sentence": "Joystick _PARAM2_ of multitouch controller _PARAM1_ is pushed in direction _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Make sure the joystick has moved from center" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::JoystickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "JoystickIdentifier", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::IsAngleIn4WayDirection" + }, + "parameters": [ + "", + "SpriteMultitouchJoystick::JoystickAngle(ControllerIdentifier, JoystickIdentifier)", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (8-way)", + "functionType": "Condition", + "name": "IsDirectionPushed8Way", + "sentence": "Joystick _PARAM2_ of multitouch controller _PARAM1_ is pushed in direction _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Make sure the joystick has moved from center" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::JoystickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "JoystickIdentifier", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::IsAngleIn8WayDirection" + }, + "parameters": [ + "", + "SpriteMultitouchJoystick::JoystickAngle(ControllerIdentifier, JoystickIdentifier)", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\",\"UpLeft\",\"UpRight\",\"DownLeft\",\"DownRight\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "the percentage the thumb has been pulled away from the joystick center (Range: 0 to 1).", + "fullName": "Joystick force (deprecated)", + "functionType": "ExpressionAndCondition", + "name": "JoystickForce", + "private": true, + "sentence": "Joystick _PARAM2_ of multitouch controller _PARAM1_ force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + }, + { + "description": "", + "name": "Coucou", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the force of multitouch contoller stick (from 0 to 1).", + "fullName": "Stick force", + "functionType": "ExpressionAndCondition", + "name": "StickForce", + "sentence": "multitouch controller _PARAM1_ _PARAM2_ stick force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "max(0, Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].Force - SpriteMultitouchJoystick::DeadZone(ControllerIdentifier, JoystickIdentifier)) / (1 - SpriteMultitouchJoystick::DeadZone(ControllerIdentifier, JoystickIdentifier))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Stick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Change the percentage the thumb has been pulled away from the joystick center (Range: 0 to 1).", + "fullName": "Joystick force", + "functionType": "Action", + "name": "SetJoystickForce", + "private": true, + "sentence": "Change the force of the joystick _PARAM2_ of multitouch controller _PARAM1_ to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].Force", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "type": "string" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle the joystick is pointing towards (Range: -180 to 180).", + "fullName": "Joystick angle (deprecated)", + "functionType": "Expression", + "name": "JoystickAngle", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle the multitouch controller stick is pointing towards (Range: -180 to 180).", + "fullName": "Stick angle", + "functionType": "Expression", + "name": "StickAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].Angle" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Change the angle the joystick is pointing towards (Range: -180 to 180).", + "fullName": "Joystick angle", + "functionType": "Action", + "name": "SetJoystickAngle", + "private": true, + "sentence": "Change the angle of the joystick _PARAM2_ of multitouch controller _PARAM1_ to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].Angle", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "type": "string" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the multitouch contoller stick force on X axis (from -1 at the left to 1 at the right).", + "fullName": "Stick X force", + "functionType": "Expression", + "name": "StickForceX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "XFromAngleAndDistance(SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier), SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Return the multitouch contoller stick force on Y axis (from -1 at the top to 1 at the bottom).", + "fullName": "Stick Y force", + "functionType": "Expression", + "name": "StickForceY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "YFromAngleAndDistance(SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier), SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a new touch has started on the right or left side of the screen.", + "fullName": "New touch on a screen side", + "functionType": "Condition", + "group": "Multitouch Joystick", + "name": "HasTouchStartedOnScreenSide", + "sentence": "A new touch has started on the _PARAM2_ side of the screen on _PARAM1_'s layer", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Side", + "=", + "\"Left\"" + ] + }, + { + "type": { + "value": "TouchX" + }, + "parameters": [ + "", + "StartedTouchOrMouseId(0)", + "<", + "CameraCenterX(Object.Layer())", + "Object.Layer()", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Side", + "=", + "\"Right\"" + ] + }, + { + "type": { + "value": "TouchX" + }, + "parameters": [ + "", + "StartedTouchOrMouseId(0)", + ">=", + "CameraCenterX(Object.Layer())", + "Object.Layer()", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch joystick", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "objectList" + }, + { + "description": "Screen side", + "name": "Side", + "supplementaryInformation": "[\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "IsInGameEdition" + }, + { + "functionName": "AcceleratedSpeed" + }, + { + "functionName": "IsButtonJustPressed" + }, + { + "functionName": "IsButtonPressed" + }, + { + "functionName": "IsButtonReleased" + }, + { + "functionName": "SetButtonState" + }, + { + "functionName": "SetDeadZone" + }, + { + "functionName": "DeadZone" + }, + { + "functionName": "AngleTo4Way" + }, + { + "functionName": "AngleTo8Way" + }, + { + "functionName": "IsAngleIn4WayDirection" + }, + { + "functionName": "IsAngleIn8WayDirection" + }, + { + "functionName": "IsDirectionPushed4Way" + }, + { + "functionName": "IsDirectionPushed8Way" + }, + { + "functionName": "JoystickForce" + }, + { + "functionName": "StickForce" + }, + { + "functionName": "SetJoystickForce" + }, + { + "functionName": "JoystickAngle" + }, + { + "functionName": "StickAngle" + }, + { + "functionName": "SetJoystickAngle" + }, + { + "functionName": "StickForceX" + }, + { + "functionName": "StickForceY" + }, + { + "folderName": "Multitouch Joystick", + "children": [ + { + "functionName": "HasTouchStartedOnScreenSide" + } + ] + } + ] + }, + "eventsBasedBehaviors": [ + { + "description": "Joystick that can be controlled by interacting with a touchscreen.", + "fullName": "Multitouch Joystick", + "helpPath": "", + "iconUrl": "", + "name": "MultitouchJoystick", + "objectType": "", + "previewIconUrl": "", + "private": true, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::SetDeadZone" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "DeadZoneRadius", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::Reset" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDeActivate", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::Reset" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::IsInGameEdition" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasGameJustResumed" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::Reset" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Manage touches", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchIndex", + "=", + "0" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "StartedTouchOrMouseCount()", + "conditions": [], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "0" + ] + }, + { + "type": { + "value": "CollisionPoint" + }, + "parameters": [ + "Object", + "TouchX(StartedTouchOrMouseId(TouchIndex), Object.Layer(), 0)", + "TouchY(StartedTouchOrMouseId(TouchIndex), Object.Layer(), 0)" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "StartedTouchOrMouseId(TouchIndex)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchIndex", + "+", + "1" + ] + } + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Move thumb back to center when not being pressed (acts like a spring on a real controller)" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasTouchEnded" + }, + "parameters": [ + "", + "TouchId" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::Reset" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Update joystick position", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetJoystickAngle" + }, + "parameters": [ + "Object", + "Behavior", + "AngleBetweenPositions(Object.CenterX(), Object.CenterY(), TouchX(TouchId, Object.Layer(), 0), TouchY(TouchId, Object.Layer(), 0))", + "AngleBetweenPositions(Object.CenterX(), Object.CenterY(), TouchX(TouchId, Object.Layer(), 0), TouchY(TouchId, Object.Layer(), 0))" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetJoystickForce" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "clamp(2 * DistanceBetweenPositions(Object.CenterX(), Object.CenterY(), TouchX(TouchId, Object.Layer(), 0), TouchY(TouchId, Object.Layer(), 0)) / Object.Width(), 0, 1)", + "" + ] + } + ] + } + ], + "parameters": [] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the joystick force (from 0 to 1).", + "fullName": "Joystick force", + "functionType": "ExpressionAndCondition", + "name": "JoystickForce", + "sentence": "the joystick force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "max(0, JoystickForce - DeadZoneRadius) / (1 - DeadZoneRadius)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "JoystickForce", + "name": "SetJoystickForce", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "JoystickForce", + "=", + "Value" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SetJoystickForce" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "JoystickForce", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + }, + { + "description": "", + "name": "Parameter", + "type": "objectList" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle the joystick is pointing towards (Range: -180 to 180).", + "fullName": "Joystick angle", + "functionType": "Expression", + "name": "JoystickAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "JoystickAngle" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Change the angle the joystick is pointing towards (Range: -180 to 180).", + "fullName": "Joystick angle", + "functionType": "Action", + "name": "SetJoystickAngle", + "private": true, + "sentence": "Change the joystick angle of _PARAM0_ to _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "JoystickAngle", + "=", + "Value" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SetJoystickAngle" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "JoystickAngle", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + }, + { + "description": "Angle", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the stick force on X axis (from -1 at the left to 1 at the right).", + "fullName": "Stick X force", + "functionType": "Expression", + "name": "StickForceX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Behavior::JoystickForce() * cos(ToRad(Object.Behavior::JoystickAngle()))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Return the stick force on Y axis (from -1 at the top to 1 at the bottom).", + "fullName": "Stick Y force", + "functionType": "Expression", + "name": "StickForceY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Behavior::JoystickForce() * sin(ToRad(Object.Behavior::JoystickAngle()))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (4-way movement)", + "functionType": "Condition", + "name": "IsDirectionPushed4Way", + "sentence": "_PARAM0_ is pushed in direction _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::JoystickForce" + }, + "parameters": [ + "Object", + "Behavior", + ">", + "0", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::IsAngleIn4WayDirection" + }, + "parameters": [ + "", + "JoystickAngle", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (8-way movement)", + "functionType": "Condition", + "name": "IsDirectionPushed8Way", + "sentence": "_PARAM0_ is pushed in direction _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::JoystickForce" + }, + "parameters": [ + "Object", + "Behavior", + ">", + "0", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::IsAngleIn8WayDirection" + }, + "parameters": [ + "", + "JoystickAngle", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\",\"UpLeft\",\"UpRight\",\"DownLeft\",\"DownRight\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a joystick is pressed.", + "fullName": "Joystick pressed", + "functionType": "Condition", + "name": "IsPressed", + "sentence": "Joystick _PARAM0_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Reset the joystick values (except for angle, which stays the same)", + "fullName": "Reset", + "functionType": "Action", + "name": "Reset", + "private": true, + "sentence": "Reset the joystick of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetJoystickForce" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "0", + "" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "0" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the multitouch controller identifier.", + "fullName": "Multitouch controller identifier", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "ControllerIdentifier", + "sentence": "the multitouch controller identifier", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ControllerIdentifier" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ControllerIdentifier", + "group": "Multitouch Joystick configuration", + "name": "SetControllerIdentifier", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ControllerIdentifier", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the joystick name.", + "fullName": "Joystick name", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "JoystickIdentifier", + "sentence": "the joystick name", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "JoystickIdentifier" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "JoystickIdentifier", + "group": "Multitouch Joystick configuration", + "name": "SetJoystickIdentifier", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "JoystickIdentifier", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the dead zone radius (range: 0 to 1) of the joystick. The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved).", + "fullName": "Dead zone radius", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "DeadZoneRadius", + "sentence": "the dead zone radius", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "DeadZoneRadius" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "DeadZoneRadius", + "group": "Multitouch Joystick configuration", + "name": "SetDeadZoneRadius", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DeadZoneRadius", + "=", + "Value" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SetDeadZone" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "Value", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Force the joystick into the pressing state.", + "fullName": "Force start pressing", + "functionType": "Action", + "name": "ForceStartPressing", + "sentence": "Force start pressing _PARAM0_ with touch identifier: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + }, + { + "description": "Touch identifier", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "onDeActivate" + }, + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "JoystickForce" + }, + { + "functionName": "SetJoystickForce" + }, + { + "functionName": "JoystickAngle" + }, + { + "functionName": "SetJoystickAngle" + }, + { + "functionName": "StickForceX" + }, + { + "functionName": "StickForceY" + }, + { + "functionName": "IsDirectionPushed4Way" + }, + { + "functionName": "IsDirectionPushed8Way" + }, + { + "functionName": "IsPressed" + }, + { + "functionName": "Reset" + }, + { + "folderName": "Multitouch Joystick configuration", + "children": [ + { + "functionName": "ControllerIdentifier" + }, + { + "functionName": "SetControllerIdentifier" + }, + { + "functionName": "JoystickIdentifier" + }, + { + "functionName": "SetJoystickIdentifier" + }, + { + "functionName": "DeadZoneRadius" + }, + { + "functionName": "SetDeadZoneRadius" + } + ] + }, + { + "functionName": "ForceStartPressing" + } + ] + }, + "propertyDescriptors": [ + { + "value": "1", + "type": "Number", + "label": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "String", + "label": "Joystick name", + "name": "JoystickIdentifier" + }, + { + "value": "0.4", + "type": "Number", + "label": "Dead zone radius (range: 0 to 1)", + "description": "The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved)", + "name": "DeadZoneRadius" + }, + { + "value": "0", + "type": "Number", + "label": "Joystick angle (range: -180 to 180)", + "hidden": true, + "name": "JoystickAngle" + }, + { + "value": "0", + "type": "Number", + "label": "Joystick force (range: 0 to 1)", + "hidden": true, + "name": "JoystickForce" + }, + { + "value": "0", + "type": "Number", + "label": "", + "hidden": true, + "name": "TouchId" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "TouchIndex" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ControllerIdentifier" + }, + { + "propertyName": "JoystickIdentifier" + }, + { + "propertyName": "DeadZoneRadius" + }, + { + "propertyName": "JoystickAngle" + }, + { + "propertyName": "JoystickForce" + }, + { + "propertyName": "TouchId" + }, + { + "propertyName": "TouchIndex" + } + ] + } + }, + { + "description": "Detect presses made on a touchscreen on the object so it acts like a button and automatically trigger the button having the same identifier for the mapper behaviors.", + "fullName": "Multitouch button", + "helpPath": "", + "iconUrl": "", + "name": "MultitouchButton", + "objectType": "", + "previewIconUrl": "", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsReleased", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsReleased", + "False", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchButton::SetButtonState" + }, + "parameters": [ + "Object", + "Behavior", + "\"Idle\"", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsJustPressed", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsJustPressed", + "False", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchButton::SetButtonState" + }, + "parameters": [ + "Object", + "Behavior", + "\"Pressed\"", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + }, + { + "type": { + "value": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchIndex", + "=", + "0" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "StartedTouchOrMouseCount()", + "conditions": [], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "CollisionPoint" + }, + "parameters": [ + "Object", + "TouchX(StartedTouchOrMouseId(TouchIndex), Object.Layer())", + "TouchY(StartedTouchOrMouseId(TouchIndex), Object.Layer())" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "Radius", + ">", + "DistanceBetweenPositions(Object.CenterX(), Object.CenterY(), TouchX(StartedTouchOrMouseId(TouchIndex), Object.Layer()), TouchY(StartedTouchOrMouseId(TouchIndex), Object.Layer()))" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "StartedTouchOrMouseId(TouchIndex)" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchButton::SetButtonState" + }, + "parameters": [ + "Object", + "Behavior", + "\"JustPressed\"", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsJustPressed", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchIndex", + "+", + "1" + ] + } + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + }, + { + "type": { + "value": "HasTouchEnded" + }, + "parameters": [ + "", + "TouchId" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchButton::SetButtonState" + }, + "parameters": [ + "Object", + "Behavior", + "\"Released\"", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsReleased", + "True", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsJustPressed", + "False", + "" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "0" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchButton", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button was just pressed.", + "fullName": "Button just pressed", + "functionType": "Condition", + "name": "IsJustPressed", + "sentence": "Button _PARAM0_ was just pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsJustPressed", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchButton", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is pressed.", + "fullName": "Button pressed", + "functionType": "Condition", + "name": "IsPressed", + "sentence": "Button _PARAM0_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchButton", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is released.", + "fullName": "Button released", + "functionType": "Condition", + "name": "IsReleased", + "sentence": "Button _PARAM0_ is released", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsReleased", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchButton", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Button state", + "functionType": "Action", + "name": "SetButtonState", + "private": true, + "sentence": "Mark the button _PARAM0_ as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::SetButtonState" + }, + "parameters": [ + "", + "ControllerIdentifier", + "ButtonIdentifier", + "ButtonState", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchButton", + "type": "behavior" + }, + { + "description": "Button state", + "name": "ButtonState", + "supplementaryInformation": "[\"Idle\",\"JustPressed\",\"Pressed\",\"Released\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "IsJustPressed" + }, + { + "functionName": "IsPressed" + }, + { + "functionName": "IsReleased" + }, + { + "functionName": "SetButtonState" + } + ] + }, + "propertyDescriptors": [ + { + "value": "1", + "type": "Number", + "label": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "A", + "type": "String", + "label": "Button identifier", + "name": "ButtonIdentifier" + }, + { + "value": "0", + "type": "Number", + "label": "TouchID", + "hidden": true, + "name": "TouchId" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "TouchIndex" + }, + { + "value": "", + "type": "Boolean", + "label": "Button released", + "hidden": true, + "name": "IsReleased" + }, + { + "value": "", + "type": "Boolean", + "label": "Button just pressed", + "hidden": true, + "name": "IsJustPressed" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Triggering circle radius", + "description": "This circle adds up to the object collision mask.", + "name": "Radius" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ControllerIdentifier" + }, + { + "propertyName": "ButtonIdentifier" + }, + { + "propertyName": "TouchId" + }, + { + "propertyName": "TouchIndex" + }, + { + "propertyName": "IsReleased" + }, + { + "propertyName": "IsJustPressed" + }, + { + "propertyName": "Radius" + } + ] + } + }, + { + "description": "Control a platformer character with a multitouch controller.", + "fullName": "Platformer multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "PlatformerMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Left\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "Property" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Right\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "Property" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Up\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "Property" + ] + }, + { + "type": { + "value": "PlatformBehavior::SimulateLadderKey" + }, + "parameters": [ + "Object", + "Property" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Down\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "Property" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsButtonPressed" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JumpButton", + "\"Down\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateJumpKey" + }, + "parameters": [ + "Object", + "Property" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::PlatformerMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Platform character behavior", + "extraInformation": [ + "PlatformBehavior::PlatformerObjectBehavior" + ], + "choices": [], + "name": "Property" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Joystick name", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "A", + "type": "String", + "label": "Jump button name", + "group": "Controls", + "name": "JumpButton" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Property" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "JoystickIdentifier" + }, + { + "propertyName": "JumpButton" + } + ] + } + ] + } + }, + { + "description": "Control a 3D physics character with a multitouch controller.", + "fullName": "3D platformer multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "Platformer3DMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::StickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "JoystickIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SetForwardAngle" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D", + "=", + "SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier) + CameraAngle(Object.Layer())" + ] + }, + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateStick" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D", + "-90", + "SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsButtonPressed" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JumpButton", + "\"Down\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateJumpKey" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::Platformer3DMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics character", + "extraInformation": [ + "Physics3D::PhysicsCharacter3D" + ], + "choices": [], + "name": "PhysicsCharacter3D" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Walk joystick", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "A", + "type": "String", + "label": "Jump button name", + "group": "Controls", + "name": "JumpButton" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "PhysicsCharacter3D" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "JoystickIdentifier" + }, + { + "propertyName": "JumpButton" + } + ] + } + ] + } + }, + { + "description": "Control a 3D physics character with a multitouch controller.", + "fullName": "3D shooter multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "Shooter3DMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::StickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "JoystickIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateStick" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D", + "SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier)", + "SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsButtonPressed" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JumpButton", + "\"Down\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateJumpKey" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::Shooter3DMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics character", + "extraInformation": [ + "Physics3D::PhysicsCharacter3D" + ], + "choices": [], + "name": "PhysicsCharacter3D" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Walk joystick", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "A", + "type": "String", + "label": "Jump button name", + "group": "Controls", + "name": "JumpButton" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "PhysicsCharacter3D" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "JoystickIdentifier" + }, + { + "propertyName": "JumpButton" + } + ] + } + ] + } + }, + { + "description": "Control camera rotations with a multitouch controller.", + "fullName": "First person camera multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "FirstPersonMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "TODO It's probably a bad idea to rotate the object around Y." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CurrentRotationSpeedZ", + "=", + "SpriteMultitouchJoystick::AcceleratedSpeed(CurrentRotationSpeedZ, SpriteMultitouchJoystick::StickForceX(ControllerIdentifier, CameraStick) * HorizontalRotationSpeedMax, HorizontalRotationSpeedMax, HorizontalRotationAcceleration, HorizontalRotationDeceleration)" + ] + }, + { + "type": { + "value": "SetAngle" + }, + "parameters": [ + "Object", + "+", + "CurrentRotationSpeedZ * TimeDelta()" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CurrentRotationSpeedY", + "=", + "SpriteMultitouchJoystick::AcceleratedSpeed(CurrentRotationSpeedY, SpriteMultitouchJoystick::StickForceY(ControllerIdentifier, CameraStick) * VerticalRotationSpeedMax, VerticalRotationSpeedMax, VerticalRotationAcceleration, VerticalRotationDeceleration)" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationY" + }, + "parameters": [ + "Object", + "Object3D", + "+", + "CurrentRotationSpeedY * TimeDelta()" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationY" + }, + "parameters": [ + "Object", + "Object3D", + "=", + "clamp(Object.Object3D::RotationY(), VerticalAngleMin, VerticalAngleMax)" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper::LookFromObjectEyes" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera to look though _PARAM1_ eyes. The object must look to the right when all its angles are 0 and the top of its head be toward Z+.", + "fullName": "Look through object eyes", + "functionType": "Action", + "group": "Layers and cameras", + "name": "LookFromObjectEyes", + "private": true, + "sentence": "Move the camera to look though _PARAM0_ eyes", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "CenterCameraOnObject" + }, + "parameters": [ + "", + "Object", + "", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "Object.Object3D::Z() + Object.Object3D::Depth() + OffsetZ", + "", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationX" + }, + "parameters": [ + "", + "=", + "- Object.Object3D::RotationY() + 90", + "GetArgumentAsString(\"Layer\")", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationY" + }, + "parameters": [ + "", + "=", + "Object.Object3D::RotationX()", + "GetArgumentAsString(\"Layer\")", + "" + ] + }, + { + "type": { + "value": "SetCameraAngle" + }, + "parameters": [ + "", + "=", + "Object.Angle() + 90", + "Object.Layer()", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the maximum horizontal rotation speed of the object.", + "fullName": "Maximum horizontal rotation speed", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "HorizontalRotationSpeedMax", + "sentence": "the maximum horizontal rotation speed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "HorizontalRotationSpeedMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "HorizontalRotationSpeedMax", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "SetHorizontalRotationSpeedMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "HorizontalRotationSpeedMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the horizontal rotation acceleration of the object.", + "fullName": "Horizontal rotation acceleration", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "HorizontalRotationAcceleration", + "sentence": "the horizontal rotation acceleration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "HorizontalRotationAcceleration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "HorizontalRotationAcceleration", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "SetHorizontalRotationAcceleration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "HorizontalRotationAcceleration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the horizontal rotation deceleration of the object.", + "fullName": "Horizontal rotation deceleration", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "HorizontalRotationDeceleration", + "sentence": "the horizontal rotation deceleration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "HorizontalRotationDeceleration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "HorizontalRotationDeceleration", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "SetHorizontalRotationDeceleration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "HorizontalRotationDeceleration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the maximum vertical rotation speed of the object.", + "fullName": "Maximum vertical rotation speed", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "VerticalRotationSpeedMax", + "sentence": "the maximum vertical rotation speed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalRotationSpeedMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalRotationSpeedMax", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "SetVerticalRotationSpeedMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalRotationSpeedMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the vertical rotation acceleration of the object.", + "fullName": "Vertical rotation acceleration", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "VerticalRotationAcceleration", + "sentence": "the vertical rotation acceleration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalRotationAcceleration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalRotationAcceleration", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "SetVerticalRotationAcceleration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalRotationAcceleration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the vertical rotation deceleration of the object.", + "fullName": "Vertical rotation deceleration", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "VerticalRotationDeceleration", + "sentence": "the vertical rotation deceleration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalRotationDeceleration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalRotationDeceleration", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "SetVerticalRotationDeceleration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalRotationDeceleration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the minimum vertical camera angle of the object.", + "fullName": "Minimum vertical camera angle", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "VerticalAngleMin", + "sentence": "the minimum vertical camera angle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalAngleMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalAngleMin", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "SetVerticalAngleMin", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalAngleMin", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the maximum vertical camera angle of the object.", + "fullName": "Maximum vertical camera angle", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "VerticalAngleMax", + "sentence": "the maximum vertical camera angle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalAngleMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalAngleMax", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "SetVerticalAngleMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalAngleMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the z position offset of the object.", + "fullName": "Z position offset", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper position configuration", + "name": "OffsetZ", + "sentence": "the z position offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "OffsetZ" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "OffsetZ", + "group": "First person camera multitouch controller mapper position configuration", + "name": "SetOffsetZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OffsetZ", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + }, + { + "folderName": "Layers and cameras", + "children": [ + { + "functionName": "LookFromObjectEyes" + } + ] + }, + { + "folderName": "First person camera multitouch controller mapper horizontal rotation configuration", + "children": [ + { + "functionName": "HorizontalRotationSpeedMax" + }, + { + "functionName": "SetHorizontalRotationSpeedMax" + }, + { + "functionName": "HorizontalRotationAcceleration" + }, + { + "functionName": "SetHorizontalRotationAcceleration" + }, + { + "functionName": "HorizontalRotationDeceleration" + }, + { + "functionName": "SetHorizontalRotationDeceleration" + } + ] + }, + { + "folderName": "First person camera multitouch controller mapper vertical rotation configuration", + "children": [ + { + "functionName": "VerticalRotationSpeedMax" + }, + { + "functionName": "SetVerticalRotationSpeedMax" + }, + { + "functionName": "VerticalRotationAcceleration" + }, + { + "functionName": "SetVerticalRotationAcceleration" + }, + { + "functionName": "VerticalRotationDeceleration" + }, + { + "functionName": "SetVerticalRotationDeceleration" + }, + { + "functionName": "VerticalAngleMin" + }, + { + "functionName": "SetVerticalAngleMin" + }, + { + "functionName": "VerticalAngleMax" + }, + { + "functionName": "SetVerticalAngleMax" + } + ] + }, + { + "folderName": "First person camera multitouch controller mapper position configuration", + "children": [ + { + "functionName": "OffsetZ" + }, + { + "functionName": "SetOffsetZ" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D capability", + "extraInformation": [ + "Scene3D::Base3DBehavior" + ], + "choices": [], + "name": "Object3D" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Secondary", + "type": "Choice", + "label": "Camera joystick", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "CameraStick" + }, + { + "value": "180", + "type": "Number", + "unit": "AngularSpeed", + "label": "Maximum rotation speed", + "group": "Horizontal rotation", + "name": "HorizontalRotationSpeedMax" + }, + { + "value": "360", + "type": "Number", + "label": "Rotation acceleration", + "group": "Horizontal rotation", + "name": "HorizontalRotationAcceleration" + }, + { + "value": "720", + "type": "Number", + "label": "Rotation deceleration", + "group": "Horizontal rotation", + "name": "HorizontalRotationDeceleration" + }, + { + "value": "120", + "type": "Number", + "unit": "AngularSpeed", + "label": "Maximum rotation speed", + "group": "Vertical rotation", + "name": "VerticalRotationSpeedMax" + }, + { + "value": "240", + "type": "Number", + "label": "Rotation acceleration", + "group": "Vertical rotation", + "name": "VerticalRotationAcceleration" + }, + { + "value": "480", + "type": "Number", + "label": "Rotation deceleration", + "group": "Vertical rotation", + "name": "VerticalRotationDeceleration" + }, + { + "value": "-90", + "type": "Number", + "unit": "DegreeAngle", + "label": "Minimum angle", + "group": "Vertical rotation", + "name": "VerticalAngleMin" + }, + { + "value": "90", + "type": "Number", + "unit": "DegreeAngle", + "label": "Maximum angle", + "group": "Vertical rotation", + "name": "VerticalAngleMax" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Z position offset", + "group": "Position", + "name": "OffsetZ" + }, + { + "value": "0", + "type": "Number", + "unit": "AngularSpeed", + "label": "Current rotation speed Z", + "hidden": true, + "name": "CurrentRotationSpeedZ" + }, + { + "value": "0", + "type": "Number", + "unit": "AngularSpeed", + "label": "Current rotation speed Y", + "hidden": true, + "name": "CurrentRotationSpeedY" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Object3D" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "CameraStick" + } + ] + }, + { + "folderName": "Horizontal rotation", + "children": [ + { + "propertyName": "HorizontalRotationSpeedMax" + }, + { + "propertyName": "HorizontalRotationAcceleration" + }, + { + "propertyName": "HorizontalRotationDeceleration" + } + ] + }, + { + "folderName": "Vertical rotation", + "children": [ + { + "propertyName": "VerticalRotationSpeedMax" + }, + { + "propertyName": "VerticalRotationAcceleration" + }, + { + "propertyName": "VerticalRotationDeceleration" + }, + { + "propertyName": "VerticalAngleMin" + }, + { + "propertyName": "VerticalAngleMax" + } + ] + }, + { + "folderName": "Position", + "children": [ + { + "propertyName": "OffsetZ" + } + ] + }, + { + "propertyName": "CurrentRotationSpeedZ" + }, + { + "propertyName": "CurrentRotationSpeedY" + } + ] + } + }, + { + "description": "Control a 3D physics car with a multitouch controller.", + "fullName": "3D car multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "PhysicsCar3DMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::StickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "SteerJoystickIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateSteeringStick" + }, + "parameters": [ + "Object", + "PhysicsCar3D", + "SpriteMultitouchJoystick::StickForceX(ControllerIdentifier, SteerJoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::StickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "SpeedJoystickIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateAcceleratorStick" + }, + "parameters": [ + "Object", + "PhysicsCar3D", + "-SpriteMultitouchJoystick::StickForceY(ControllerIdentifier, SpeedJoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsButtonPressed" + }, + "parameters": [ + "", + "ControllerIdentifier", + "HandBrakeButton", + "\"Down\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateHandBrakeKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::PhysicsCar3DMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics car", + "extraInformation": [ + "Physics3D::PhysicsCar3D" + ], + "choices": [], + "name": "PhysicsCar3D" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Steer joystick", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "SteerJoystickIdentifier" + }, + { + "value": "Secondary", + "type": "Choice", + "label": "Speed joystick", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "SpeedJoystickIdentifier" + }, + { + "value": "B", + "type": "String", + "label": "Hand brake button name", + "group": "Controls", + "name": "HandBrakeButton" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "PhysicsCar3D" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "SteerJoystickIdentifier" + }, + { + "propertyName": "SpeedJoystickIdentifier" + }, + { + "propertyName": "HandBrakeButton" + } + ] + } + ] + } + }, + { + "description": "Control a top-down character with a multitouch controller.", + "fullName": "Top-down multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "TopDownMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "StickMode", + "=", + "\"Analog\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateStick" + }, + "parameters": [ + "Object", + "TopDownMovement", + "SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier)", + "SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "StickMode", + "=", + "\"360°\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateStick" + }, + "parameters": [ + "Object", + "TopDownMovement", + "SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier)", + "sign(SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier))" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "StickMode", + "=", + "\"8 Directions\"" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "TopDownMovementBehavior::DiagonalsAllowed" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Left\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Right\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Up\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Down\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "TopDownMovementBehavior::DiagonalsAllowed" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Left\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Right\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Up\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Down\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"UpLeft\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + }, + { + "type": { + "value": "TopDownMovementBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"UpRight\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + }, + { + "type": { + "value": "TopDownMovementBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"DownLeft\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + }, + { + "type": { + "value": "TopDownMovementBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"DownRight\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + }, + { + "type": { + "value": "TopDownMovementBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::TopDownMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Top-down movement behavior", + "extraInformation": [ + "TopDownMovementBehavior::TopDownMovementBehavior" + ], + "choices": [], + "name": "TopDownMovement" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Joystick name", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "Analog", + "type": "Choice", + "label": "Stick mode", + "group": "Controls", + "choices": [ + { + "label": "Analog", + "value": "Analog" + }, + { + "label": "360°", + "value": "360°" + }, + { + "label": "8 Directions", + "value": "8 Directions" + } + ], + "name": "StickMode" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "TopDownMovement" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "propertyName": "JoystickIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "StickMode" + } + ] + } + ] + } + } + ], + "eventsBasedObjects": [ + { + "areaMaxX": 64, + "areaMaxY": 64, + "areaMaxZ": 64, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "assetStoreTag": "multitouch joysticks", + "defaultName": "Joystick", + "description": "Joystick for touchscreens.", + "fullName": "Multitouch Joystick", + "helpPath": "", + "iconUrl": "", + "isUsingLegacyInstancesRenderer": true, + "name": "SpriteMultitouchJoystick", + "previewIconUrl": "", + "objects": [ + { + "adaptCollisionMaskAutomatically": false, + "assetStoreId": "", + "name": "Thumb", + "type": "Sprite", + "updateIfNotVisible": false, + "variables": [], + "effects": [], + "behaviors": [], + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [] + } + ] + } + ] + }, + { + "adaptCollisionMaskAutomatically": false, + "assetStoreId": "", + "name": "Border", + "type": "Sprite", + "updateIfNotVisible": false, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "MultitouchJoystick", + "type": "SpriteMultitouchJoystick::MultitouchJoystick", + "ControllerIdentifier": 1, + "JoystickIdentifier": "Primary", + "FloatingEnabled": false, + "DeadZoneRadius": 0.4, + "JoystickAngle": 0, + "JoystickForce": 0, + "TouchId": 0, + "TouchIndex": 0 + } + ], + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [] + } + ] + } + ] + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Thumb" + }, + { + "objectName": "Border" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [], + "editionSettings": [], + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Create" + }, + "parameters": [ + "", + "Border", + "0", + "0", + "" + ] + }, + { + "type": { + "value": "Create" + }, + "parameters": [ + "", + "Thumb", + "0", + "0", + "" + ] + }, + { + "type": { + "value": "SetZOrder" + }, + "parameters": [ + "Border", + "=", + "1" + ] + }, + { + "type": { + "value": "SetZOrder" + }, + "parameters": [ + "Thumb", + "=", + "2" + ] + }, + { + "type": { + "value": "SetCenter" + }, + "parameters": [ + "Border", + "=", + "0", + "=", + "0" + ] + }, + { + "type": { + "value": "SetCenter" + }, + "parameters": [ + "Thumb", + "=", + "0", + "=", + "0" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::UpdateConfiguration" + }, + "parameters": [ + "Object", + "" + ] + }, + { + "type": { + "value": "SetIncludedInParentCollisionMask" + }, + "parameters": [ + "Thumb", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::IsInGameEdition" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "PutAroundObject" + }, + "parameters": [ + "Thumb", + "Border", + "Border.MultitouchJoystick::JoystickForce() * Border.Width() / 2", + "Border.MultitouchJoystick::JoystickAngle()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldBeHiddenWhenReleased", + "True", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::IsPressed" + }, + "parameters": [ + "Object", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Hide" + }, + "parameters": [ + "Object" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::ActivateControl" + }, + "parameters": [ + "Object", + "no", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "ShouldBeHiddenWhenReleased", + "False", + "" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::UpdateConfiguration" + }, + "parameters": [ + "Object", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Pass the object property values to the behavior.", + "fullName": "Update configuration", + "functionType": "Action", + "name": "UpdateConfiguration", + "private": true, + "sentence": "Update the configuration of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetControllerIdentifier" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "ControllerIdentifier", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetJoystickIdentifier" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "JoystickIdentifier", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetDeadZoneRadius" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "DeadZoneRadius", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Show the joystick until it is released.", + "fullName": "Show and start pressing", + "functionType": "Action", + "name": "TeleportAndPress", + "sentence": "Show _PARAM0_ at the cursor position and start pressing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetXY" + }, + "parameters": [ + "Object", + "=", + "Object.ParentTouchX(StartedTouchOrMouseId(0))", + "=", + "Object.ParentTouchY(StartedTouchOrMouseId(0))" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::ActivateControl" + }, + "parameters": [ + "Object", + "yes", + "" + ] + }, + { + "type": { + "value": "Show" + }, + "parameters": [ + "Object", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "ShouldBeHiddenWhenReleased", + "True", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::ForceStartPressing" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "StartedTouchOrMouseId(0)", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the X position of a specified touch", + "fullName": "Touch X position (on parent)", + "functionType": "Expression", + "name": "ParentTouchX", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];", + "const touchId = eventsFunctionContext.getArgument(\"TouchId\");", + "eventsFunctionContext.returnValue = gdjs.evtTools.input.getTouchX(object.getInstanceContainer(), touchId, object.getLayer());" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + }, + { + "description": "Touch identifier", + "name": "TouchId", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the X position of a specified touch", + "fullName": "Touch X position (on parent)", + "functionType": "Expression", + "name": "ParentTouchY", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];", + "const touchId = eventsFunctionContext.getArgument(\"TouchId\");", + "eventsFunctionContext.returnValue = gdjs.evtTools.input.getTouchY(object.getInstanceContainer(), touchId, object.getLayer());" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + }, + { + "description": "Touch identifier", + "name": "TouchId", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "De/activate control of the joystick.", + "fullName": "De/activate control", + "functionType": "Action", + "name": "ActivateControl", + "sentence": "Activate control of _PARAM0_: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldActivate", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "no" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldActivate", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "yes" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + }, + { + "description": "Activate", + "name": "ShouldActivate", + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a stick is pressed.", + "fullName": "Stick pressed", + "functionType": "Condition", + "name": "IsPressed", + "sentence": "Stick _PARAM0_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::IsPressed" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "!=" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the joystick force (from 0 to 1).", + "fullName": "Joystick force (deprecated)", + "functionType": "ExpressionAndCondition", + "name": "JoystickForce", + "private": true, + "sentence": "the joystick force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::JoystickForce()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the strick force (from 0 to 1).", + "fullName": "Stick force", + "functionType": "ExpressionAndCondition", + "name": "StickForce", + "sentence": "the stick force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::JoystickForce()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the stick force on X axis (from -1 at the left to 1 at the right).", + "fullName": "Stick X force", + "functionType": "ExpressionAndCondition", + "name": "StickForceX", + "sentence": "the stick X force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::StickForceX()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the stick force on Y axis (from -1 at the top to 1 at the bottom).", + "fullName": "Stick Y force", + "functionType": "ExpressionAndCondition", + "name": "StickForceY", + "sentence": "the stick Y force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::StickForceY()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle the joystick is pointing towards (from -180 to 180).", + "fullName": "Joystick angle (deprecated)", + "functionType": "Expression", + "name": "JoystickAngle", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::JoystickAngle()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle the stick is pointing towards (from -180 to 180).", + "fullName": "Stick angle", + "functionType": "Expression", + "name": "StickAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::JoystickAngle()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (4-way movement)", + "functionType": "Condition", + "name": "IsDirectionPushed4Way", + "sentence": "_PARAM0_ is pushed in direction _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (8-way movement)", + "functionType": "Condition", + "name": "IsDirectionPushed8Way", + "sentence": "_PARAM0_ is pushed in direction _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\",\"UpLeft\",\"UpRight\",\"DownLeft\",\"DownRight\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "the multitouch controller identifier (1, 2, 3, 4...).", + "fullName": "Multitouch controller identifier", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "ControllerIdentifier", + "sentence": "the multitouch controller identifier", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::PropertyControllerIdentifier()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ControllerIdentifier", + "group": "Multitouch Joystick configuration", + "name": "SetControllerIdentifier", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetControllerIdentifier" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "Value", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the joystick name of the object.", + "fullName": "Joystick name", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "JoystickIdentifier", + "sentence": "the joystick name", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "Border.MultitouchJoystick::PropertyJoystickIdentifier()" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "JoystickIdentifier", + "group": "Multitouch Joystick configuration", + "name": "SetJoystickIdentifier", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetJoystickIdentifier" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "Value", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the dead zone radius of the joystick (range: 0 to 1). The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved).", + "fullName": "Dead zone radius", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "DeadZoneRadius", + "sentence": "the dead zone radius", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::PropertyDeadZoneRadius()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "DeadZoneRadius", + "group": "Multitouch Joystick configuration", + "name": "SetDeadZoneRadius", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetDeadZoneRadius" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "Value", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPostEvents" + }, + { + "functionName": "onHotReloading" + }, + { + "functionName": "UpdateConfiguration" + }, + { + "functionName": "TeleportAndPress" + }, + { + "functionName": "ParentTouchX" + }, + { + "functionName": "ParentTouchY" + }, + { + "functionName": "ActivateControl" + }, + { + "functionName": "IsPressed" + }, + { + "functionName": "JoystickForce" + }, + { + "functionName": "StickForce" + }, + { + "functionName": "StickForceX" + }, + { + "functionName": "StickForceY" + }, + { + "functionName": "JoystickAngle" + }, + { + "functionName": "StickAngle" + }, + { + "functionName": "IsDirectionPushed4Way" + }, + { + "functionName": "IsDirectionPushed8Way" + }, + { + "folderName": "Multitouch Joystick configuration", + "children": [ + { + "functionName": "ControllerIdentifier" + }, + { + "functionName": "SetControllerIdentifier" + }, + { + "functionName": "JoystickIdentifier" + }, + { + "functionName": "SetJoystickIdentifier" + }, + { + "functionName": "DeadZoneRadius" + }, + { + "functionName": "SetDeadZoneRadius" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "1", + "type": "Number", + "label": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Joystick name", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "0.4", + "type": "Number", + "label": "Dead zone radius (range: 0 to 1)", + "description": "The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved)", + "name": "DeadZoneRadius" + }, + { + "value": "Center-center", + "type": "String", + "label": "", + "description": "Only used by the scene editor.", + "extraInformation": [ + "Thumb" + ], + "choices": [], + "hidden": true, + "name": "ThumbAnchorOrigin" + }, + { + "value": "Center-center", + "type": "Number", + "label": "", + "description": "Only used by the scene editor.", + "hidden": true, + "name": "ThumbAnchorTarget" + }, + { + "value": "true", + "type": "Boolean", + "label": "", + "description": "Only used by the scene editor.", + "extraInformation": [ + "Thumb" + ], + "choices": [], + "hidden": true, + "name": "ThumbIsScaledProportionally" + }, + { + "value": "Center-center", + "type": "String", + "label": "", + "description": "Only used by the scene editor.", + "hidden": true, + "name": "ParentOrigin" + }, + { + "value": "", + "type": "Boolean", + "label": "", + "hidden": true, + "name": "ShouldBeHiddenWhenReleased" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ControllerIdentifier" + }, + { + "propertyName": "JoystickIdentifier" + }, + { + "propertyName": "DeadZoneRadius" + }, + { + "propertyName": "ThumbAnchorOrigin" + }, + { + "propertyName": "ThumbAnchorTarget" + }, + { + "propertyName": "ThumbIsScaledProportionally" + }, + { + "propertyName": "ParentOrigin" + }, + { + "propertyName": "ShouldBeHiddenWhenReleased" + } + ] + } + } + ] + }, + { + "author": "", + "category": "Input", + "dimension": "", + "extensionNamespace": "", + "fullName": "3D car keyboard mapper", + "gdevelopVersion": ">=5.5.222", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBhdGggZD0iTTI5LDE0aC00LjJjMC4xLTAuMywwLjItMC42LDAuMi0xYzAtMS43LTEuMy0zLTMtM0g4Yy0wLjYsMC0xLTAuNC0xLTFzMC40LTEsMS0xaDZjMS43LDAsMy0xLjMsMy0zVjJjMC0wLjYtMC40LTEtMS0xDQoJcy0xLDAuNC0xLDF2M2MwLDAuNi0wLjQsMS0xLDFIOEM2LjMsNiw1LDcuMyw1LDlzMS4zLDMsMywzaDE0YzAuNiwwLDEsMC40LDEsMXMtMC40LDEtMSwxSDNjLTAuNiwwLTEsMC40LTEsMXYxNGMwLDAuNiwwLjQsMSwxLDENCgloMjZjMC42LDAsMS0wLjQsMS0xVjE1QzMwLDE0LjQsMjkuNiwxNCwyOSwxNHogTTcsMThoMmMwLjYsMCwxLDAuNCwxLDFjMC0wLjYsMC40LTEsMS0xaDJjMC42LDAsMSwwLjQsMSwxYzAtMC42LDAuNC0xLDEtMWgyDQoJYzAuNiwwLDEsMC40LDEsMWMwLTAuNiwwLjQtMSwxLTFoMmMwLjYsMCwxLDAuNCwxLDFjMC0wLjYsMC40LTEsMS0xaDJjMC42LDAsMSwwLjQsMSwxcy0wLjQsMS0xLDFoLTJjLTAuNiwwLTEtMC40LTEtMQ0KCWMwLDAuNi0wLjQsMS0xLDFoLTJjLTAuNiwwLTEtMC40LTEtMWMwLDAuNi0wLjQsMS0xLDFoLTJjLTAuNiwwLTEtMC40LTEtMWMwLDAuNi0wLjQsMS0xLDFoLTJjLTAuNiwwLTEtMC40LTEtMWMwLDAuNi0wLjQsMS0xLDFINw0KCWMtMC42LDAtMS0wLjQtMS0xUzYuNCwxOCw3LDE4eiBNMTksMjJjMCwwLjEsMCwwLjMtMC4xLDAuNHMtMC4xLDAuMi0wLjIsMC4zYzAsMC0wLjEsMC4xLTAuMiwwLjFjMCwwLTAuMSwwLjEtMC4yLDAuMQ0KCWMtMC4xLDAtMC4xLDAtMC4yLDAuMWMtMC4xLDAtMC4xLDAtMC4yLDBjLTAuMSwwLTAuMSwwLTAuMiwwYy0wLjEsMC0wLjEsMC0wLjItMC4xYy0wLjEsMC0wLjEtMC4xLTAuMi0wLjFjMCwwLTAuMS0wLjEtMC4xLTAuMQ0KCWMtMC4xLTAuMS0wLjItMC4yLTAuMi0wLjNDMTcsMjIuMywxNywyMi4xLDE3LDIyYzAtMC4xLDAtMC4zLDAuMS0wLjRjMC0wLjEsMC4xLTAuMiwwLjItMC4zYzAsMCwwLjEtMC4xLDAuMS0wLjENCgljMC4xLDAsMC4xLTAuMSwwLjItMC4xYzAuMSwwLDAuMSwwLDAuMi0wLjFjMC4xLDAsMC4zLDAsMC40LDBjMC4xLDAsMC4xLDAsMC4yLDAuMWMwLjEsMCwwLjEsMC4xLDAuMiwwLjFjMC4xLDAsMC4xLDAuMSwwLjIsMC4xDQoJYzAuMSwwLjEsMC4yLDAuMiwwLjIsMC4zQzE5LDIxLjcsMTksMjEuOSwxOSwyMnogTTE1LDIyYzAsMC4xLDAsMC4zLTAuMSwwLjRzLTAuMSwwLjItMC4yLDAuM2MwLDAtMC4xLDAuMS0wLjIsMC4xDQoJYzAsMC0wLjEsMC4xLTAuMiwwLjFjLTAuMSwwLTAuMSwwLTAuMiwwLjFjLTAuMSwwLTAuMSwwLTAuMiwwYy0wLjMsMC0wLjUtMC4xLTAuNy0wLjNjLTAuMS0wLjEtMC4yLTAuMi0wLjItMC4zDQoJQzEzLDIyLjMsMTMsMjIuMSwxMywyMmMwLTAuMSwwLTAuMSwwLTAuMmMwLTAuMSwwLTAuMSwwLjEtMC4yYzAtMC4xLDAtMC4xLDAuMS0wLjJjMC0wLjEsMC4xLTAuMSwwLjEtMC4xYzAuMi0wLjIsMC42LTAuMywwLjktMC4zDQoJYzAuMSwwLDAuMSwwLDAuMiwwLjFjMC4xLDAsMC4xLDAuMSwwLjIsMC4xYzAuMSwwLDAuMSwwLjEsMC4yLDAuMWMwLDAsMC4xLDAuMSwwLjEsMC4xYzAsMC4xLDAuMSwwLjEsMC4xLDAuMmMwLDAuMSwwLDAuMSwwLjEsMC4yDQoJQzE1LDIxLjksMTUsMjEuOSwxNSwyMnogTTUsMjJjMC0wLjEsMC0wLjMsMC4xLTAuNGMwLTAuMSwwLjEtMC4yLDAuMi0wLjNjMC4yLTAuMiwwLjYtMC4zLDAuOS0wLjNjMC4xLDAsMC4xLDAsMC4yLDAuMQ0KCWMwLjEsMCwwLjEsMC4xLDAuMiwwLjFjMCwwLDAuMSwwLjEsMC4yLDAuMWMwLjEsMC4xLDAuMiwwLjIsMC4yLDAuM0M3LDIxLjcsNywyMS45LDcsMjJjMCwwLjMtMC4xLDAuNS0wLjMsMC43YzAsMC0wLjEsMC4xLTAuMiwwLjENCgljLTAuMSwwLTAuMSwwLjEtMC4yLDAuMWMtMC4xLDAtMC4xLDAtMC4yLDAuMWMtMC4xLDAtMC4xLDAtMC4yLDBjLTAuMywwLTAuNS0wLjEtMC43LTAuM0M1LjEsMjIuNSw1LDIyLjMsNSwyMnogTTgsMjZINw0KCWMtMC42LDAtMS0wLjQtMS0xczAuNC0xLDEtMWgxYzAuNiwwLDEsMC40LDEsMVM4LjYsMjYsOCwyNnogTTEwLjksMjIuNGMwLDAuMSwwLDAuMS0wLjEsMC4yYzAsMC0wLjEsMC4xLTAuMSwwLjENCglDMTAuNSwyMi45LDEwLjMsMjMsMTAsMjNjLTAuMSwwLTAuMywwLTAuNC0wLjFjLTAuMS0wLjEtMC4yLTAuMS0wLjMtMC4yYzAtMC4xLTAuMS0wLjEtMC4xLTAuMWMwLTAuMS0wLjEtMC4xLTAuMS0wLjINCgljMC0wLjEsMC0wLjEtMC4xLTAuMmMwLTAuMSwwLTAuMSwwLTAuMmMwLTAuMywwLjEtMC41LDAuMy0wLjdjMC4xLTAuMSwwLjItMC4yLDAuMy0wLjJjMC40LTAuMiwwLjgtMC4xLDEuMSwwLjINCgljMC4yLDAuMiwwLjMsMC40LDAuMywwLjdjMCwwLjEsMCwwLjEsMCwwLjJDMTEsMjIuMywxMSwyMi4zLDEwLjksMjIuNHogTTIwLDI2aC04Yy0wLjYsMC0xLTAuNC0xLTFzMC40LTEsMS0xaDhjMC42LDAsMSwwLjQsMSwxDQoJUzIwLjYsMjYsMjAsMjZ6IE0yMi45LDIyLjRjMCwwLjEsMCwwLjEtMC4xLDAuMmMwLDAtMC4xLDAuMS0wLjEsMC4xYzAsMC0wLjEsMC4xLTAuMiwwLjFjMCwwLTAuMSwwLjEtMC4yLDAuMWMtMC4xLDAtMC4xLDAtMC4yLDAuMQ0KCWMtMC4xLDAtMC4xLDAtMC4yLDBjLTAuMSwwLTAuMSwwLTAuMiwwYy0wLjEsMC0wLjEsMC0wLjItMC4xYy0wLjEsMC0wLjEtMC4xLTAuMi0wLjFjMCwwLTAuMS0wLjEtMC4xLTAuMWMwLTAuMS0wLjEtMC4xLTAuMS0wLjENCgljMC0wLjEtMC4xLTAuMS0wLjEtMC4yYzAtMC4xLDAtMC4xLTAuMS0wLjJjMC0wLjEsMC0wLjEsMC0wLjJjMC0wLjMsMC4xLTAuNSwwLjMtMC43YzAsMCwwLjEtMC4xLDAuMS0wLjFjMC4xLDAsMC4xLTAuMSwwLjItMC4xDQoJYzAuMSwwLDAuMSwwLDAuMi0wLjFjMC4xLDAsMC4zLDAsMC40LDBjMC4xLDAsMC4xLDAsMC4yLDAuMWMwLjEsMCwwLjEsMC4xLDAuMiwwLjFjMC4xLDAsMC4xLDAuMSwwLjIsMC4xYzAuMiwwLjIsMC4zLDAuNCwwLjMsMC43DQoJYzAsMC4xLDAsMC4xLDAsMC4yQzIzLDIyLjMsMjMsMjIuMywyMi45LDIyLjR6IE0yNSwyNmgtMWMtMC42LDAtMS0wLjQtMS0xczAuNC0xLDEtMWgxYzAuNiwwLDEsMC40LDEsMVMyNS42LDI2LDI1LDI2eiBNMjYuNywyMi43DQoJQzI2LjUsMjIuOSwyNi4zLDIzLDI2LDIzYy0wLjEsMC0wLjEsMC0wLjIsMGMtMC4xLDAtMC4xLDAtMC4yLTAuMWMtMC4xLDAtMC4xLTAuMS0wLjItMC4xYy0wLjEsMC0wLjEtMC4xLTAuMi0wLjENCglDMjUuMSwyMi41LDI1LDIyLjMsMjUsMjJjMC0wLjEsMC0wLjMsMC4xLTAuNGMwLTAuMSwwLjEtMC4yLDAuMi0wLjNjMCwwLDAuMS0wLjEsMC4yLTAuMWMwLDAsMC4xLTAuMSwwLjItMC4xYzAuMSwwLDAuMSwwLDAuMi0wLjENCgljMC4zLTAuMSwwLjcsMCwwLjksMC4zYzAuMiwwLjIsMC4zLDAuNCwwLjMsMC43QzI3LDIyLjMsMjYuOSwyMi41LDI2LjcsMjIuN3oiLz4NCjwvc3ZnPg0K", + "name": "PhysicsCar3DKeyMapper", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/Glyphster Pack/Master/SVG/Computers and Hardware/3e5871434a72821bf3ecb44a6857e62a438cf23dc8f95966f56ae97e95315468_Computers and Hardware_keyboard.svg", + "shortDescription": "3D car keyboard controls.", + "version": "1.0.1", + "description": "3D physics car keyboard controls.", + "origin": { + "identifier": "PhysicsCar3DKeyMapper", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT" + }, + "eventsBasedBehaviors": [ + { + "description": "Control a 3D physics car with a keyboard.", + "fullName": "3D car keyboard mapper", + "helpPath": "", + "iconUrl": "", + "name": "PhysicsCar3DKeyboardMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Down" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateBackwardKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Up" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateForwardKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Left" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateLeftKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Right" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateRightKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "HandBrake" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateHandBrakeKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "PhysicsCar3DKeyMapper::PhysicsCar3DKeyboardMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics car", + "extraInformation": [ + "Physics3D::PhysicsCar3D" + ], + "choices": [], + "name": "PhysicsCar3D" + }, + { + "value": "Space", + "type": "KeyboardKey", + "label": "Hand brake key", + "name": "HandBrake" + }, + { + "value": "Up", + "type": "KeyboardKey", + "label": "Up key", + "name": "Up" + }, + { + "value": "Left", + "type": "KeyboardKey", + "label": "Left key", + "name": "Left" + }, + { + "value": "Down", + "type": "KeyboardKey", + "label": "Down key", + "name": "Down" + }, + { + "value": "Right", + "type": "KeyboardKey", + "label": "Right key", + "name": "Right" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "PhysicsCar3D" + }, + { + "propertyName": "HandBrake" + }, + { + "propertyName": "Up" + }, + { + "propertyName": "Left" + }, + { + "propertyName": "Down" + }, + { + "propertyName": "Right" + } + ] + } + } + ], + "eventsBasedObjects": [] + }, + { + "author": "", + "category": "General", + "dimension": "3D", + "extensionNamespace": "", + "fullName": "3D terrain", + "gdevelopVersion": ">=5.5.222", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXRlcnJhaW4iIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTQsNkwxMC4yNSwxMUwxMy4xLDE0LjhMMTEuNSwxNkM5LjgxLDEzLjc1IDcsMTAgNywxMEwxLDE4SDIzTDE0LDZaIiAvPjwvc3ZnPg==", + "name": "Terrain3D", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/10e0a26c0d500830dfe23b94138beb0ef61607ba62bca05a5aa5c849777f1b06_terrain.svg", + "shortDescription": "A ground with hills and valleys that can be sculpted and painted in the editor, with collisions.", + "version": "0.1.0", + "description": [ + "A 3D terrain object for open worlds, adventure or racing games: hills, mountains, islands or valleys, with painted layers of grass, dirt, rock or sand.", + "", + "- **Sculpt and paint in the scene editor**: select a terrain to show the tools (raise, lower, smooth, flatten and paint 4 layers).", + "- **Start from a relief** (hills, mountains, island) or **from a heightmap image**.", + "- **Steep slopes automatically show rock** (or any layer).", + "- **Natural borders between layers**: stones and tufts of a texture cover the gaps of the other where layers meet (height blending), and the repetition of textures is made less visible (texture variation).", + "- **Physics**: add the **3D physics** behavior to the terrain. Characters and objects collide with the ground exactly. The terrain is always static.", + "- **Pathfinding**: add the **floor/obstacle for pathfinding (navmesh based)** behavior to the terrain, so that characters with the navmesh pathfinding behavior walk on its hills and around its cliffs (in recent GDevelop versions).", + "- **Shape it with the Edits property**: a list of raise, flatten, smooth and paint strokes written in JSON, shown in the scene editor. It's readable and can be changed by hand or by an AI agent, like the relief, the heightmap and the other properties. Actions only change the terrain during the game.", + "- **Change it during the game** with actions (raise, lower, flatten, smooth, paint, along a line for paths and rivers) and read the ground height, slope or layer with expressions.", + "- **Grass**: add a **terrain grass** object over the terrain. Thousands of blades sway in the wind, grow where a layer (like grass) is painted and where the ground is not too steep, and are pushed aside by characters. Sculpt or paint the terrain and the grass follows.", + "- **Forests, rocks and bushes**: add a **scattered models** object over the terrain. It places hundreds or thousands of copies of a 3D model (trees, rocks, bushes, flowers...) on the ground and draws them all at once, which is much faster than an object for each. Copies grow where a layer is painted, away from steep slopes, and follow the ground when it's sculpted. Add or erase them with the brushes of the scene editor or with the Edits property, give trees trunk collisions, and cut them down with actions. Ready-made forests, rocks and bushes are in the asset store.", + "- **Tunnels and caves**: make holes in the ground (and fill them again) with the brushes, the Edits property or actions, and put a 3D model of the tunnel or the cave (with a mesh collision shape) under the ground. Nothing is drawn in holes, and characters, cars and rays go through them.", + "- **Made for large worlds**: the terrain is split in chunks that are hidden when off-screen and simplified when far away, and collisions are updated only where the ground changes." + ], + "origin": { + "identifier": "Terrain3D", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d", + "terrain", + "ground", + "heightmap", + "landscape", + "open world", + "grass", + "foliage", + "forest", + "trees", + "tunnel", + "cave" + ], + "authorIds": [], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [ + { + "fullName": "Define helper classes", + "functionType": "Action", + "name": "DefineHelperClasses", + "sentence": "Define helper classes JavaScript code", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (gdjs.__terrain3DExtension) {", + " return;", + "}", + "", + "// Tested by the gameplay tests of the \"3D island terrain\" example (GDevelop-examples repository).", + "", + "const groundsByScene = new WeakMap();", + "/**", + " * The grounds of a scene (the terrains), followed by the terrain grass.", + " *", + " * A ground has: `getVersion()` (changing when the ground changes),", + " * `containsPoint(x, y)`, `getHeightAt(x, y)` (Z position of the ground),", + " * `getSlopeAt(x, y)` (in degrees), `getLayerWeightAt(x, y, layer)` (how", + " * much a painted layer, from 1 to 4, is there, from 0 to 1) and optionally", + " * `getTintAt(x, y)` (the painted tint, as red, green and blue from 0 to 255).", + " * @param {gdjs.RuntimeScene} runtimeScene", + " * @returns {Set}", + " */", + "const getGrounds = (runtimeScene) => {", + " let grounds = groundsByScene.get(runtimeScene);", + " if (!grounds) {", + " grounds = new Set();", + " groundsByScene.set(runtimeScene, grounds);", + " }", + " return grounds;", + "};", + "", + "/**", + " * @param {number} seed", + " * @returns {() => number} Random numbers between 0 (included) and 1 (excluded), the same for the same seed.", + " */", + "const createRandom = (seed) => {", + " let state = seed >>> 0 || 1;", + " return () => {", + " state ^= state << 13;", + " state ^= state >>> 17;", + " state ^= state << 5;", + " return (state >>> 0) / 4294967296;", + " };", + "};", + "", + "/**", + " * Find the highest ground at a position: grounds can overlap (like an island over a sea floor).", + " * @param {Iterable} grounds", + " * @param {{ground: object | null, z: number}} result Set to the ground (null if there is none) and its Z position.", + " */", + "const findHighestGround = (grounds, x, y, result) => {", + " result.ground = null;", + " for (const ground of grounds) {", + " if (!ground.containsPoint(x, y)) continue;", + " const z = ground.getHeightAt(x, y);", + " if (!result.ground || z > result.z) {", + " result.ground = ground;", + " result.z = z;", + " }", + " }", + " return result;", + "};", + "", + "// The grounds or the objects placed on them can change every frame (while", + "// sculpting or moving them in the editor): things are placed again at most this often.", + "const PLACEMENT_DELAY = 0.3;", + "", + "/**", + " * Tells when things placed on the grounds of a scene (grass blades, scattered", + " * models) must be placed again: after the grounds or the given properties", + " * changed, at most every `PLACEMENT_DELAY` seconds.", + " */", + "class GroundPlacement {", + " /** @param {gdjs.RuntimeScene} runtimeScene */", + " constructor(runtimeScene) {", + " this.grounds = getGrounds(runtimeScene);", + " this.placedFrom = '';", + " this.timeSincePlacement = 0;", + " }", + "", + " /**", + " * @param {number} elapsedSeconds", + " * @param {string} propertiesKey Changes when the things must be placed again.", + " */", + " shouldPlaceAgain(elapsedSeconds, propertiesKey) {", + " this.timeSincePlacement += elapsedSeconds;", + " if (this.placedFrom && this.timeSincePlacement < PLACEMENT_DELAY) return false;", + " let placementKey = propertiesKey;", + " for (const ground of this.grounds) placementKey += '|' + ground.getVersion();", + " if (placementKey === this.placedFrom) return false;", + " this.placedFrom = placementKey;", + " this.timeSincePlacement = 0;", + " return true;", + " }", + "", + " placeAgain() {", + " this.placedFrom = '';", + " }", + "}", + "", + "/**", + " * Replace parts of the shader code of a Three.js material, with an error if", + " * a part is missing (for example after an update of Three.js).", + " * @param {string} shaderCode", + " * @param {Array<[string, string]>} replacements The searched code and its replacement.", + " * @param {string} materialName", + " */", + "const patchShaderCode = (shaderCode, replacements, materialName) => {", + " for (const [searchedCode, newCode] of replacements) {", + " if (!shaderCode.includes(searchedCode)) {", + " console.error(`${materialName}: \"${searchedCode}\" was not found in the shader, which won't look as expected.`);", + " }", + " shaderCode = shaderCode.replace(searchedCode, newCode);", + " }", + " return shaderCode;", + "};", + "", + "// Size of the object inner area. Instances are scaled from it, so these only", + "// define the default size of a new terrain.", + "const AREA_SIZE = 4096;", + "const AREA_DEPTH = 600;", + "", + "// Terrains have at most 8 x 8 chunks (so at most 64 draw calls), of at least", + "// 32 x 32 cells.", + "const MIN_CHUNK_CELLS = 32;", + "const MAX_CHUNKS_PER_SIDE = 8;", + "/** @param {number} resolution */", + "const getChunkCells = (resolution) => Math.max(MIN_CHUNK_CELLS, resolution / MAX_CHUNKS_PER_SIDE);", + "// Grid steps of each level of detail, and the distance (in chunk sizes)", + "// from which each one is used.", + "const LOD_STEPS = [1, 2, 4];", + "const LOD_DISTANCES = [0, 3, 6];", + "", + "const LAYER_COUNT = 4;", + "const RESOLUTIONS = [64, 128, 256, 512, 1024];", + "const DEFAULT_RESOLUTION = 256;", + "// Each undo step is a copy of the data: fewer are kept for big terrains.", + "const UNDO_STEPS_SAMPLES = 20 * 257 * 257;", + "", + "const clamp01 = (value) => (value < 0 ? 0 : value > 1 ? 1 : value);", + "const clampInteger = (value, min, max) =>", + " value < min ? min : value > max ? max : Math.floor(value);", + "", + "/**", + " * A rectangle of grid samples, inclusive. Empty when `minI > maxI`.", + " */", + "class SampleRectangle {", + " constructor() {", + " this.clear();", + " }", + "", + " clear() {", + " this.minI = Number.MAX_SAFE_INTEGER;", + " this.minJ = Number.MAX_SAFE_INTEGER;", + " this.maxI = -1;", + " this.maxJ = -1;", + " }", + "", + " isEmpty() {", + " return this.minI > this.maxI;", + " }", + "", + " /** @param {SampleRectangle} other */", + " add(other) {", + " if (other.isEmpty()) return;", + " this.minI = Math.min(this.minI, other.minI);", + " this.minJ = Math.min(this.minJ, other.minJ);", + " this.maxI = Math.max(this.maxI, other.maxI);", + " this.maxJ = Math.max(this.maxJ, other.maxJ);", + " }", + "", + " set(minI, minJ, maxI, maxJ) {", + " this.minI = minI;", + " this.minJ = minJ;", + " this.maxI = maxI;", + " this.maxJ = maxJ;", + " return this;", + " }", + "}", + "", + "/**", + " * The heights (between 0 and 1) and the painted layers weights of a square", + " * grid of `size * size` samples.", + " */", + "class TerrainData {", + " /** @param {number} resolution The number of cells on each side. */", + " constructor(resolution) {", + " this.resolution = resolution;", + " this.size = resolution + 1;", + " this.heights = new Float32Array(this.size * this.size);", + " /** 4 weights (0-255) per sample, one for each layer. */", + " this.splat = new Uint8Array(this.size * this.size * LAYER_COUNT);", + " this.fillLayer(0);", + " /**", + " * 4 bytes per sample: a tint multiplying the layers (red, green and", + " * blue, white for none) and the wetness (0 for dry).", + " */", + " this.colors = new Uint8Array(this.size * this.size * 4);", + " this.clearColors();", + " }", + "", + " clearColors() {", + " this.colors.fill(255);", + " for (let index = 3; index < this.colors.length; index += 4) {", + " this.colors[index] = 0;", + " }", + " }", + "", + " /** @param {number} layer */", + " fillLayer(layer) {", + " this.splat.fill(0);", + " for (let index = layer; index < this.splat.length; index += LAYER_COUNT) {", + " this.splat[index] = 255;", + " }", + " }", + "", + " getHeight(i, j) {", + " const last = this.resolution;", + " const clampedI = i < 0 ? 0 : i > last ? last : i;", + " const clampedJ = j < 0 ? 0 : j > last ? last : j;", + " return this.heights[clampedJ * this.size + clampedI];", + " }", + "", + " /**", + " * @param {number} gridX", + " * @param {number} gridY", + " * @returns {number} The height interpolated on the triangles that are drawn.", + " */", + " sampleHeight(gridX, gridY) {", + " const x = Math.min(Math.max(gridX, 0), this.resolution);", + " const y = Math.min(Math.max(gridY, 0), this.resolution);", + " const i = Math.min(Math.floor(x), this.resolution - 1);", + " const j = Math.min(Math.floor(y), this.resolution - 1);", + " const fx = x - i;", + " const fy = y - j;", + " const topRight = this.getHeight(i + 1, j);", + " const bottomLeft = this.getHeight(i, j + 1);", + " if (fx + fy <= 1) {", + " const topLeft = this.getHeight(i, j);", + " return topLeft + fx * (topRight - topLeft) + fy * (bottomLeft - topLeft);", + " }", + " const bottomRight = this.getHeight(i + 1, j + 1);", + " return (", + " bottomRight +", + " (1 - fx) * (bottomLeft - bottomRight) +", + " (1 - fy) * (topRight - bottomRight)", + " );", + " }", + "", + " /** @returns {number} The weight (0-1) of a layer, interpolated between samples. */", + " sampleLayerWeight(gridX, gridY, layer) {", + " const x = Math.min(Math.max(gridX, 0), this.resolution);", + " const y = Math.min(Math.max(gridY, 0), this.resolution);", + " const i = Math.min(Math.floor(x), this.resolution - 1);", + " const j = Math.min(Math.floor(y), this.resolution - 1);", + " const fx = x - i;", + " const fy = y - j;", + " const weight = (i, j) => this.splat[(j * this.size + i) * LAYER_COUNT + layer];", + " const top = weight(i, j) + fx * (weight(i + 1, j) - weight(i, j));", + " const bottom = weight(i, j + 1) + fx * (weight(i + 1, j + 1) - weight(i, j + 1));", + " return (top + fy * (bottom - top)) / 255;", + " }", + "", + " /**", + " * @param {number} channel 0, 1 and 2 for the tint, 3 for the wetness.", + " * @returns {number} The channel (0-1), interpolated between samples.", + " */", + " sampleColor(gridX, gridY, channel) {", + " const x = Math.min(Math.max(gridX, 0), this.resolution);", + " const y = Math.min(Math.max(gridY, 0), this.resolution);", + " const i = Math.min(Math.floor(x), this.resolution - 1);", + " const j = Math.min(Math.floor(y), this.resolution - 1);", + " const fx = x - i;", + " const fy = y - j;", + " const value = (i, j) => this.colors[(j * this.size + i) * 4 + channel];", + " const top = value(i, j) + fx * (value(i + 1, j) - value(i, j));", + " const bottom = value(i, j + 1) + fx * (value(i + 1, j + 1) - value(i, j + 1));", + " return (top + fy * (bottom - top)) / 255;", + " }", + "", + " /**", + " * @param {number} index The index of a sample.", + " * @returns {boolean} true if no layer is painted on the sample: it's a hole.", + " */", + " isHole(index) {", + " const offset = index * LAYER_COUNT;", + " return (this.splat[offset] | this.splat[offset + 1] | this.splat[offset + 2] | this.splat[offset + 3]) === 0;", + " }", + "", + " /**", + " * @returns {number} The sum of the layer weights (0-1), interpolated between samples: there is a", + " * hole where it's under 0.5, like what is drawn.", + " */", + " sampleSolidity(gridX, gridY) {", + " let solidity = 0;", + " for (let layer = 0; layer < LAYER_COUNT; layer++) solidity += this.sampleLayerWeight(gridX, gridY, layer);", + " return solidity;", + " }", + "", + " /** @returns {number} The index (from 0) of the layer painted the most. */", + " getDominantLayer(gridX, gridY) {", + " const i = clampInteger(Math.round(gridX), 0, this.resolution);", + " const j = clampInteger(Math.round(gridY), 0, this.resolution);", + " const offset = (j * this.size + i) * LAYER_COUNT;", + " let dominantLayer = 0;", + " for (let layer = 1; layer < LAYER_COUNT; layer++) {", + " if (this.splat[offset + layer] > this.splat[offset + dominantLayer]) {", + " dominantLayer = layer;", + " }", + " }", + " return dominantLayer;", + " }", + "", + " /** @param {TerrainData} other */", + " copyFrom(other) {", + " this.heights.set(other.heights);", + " this.splat.set(other.splat);", + " this.colors.set(other.colors);", + " }", + "", + " /**", + " * @param {number} resolution", + " * @returns {TerrainData} A copy of this data with another resolution.", + " */", + " resampled(resolution) {", + " const data = new TerrainData(resolution);", + " const ratio = this.resolution / resolution;", + " for (let j = 0; j < data.size; j++) {", + " for (let i = 0; i < data.size; i++) {", + " const index = j * data.size + i;", + " data.heights[index] = this.sampleHeight(i * ratio, j * ratio);", + " const sourceOffset =", + " (Math.round(j * ratio) * this.size + Math.round(i * ratio)) * LAYER_COUNT;", + " for (let layer = 0; layer < LAYER_COUNT; layer++) {", + " data.splat[index * LAYER_COUNT + layer] = this.splat[sourceOffset + layer];", + " }", + " for (let channel = 0; channel < 4; channel++) {", + " data.colors[index * 4 + channel] = this.colors[sourceOffset + channel];", + " }", + " }", + " }", + " return data;", + " }", + "}", + "", + "/**", + " * Brushes change the samples around a segment, with an effect going smoothly", + " * from full on the segment to none at `radius`. A point is a segment of", + " * length 0. Positions are in scene units relative to the terrain origin.", + " */", + "const brushes = {", + " /**", + " * @param {TerrainData} data", + " * @param {{cellWidth: number, cellHeight: number, ax: number, ay: number, bx: number, by: number, radius: number}} stroke", + " * @param {(sampleIndex: number, weight: number, t: number) => void} apply Called with the weight of", + " * the brush and the position (from 0 to 1) of the nearest point of the segment.", + " * @param {SampleRectangle} changedSamples", + " */", + " forEachSample(data, stroke, apply, changedSamples) {", + " const { cellWidth, cellHeight, ax, ay, bx, by, radius } = stroke;", + " const last = data.resolution;", + " const minI = clampInteger(Math.floor((Math.min(ax, bx) - radius) / cellWidth), 0, last);", + " const maxI = clampInteger(Math.ceil((Math.max(ax, bx) + radius) / cellWidth), 0, last);", + " const minJ = clampInteger(Math.floor((Math.min(ay, by) - radius) / cellHeight), 0, last);", + " const maxJ = clampInteger(Math.ceil((Math.max(ay, by) + radius) / cellHeight), 0, last);", + " changedSamples.set(minI, minJ, maxI, maxJ);", + " if (radius <= 0) return;", + "", + " const abX = bx - ax;", + " const abY = by - ay;", + " const abLengthSquared = abX * abX + abY * abY;", + " const radiusSquared = radius * radius;", + " for (let j = minJ; j <= maxJ; j++) {", + " const y = j * cellHeight;", + " for (let i = minI; i <= maxI; i++) {", + " const x = i * cellWidth;", + " const t =", + " abLengthSquared > 0", + " ? clamp01(((x - ax) * abX + (y - ay) * abY) / abLengthSquared)", + " : 0;", + " const dx = x - (ax + t * abX);", + " const dy = y - (ay + t * abY);", + " const distanceSquared = dx * dx + dy * dy;", + " if (distanceSquared >= radiusSquared) continue;", + " const falloff = 1 - distanceSquared / radiusSquared;", + " apply(j * data.size + i, falloff * falloff, t);", + " }", + " }", + " },", + "", + " raise(data, stroke, amount, changedSamples) {", + " const { heights } = data;", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index, weight) => {", + " heights[index] = clamp01(heights[index] + amount * weight);", + " },", + " changedSamples", + " );", + " },", + "", + " /**", + " * @param {number} targetHeight", + " * @param {number} [endTargetHeight] The target at the end of the segment, for a slope (like a road", + " * going up). The same target as at its start by default.", + " */", + " flatten(data, stroke, targetHeight, strength, changedSamples, endTargetHeight = targetHeight) {", + " const { heights } = data;", + " const startTarget = clamp01(targetHeight);", + " const endTarget = clamp01(endTargetHeight);", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index, weight, t) => {", + " const target = startTarget + (endTarget - startTarget) * t;", + " heights[index] += (target - heights[index]) * clamp01(weight * strength);", + " },", + " changedSamples", + " );", + " },", + "", + " smooth(data, stroke, strength, changedSamples) {", + " const { heights, size } = data;", + " const changes = [];", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index, weight) => {", + " const i = index % size;", + " const j = (index - i) / size;", + " let sum = 0;", + " for (let dj = -1; dj <= 1; dj++) {", + " for (let di = -1; di <= 1; di++) {", + " sum += data.getHeight(i + di, j + dj);", + " }", + " }", + " changes.push(index, heights[index] + (sum / 9 - heights[index]) * clamp01(weight * strength));", + " },", + " changedSamples", + " );", + " // Written after all averages are computed, so that the result", + " // doesn't depend on the order in which samples are visited.", + " for (let index = 0; index < changes.length; index += 2) {", + " heights[changes[index]] = changes[index + 1];", + " }", + " },", + "", + " paint(data, stroke, layer, strength, changedSamples) {", + " const { splat } = data;", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index, weight) => {", + " const amount = clamp01(weight * strength);", + " const offset = index * LAYER_COUNT;", + " for (let otherLayer = 0; otherLayer < LAYER_COUNT; otherLayer++) {", + " const target = otherLayer === layer ? 255 : 0;", + " const value = splat[offset + otherLayer];", + " // Rounded towards the target, so that light strokes still paint.", + " const change = (target - value) * amount;", + " splat[offset + otherLayer] = value + (change > 0 ? Math.ceil(change) : Math.floor(change));", + " }", + " },", + " changedSamples", + " );", + " },", + "", + " /** Remove all the layers, making a hole (`fill` or painting a layer fills it again). */", + " hole(data, stroke, changedSamples) {", + " const { splat } = data;", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index) => {", + " splat.fill(0, index * LAYER_COUNT, (index + 1) * LAYER_COUNT);", + " },", + " changedSamples", + " );", + " },", + "", + " /**", + " * Fill the holes in the radius with the layers of the ground around them:", + " * each pass fills the holes next to the ground with the average of their", + " * neighbors, from the edges of the holes inward.", + " */", + " fill(data, stroke, changedSamples) {", + " const { splat, size, resolution } = data;", + " /** @type {number[]} */", + " let holes = [];", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index) => {", + " if (data.isHole(index)) holes.push(index);", + " },", + " changedSamples", + " );", + " const filledWeights = new Float32Array(holes.length * LAYER_COUNT);", + " while (holes.length > 0) {", + " /** @type {number[]} */", + " const remainingHoles = [];", + " /** @type {number[]} */", + " const filledHoles = [];", + " for (const index of holes) {", + " const i = index % size;", + " const j = (index - i) / size;", + " const weights = filledWeights.subarray(filledHoles.length * LAYER_COUNT, (filledHoles.length + 1) * LAYER_COUNT);", + " weights.fill(0);", + " for (let neighborJ = Math.max(j - 1, 0); neighborJ <= Math.min(j + 1, resolution); neighborJ++) {", + " for (let neighborI = Math.max(i - 1, 0); neighborI <= Math.min(i + 1, resolution); neighborI++) {", + " const neighbor = neighborJ * size + neighborI;", + " if (data.isHole(neighbor)) continue;", + " for (let layer = 0; layer < LAYER_COUNT; layer++) weights[layer] += splat[neighbor * LAYER_COUNT + layer];", + " }", + " }", + " if (weights[0] + weights[1] + weights[2] + weights[3] > 0) filledHoles.push(index);", + " else remainingHoles.push(index);", + " }", + " if (filledHoles.length === 0) {", + " // No ground around (a hole bigger than the brush): the first layer is used.", + " for (const index of remainingHoles) splat[index * LAYER_COUNT] = 255;", + " break;", + " }", + " // Filled after the pass, so that the holes are filled from the edges inward.", + " filledHoles.forEach((index, filledIndex) => {", + " const weights = filledWeights.subarray(filledIndex * LAYER_COUNT, (filledIndex + 1) * LAYER_COUNT);", + " const total = weights[0] + weights[1] + weights[2] + weights[3];", + " for (let layer = 0; layer < LAYER_COUNT; layer++) {", + " splat[index * LAYER_COUNT + layer] = Math.round((weights[layer] / total) * 255);", + " }", + " });", + " holes = remainingHoles;", + " }", + " },", + "", + " /** @param {number[]} color The red, green and blue (0-255) of the tint: white removes it. */", + " tint(data, stroke, color, strength, changedSamples) {", + " brushes.paintColors(data, stroke, 0, color, strength, changedSamples);", + " },", + "", + " /** @param {number} wetness From 0 (dry) to 1. */", + " wet(data, stroke, wetness, strength, changedSamples) {", + " brushes.paintColors(data, stroke, 3, [Math.round(clamp01(wetness) * 255)], strength, changedSamples);", + " },", + "", + " /**", + " * Bring channels of the colors (see `TerrainData.colors`) towards targets.", + " * @param {number} firstChannel", + " * @param {number[]} targets From 0 to 255, for the channels from the first one.", + " */", + " paintColors(data, stroke, firstChannel, targets, strength, changedSamples) {", + " const { colors } = data;", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index, weight) => {", + " const amount = clamp01(weight * strength);", + " for (let channel = 0; channel < targets.length; channel++) {", + " const offset = index * 4 + firstChannel + channel;", + " // Rounded towards the target, so that light strokes still paint.", + " const change = (targets[channel] - colors[offset]) * amount;", + " colors[offset] += change > 0 ? Math.ceil(change) : Math.floor(change);", + " }", + " },", + " changedSamples", + " );", + " },", + "};", + "", + "const relief = (() => {", + " const hash = (x, y, seed) => {", + " let h = Math.imul(x, 374761393) + Math.imul(y, 668265263) + Math.imul(seed, 982451653);", + " h = Math.imul(h ^ (h >>> 13), 1274126177);", + " return ((h ^ (h >>> 16)) >>> 0) / 4294967296;", + " };", + " const valueNoise = (x, y, seed) => {", + " const x0 = Math.floor(x);", + " const y0 = Math.floor(y);", + " const fx = x - x0;", + " const fy = y - y0;", + " const sx = fx * fx * (3 - 2 * fx);", + " const sy = fy * fy * (3 - 2 * fy);", + " const top = hash(x0, y0, seed) + sx * (hash(x0 + 1, y0, seed) - hash(x0, y0, seed));", + " const bottom =", + " hash(x0, y0 + 1, seed) + sx * (hash(x0 + 1, y0 + 1, seed) - hash(x0, y0 + 1, seed));", + " return top + sy * (bottom - top);", + " };", + " /** Fractal noise between 0 and 1. */", + " const fractalNoise = (x, y, seed) => {", + " let value = 0;", + " let amplitude = 0.5;", + " let frequency = 1;", + " for (let octave = 0; octave < 5; octave++) {", + " value += amplitude * valueNoise(x * frequency, y * frequency, seed + octave);", + " amplitude *= 0.5;", + " frequency *= 2;", + " }", + " return value / 0.96875;", + " };", + " const smoothstep = (edge0, edge1, x) => {", + " const t = clamp01((x - edge0) / (edge1 - edge0));", + " return t * t * (3 - 2 * t);", + " };", + " const reliefHeights = {", + " Flat: () => 0,", + " Hills: (u, v, seed) => 0.05 + 0.5 * Math.pow(fractalNoise(u * 3, v * 3, seed), 2),", + " Mountains: (u, v, seed) => {", + " const ridge = 1 - Math.abs(2 * fractalNoise(u * 2.5, v * 2.5, seed) - 1);", + " return 0.9 * Math.pow(ridge, 2.5);", + " },", + " Island: (u, v, seed) => {", + " const distanceToCenter = 2 * Math.hypot(u - 0.5, v - 0.5);", + " const coast = 0.2 * (fractalNoise(u * 4, v * 4, seed + 7) - 0.5);", + " const land = smoothstep(0.95, 0.55, distanceToCenter + coast);", + " return land * (0.12 + 0.6 * Math.pow(fractalNoise(u * 3, v * 3, seed), 1.5));", + " },", + " };", + " const beachLayer = 3;", + "", + " return {", + " /**", + " * @param {TerrainData} data", + " * @param {string} reliefName Flat, Hills, Mountains or Island.", + " * @param {number} seed", + " */", + " generate(data, reliefName, seed) {", + " const getHeight = reliefHeights[reliefName] || reliefHeights.Flat;", + " const integerSeed = Math.floor(seed) | 0;", + " data.fillLayer(0);", + " for (let j = 0; j < data.size; j++) {", + " for (let i = 0; i < data.size; i++) {", + " const index = j * data.size + i;", + " const height = clamp01(", + " getHeight(i / data.resolution, j / data.resolution, integerSeed)", + " );", + " data.heights[index] = height;", + " if (reliefName === 'Island') {", + " const sand = Math.round(255 * smoothstep(0.16, 0.1, height));", + " const offset = index * LAYER_COUNT;", + " data.splat[offset] = 255 - sand;", + " data.splat[offset + beachLayer] = sand;", + " }", + " }", + " }", + " },", + " };", + "})();", + "", + "/**", + " * Edits written in the Edits property: a JSON list of brush strokes, like", + " * `[{\"tool\": \"raise\", \"x\": 0.5, \"y\": 0.5, \"radius\": 0.2, \"height\": 0.3}]`,", + " * applied on the relief or the heightmap. Positions and radius are fractions", + " * of the terrain size, heights and Z positions fractions of its depth: they", + " * are the same for all instances, whatever their size. They are made to be", + " * read and written by people and by AI agents.", + " */", + "const edits = {", + " /**", + " * @param {TerrainData} data", + " * @param {string} text", + " */", + " apply(data, text) {", + " if (!text.trim()) return;", + " let editList;", + " try {", + " editList = JSON.parse(text);", + " } catch (error) {", + " console.warn('The edits of a terrain are not valid JSON: ' + error.message);", + " return;", + " }", + " if (!Array.isArray(editList)) {", + " console.warn('The edits of a terrain must be a JSON list (like [{\"tool\": \"raise\", ...}]).');", + " return;", + " }", + " const numberOr = (value, defaultValue) => (typeof value === 'number' && isFinite(value) ? value : defaultValue);", + " const changedSamples = new SampleRectangle();", + " for (const edit of editList) {", + " if (!edit || typeof edit !== 'object') continue;", + " const x = numberOr(edit.x, 0.5);", + " const y = numberOr(edit.y, 0.5);", + " const stroke = {", + " cellWidth: 1 / data.resolution,", + " cellHeight: 1 / data.resolution,", + " ax: x,", + " ay: y,", + " bx: numberOr(edit.toX, x),", + " by: numberOr(edit.toY, y),", + " radius: numberOr(edit.radius, 0.1),", + " };", + " const strength = clamp01(numberOr(edit.strength, 1));", + " if (edit.tool === 'raise') {", + " brushes.raise(data, stroke, numberOr(edit.height, 0.1), changedSamples);", + " } else if (edit.tool === 'flatten') {", + " const z = numberOr(edit.z, 0);", + " brushes.flatten(data, stroke, z, strength, changedSamples, numberOr(edit.toZ, z));", + " } else if (edit.tool === 'smooth') {", + " // Smoothing the same fraction of the terrain at any resolution.", + " const passes = Math.max(Math.round(((1 + 9 * strength) * data.resolution) / 256), 1);", + " for (let pass = 0; pass < passes; pass++) brushes.smooth(data, stroke, 1, changedSamples);", + " } else if (edit.tool === 'paint') {", + " const layer = clampInteger(numberOr(edit.layer, 1) - 1, 0, LAYER_COUNT - 1);", + " brushes.paint(data, stroke, layer, strength, changedSamples);", + " } else if (edit.tool === 'tint') {", + " const color = gdjs.rgbOrHexToRGBColor(typeof edit.color === 'string' ? edit.color : '255;255;255');", + " brushes.tint(data, stroke, color, strength, changedSamples);", + " } else if (edit.tool === 'wetness') {", + " brushes.wet(data, stroke, numberOr(edit.amount, 1), strength, changedSamples);", + " } else if (edit.tool === 'hole') {", + " brushes.hole(data, stroke, changedSamples);", + " } else if (edit.tool === 'fill') {", + " brushes.fill(data, stroke, changedSamples);", + " } else {", + " console.warn('Unknown tool in the edits of a terrain: \"' + edit.tool + '\" (use raise, flatten, smooth, paint, tint, wetness, hole or fill).');", + " }", + " }", + " },", + "};", + "", + "/**", + " * @param {gdjs.RuntimeGame} game", + " * @param {string} imageResourceName", + " * @returns {CanvasImageSource | null} The image, or null if it's not loaded (yet).", + " */", + "const getLoadedImage = (game, imageResourceName) => {", + " const imageManager = game.getImageManager();", + " if (typeof imageManager.getImageSource === 'function') return imageManager.getImageSource(imageResourceName);", + " // Older GDevelop versions: a 192 x 192 placeholder is given for images that are not loaded.", + " const texture = imageManager.getPIXITexture(imageResourceName);", + " return texture && texture !== imageManager.getInvalidPIXITexture() ? texture.baseTexture.getDrawableSource() : null;", + "};", + "", + "/**", + " * Loads heights from an image: black is the lowest, white the highest.", + " * Heights have 16 bits of precision when the red channel holds the most", + " * significant byte and the green one the least significant (a gray image", + " * works as expected too).", + " * @param {TerrainData} data", + " * @param {gdjs.RuntimeGame} game", + " * @param {string} imageResourceName", + " * @returns {boolean} true if the image could be read.", + " */", + "const loadHeightsFromImage = (data, game, imageResourceName) => {", + " const source = getLoadedImage(game, imageResourceName);", + " if (!source) return false;", + "", + " const canvas = document.createElement('canvas');", + " canvas.width = source.width;", + " canvas.height = source.height;", + " const context = canvas.getContext('2d', { willReadFrequently: true });", + " if (!context) return false;", + " context.drawImage(source, 0, 0);", + " const pixels = context.getImageData(0, 0, source.width, source.height).data;", + " // Pixels are decoded before being interpolated, to keep the 16 bits.", + " const pixelHeight = (x, y) => {", + " const offset = (y * source.width + x) * 4;", + " return (256 * pixels[offset] + pixels[offset + 1]) / 65535;", + " };", + " for (let j = 0; j < data.size; j++) {", + " const y = (j / data.resolution) * (source.height - 1);", + " const y0 = Math.floor(y);", + " const y1 = Math.min(y0 + 1, source.height - 1);", + " for (let i = 0; i < data.size; i++) {", + " const x = (i / data.resolution) * (source.width - 1);", + " const x0 = Math.floor(x);", + " const x1 = Math.min(x0 + 1, source.width - 1);", + " const top = pixelHeight(x0, y0) + (x - x0) * (pixelHeight(x1, y0) - pixelHeight(x0, y0));", + " const bottom = pixelHeight(x0, y1) + (x - x0) * (pixelHeight(x1, y1) - pixelHeight(x0, y1));", + " data.heights[j * data.size + i] = top + (y - y0) * (bottom - top);", + " }", + " }", + " return true;", + "};", + "", + "/**", + " * Stores the data in a string. Only the differences with the base data (from", + " * the relief or the heightmap) are stored: heights on 16 bits, layer weights", + " * on 8 bits, then (since format 2) the tint and wetness on 8 bits. Each value is predicted from its neighbors and the", + " * prediction errors are written as variable length integers, with runs of", + " * zeros collapsed. Untouched or smoothly changed areas take almost no space.", + " */", + "const codec = (() => {", + " const formatVersion = '2';", + " // Format 1 has no tint and wetness.", + " const readableFormatVersions = ['1', '2'];", + "", + " class ByteWriter {", + " constructor() {", + " this.bytes = new Uint8Array(1024);", + " this.length = 0;", + " }", + " writeVarUint(value) {", + " if (this.length + 5 > this.bytes.length) {", + " const bytes = new Uint8Array(this.bytes.length * 2);", + " bytes.set(this.bytes);", + " this.bytes = bytes;", + " }", + " while (value >= 0x80) {", + " this.bytes[this.length++] = (value & 0x7f) | 0x80;", + " value >>>= 7;", + " }", + " this.bytes[this.length++] = value;", + " }", + " }", + "", + " class ByteReader {", + " /** @param {Uint8Array} bytes */", + " constructor(bytes) {", + " this.bytes = bytes;", + " this.position = 0;", + " }", + " readVarUint() {", + " let value = 0;", + " let shift = 0;", + " let byte;", + " do {", + " if (this.position >= this.bytes.length) throw new Error('Truncated terrain data.');", + " byte = this.bytes[this.position++];", + " value += (byte & 0x7f) * Math.pow(2, shift);", + " shift += 7;", + " } while (byte & 0x80);", + " return value;", + " }", + " }", + "", + " /** Predicts a value from the left, top and top-left ones. */", + " const predict = (values, size, index) => {", + " const i = index % size;", + " if (index < size) return i === 0 ? 0 : values[index - 1];", + " if (i === 0) return values[index - size];", + " return values[index - 1] + values[index - size] - values[index - size - 1];", + " };", + "", + " /**", + " * @param {ByteWriter} writer", + " * @param {Int32Array} values A grid of `size * size` values.", + " */", + " const writeGrid = (writer, values, size) => {", + " let zeroRun = 0;", + " for (let index = 0; index < values.length; index++) {", + " const error = values[index] - predict(values, size, index);", + " if (error === 0) {", + " zeroRun++;", + " continue;", + " }", + " if (zeroRun > 0) {", + " writer.writeVarUint(0);", + " writer.writeVarUint(zeroRun);", + " zeroRun = 0;", + " }", + " // Zigzag: small negative and positive numbers get small codes.", + " writer.writeVarUint(error > 0 ? error * 2 : -error * 2 - 1);", + " }", + " if (zeroRun > 0) {", + " writer.writeVarUint(0);", + " writer.writeVarUint(zeroRun);", + " }", + " };", + "", + " /** @param {ByteReader} reader */", + " const readGrid = (reader, size) => {", + " const values = new Int32Array(size * size);", + " let zeroRun = 0;", + " for (let index = 0; index < values.length; index++) {", + " let error = 0;", + " if (zeroRun > 0) {", + " zeroRun--;", + " } else {", + " const code = reader.readVarUint();", + " if (code === 0) {", + " zeroRun = reader.readVarUint() - 1;", + " } else {", + " error = code % 2 === 0 ? code / 2 : -(code + 1) / 2;", + " }", + " }", + " values[index] = predict(values, size, index) + error;", + " }", + " return values;", + " };", + "", + " const toBase64 = (bytes, length) => {", + " let binary = '';", + " for (let offset = 0; offset < length; offset += 0x8000) {", + " binary += String.fromCharCode.apply(null, bytes.subarray(offset, Math.min(offset + 0x8000, length)));", + " }", + " return btoa(binary);", + " };", + "", + " const fromBase64 = (base64) => {", + " const binary = atob(base64);", + " const bytes = new Uint8Array(binary.length);", + " for (let index = 0; index < binary.length; index++) {", + " bytes[index] = binary.charCodeAt(index);", + " }", + " return bytes;", + " };", + "", + " return {", + " /**", + " * @param {TerrainData} data", + " * @param {TerrainData} baseData The data without changes, with the same resolution.", + " */", + " encode(data, baseData) {", + " const writer = new ByteWriter();", + " const values = new Int32Array(data.heights.length);", + " for (let index = 0; index < values.length; index++) {", + " values[index] = Math.round((data.heights[index] - baseData.heights[index]) * 65535);", + " }", + " writeGrid(writer, values, data.size);", + " for (let layer = 0; layer < LAYER_COUNT; layer++) {", + " for (let index = 0; index < values.length; index++) {", + " const offset = index * LAYER_COUNT + layer;", + " values[index] = data.splat[offset] - baseData.splat[offset];", + " }", + " writeGrid(writer, values, data.size);", + " }", + " // Not painted, these grids are zeros: a few bytes each.", + " for (let channel = 0; channel < 4; channel++) {", + " for (let index = 0; index < values.length; index++) {", + " const offset = index * 4 + channel;", + " values[index] = data.colors[offset] - baseData.colors[offset];", + " }", + " writeGrid(writer, values, data.size);", + " }", + " return formatVersion + ';' + data.resolution + ';' + toBase64(writer.bytes, writer.length);", + " },", + "", + " /**", + " * Store grids of integers in a string, like the terrain data. Used by other objects (the scattered models).", + " * @param {Int32Array[]} grids Grids of `size * size` values.", + " * @param {number} size", + " */", + " encodeGrids(grids, size) {", + " const writer = new ByteWriter();", + " for (const grid of grids) writeGrid(writer, grid, size);", + " return toBase64(writer.bytes, writer.length);", + " },", + "", + " /**", + " * @param {string} base64 A string from `encodeGrids`.", + " * @param {number} size", + " * @param {number} count The number of grids.", + " * @returns {Int32Array[]} The grids (an error is thrown if the string is not valid).", + " */", + " decodeGrids(base64, size, count) {", + " const reader = new ByteReader(fromBase64(base64));", + " return Array.from({ length: count }, () => readGrid(reader, size));", + " },", + "", + " /**", + " * @param {string} text", + " * @param {(resolution: number) => TerrainData} createBaseData", + " * @returns {TerrainData | null} null if the text is empty or not valid.", + " */", + " decode(text, createBaseData) {", + " if (!text) return null;", + " const parts = text.split(';');", + " const resolution = parseInt(parts[1], 10);", + " if (readableFormatVersions.indexOf(parts[0]) === -1 || RESOLUTIONS.indexOf(resolution) === -1) {", + " return null;", + " }", + " try {", + " const reader = new ByteReader(fromBase64(parts[2]));", + " const data = createBaseData(resolution);", + " const heightChanges = readGrid(reader, data.size);", + " for (let index = 0; index < heightChanges.length; index++) {", + " data.heights[index] = clamp01(data.heights[index] + heightChanges[index] / 65535);", + " }", + " for (let layer = 0; layer < LAYER_COUNT; layer++) {", + " const weightChanges = readGrid(reader, data.size);", + " for (let index = 0; index < weightChanges.length; index++) {", + " const offset = index * LAYER_COUNT + layer;", + " // The base may have changed since the data was saved.", + " data.splat[offset] = Math.min(Math.max(data.splat[offset] + weightChanges[index], 0), 255);", + " }", + " }", + " if (parts[0] !== '1') {", + " for (let channel = 0; channel < 4; channel++) {", + " const colorChanges = readGrid(reader, data.size);", + " for (let index = 0; index < colorChanges.length; index++) {", + " const offset = index * 4 + channel;", + " data.colors[offset] = Math.min(Math.max(data.colors[offset] + colorChanges[index], 0), 255);", + " }", + " }", + " }", + " return data;", + " } catch (error) {", + " console.error('Terrain data could not be read:', error);", + " return null;", + " }", + " },", + " };", + "})();", + "", + "const lodIndices = new Map();", + "/**", + " * The triangles of a chunk, shared by all chunks of the same size. Chunk", + " * borders have a skirt going down, hiding the gaps between chunks drawn", + " * with different levels of detail.", + " * @param {number} chunkCells", + " * @param {number} step", + " */", + "const getLodIndex = (chunkCells, step) => {", + " const key = chunkCells + ';' + step;", + " const existingIndex = lodIndices.get(key);", + " if (existingIndex) return existingIndex;", + "", + " const sideVertices = chunkCells + 1;", + " const gridVertices = sideVertices * sideVertices;", + " const indices = [];", + " const grid = (i, j) => j * sideVertices + i;", + " for (let j = 0; j < chunkCells; j += step) {", + " for (let i = 0; i < chunkCells; i += step) {", + " const topLeft = grid(i, j);", + " const topRight = grid(i + step, j);", + " const bottomLeft = grid(i, j + step);", + " const bottomRight = grid(i + step, j + step);", + " indices.push(topLeft, topRight, bottomLeft, topRight, bottomRight, bottomLeft);", + " }", + " }", + " const borders = [", + " (k) => grid(k, 0),", + " (k) => grid(k, chunkCells),", + " (k) => grid(0, k),", + " (k) => grid(chunkCells, k),", + " ];", + " borders.forEach((borderVertex, border) => {", + " const skirtVertex = (k) => gridVertices + border * sideVertices + k;", + " for (let k = 0; k < chunkCells; k += step) {", + " const a = borderVertex(k);", + " const b = borderVertex(k + step);", + " const skirtA = skirtVertex(k);", + " const skirtB = skirtVertex(k + step);", + " // Both faces, as a skirt can be seen from either side.", + " indices.push(a, b, skirtB, a, skirtB, skirtA, a, skirtB, b, a, skirtA, skirtB);", + " }", + " });", + " const index = new THREE.BufferAttribute(new Uint16Array(indices), 1);", + " lodIndices.set(key, index);", + " return index;", + "};", + "", + "const vertexShaderDeclarations = `", + "varying vec2 vTerrainLocalXY;", + "varying float vTerrainLocalZ;", + "uniform float terrainWorldScale;", + "varying vec2 vTerrainWorldXY;", + "varying float vTerrainUp;", + "`;", + "const vertexShaderCode = `", + "vTerrainLocalXY = position.xy;", + "vTerrainLocalZ = position.z;", + "vTerrainWorldXY = (modelMatrix * vec4(transformed, 1.0)).xy * terrainWorldScale;", + "vTerrainUp = normalize(transpose(inverse(mat3(modelMatrix))) * objectNormal).z;", + "`;", + "const fragmentShaderDeclarations = `", + "uniform sampler2D terrainSplatMap;", + "uniform vec2 terrainSplatUvScaleAndOffset;", + "uniform vec3 terrainLayerColors[4];", + "uniform sampler2D terrainLayerMap0;", + "uniform sampler2D terrainLayerMap1;", + "uniform sampler2D terrainLayerMap2;", + "uniform sampler2D terrainLayerMap3;", + "uniform sampler2D terrainLayerNormalMap0;", + "uniform sampler2D terrainLayerNormalMap1;", + "uniform sampler2D terrainLayerNormalMap2;", + "uniform sampler2D terrainLayerNormalMap3;", + "uniform float terrainHasNormalMaps;", + "uniform float terrainNormalMapStrength;", + "uniform float terrainTextureSize;", + "uniform float terrainHeightBlending;", + "uniform float terrainTextureVariation;", + "uniform vec4 terrainCliffLayer;", + "uniform vec2 terrainCliffCosines;", + "uniform vec4 terrainBrush;", + "uniform sampler2D terrainColorMap;", + "uniform vec3 terrainWetShore;", + "varying vec2 vTerrainLocalXY;", + "varying float vTerrainLocalZ;", + "varying vec2 vTerrainWorldXY;", + "varying float vTerrainUp;", + "", + "float terrainHash(vec2 p) {", + " return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);", + "}", + "// The rotation of the copies of textures used to make repetition less visible.", + "const mat2 terrainCopyRotation = mat2(0.8, -0.6, 0.6, 0.8);", + "/** A bump of a normal map (from -1 to 1), mixed with its rotated copy in patches. */", + "vec3 terrainSampleNormal(sampler2D normalMap, vec2 uv, vec2 uv2, float patches) {", + " vec3 bump = texture2D(normalMap, uv).xyz * 2.0 - 1.0;", + " if (patches <= 0.0) return bump;", + " vec3 copyBump = texture2D(normalMap, uv2).xyz * 2.0 - 1.0;", + " // The bumps of the copy are rotated back with the inverse rotation.", + " copyBump.xy = transpose(terrainCopyRotation) * copyBump.xy;", + " return mix(bump, copyBump, patches);", + "}", + "float terrainNoise(vec2 p) {", + " vec2 i = floor(p);", + " vec2 f = fract(p);", + " vec2 u = f * f * (3.0 - 2.0 * f);", + " return mix(", + " mix(terrainHash(i), terrainHash(i + vec2(1.0, 0.0)), u.x),", + " mix(terrainHash(i + vec2(0.0, 1.0)), terrainHash(i + vec2(1.0, 1.0)), u.x),", + " u.y", + " );", + "}", + "`;", + "// Holes are samples without any painted layer: the interpolated sum of the", + "// weights gives smooth edges. Only compiled for terrains having holes, as", + "// discard makes drawing slower on some GPUs.", + "const holeShaderCode = `", + "#ifdef TERRAIN_HOLES", + "if (dot(texture2D(terrainSplatMap, vTerrainLocalXY * terrainSplatUvScaleAndOffset.x + terrainSplatUvScaleAndOffset.y), vec4(1.0)) < 0.5) discard;", + "#endif", + "`;", + "// For the shadows: the depth of holes is not drawn.", + "const holeDepthShaderDeclarations = `", + "uniform sampler2D terrainSplatMap;", + "uniform vec2 terrainSplatUvScaleAndOffset;", + "varying vec2 vTerrainLocalXY;", + "`;", + "", + "const fragmentShaderCode = holeShaderCode + `", + "// Borders between layers are made irregular and sharper, to look painted.", + "vec2 terrainNoisePosition = vTerrainWorldXY / 24.0;", + "vec4 terrainJitter = vec4(", + " terrainNoise(terrainNoisePosition),", + " terrainNoise(terrainNoisePosition + 19.1),", + " terrainNoise(terrainNoisePosition + 37.7),", + " terrainNoise(terrainNoisePosition + 53.3)", + ");", + "// Layer weights are in a texture, so that painted details stay the same", + "// whatever the level of detail of the ground.", + "vec2 terrainSplatUv = vTerrainLocalXY * terrainSplatUvScaleAndOffset.x + terrainSplatUvScaleAndOffset.y;", + "vec4 terrainWeights = texture2D(terrainSplatMap, terrainSplatUv) * (0.7 + 0.6 * terrainJitter);", + "terrainWeights = terrainWeights * terrainWeights;", + "terrainWeights /= max(dot(terrainWeights, vec4(1.0)), 0.00001);", + "float terrainCliff = 1.0 - smoothstep(terrainCliffCosines.x, terrainCliffCosines.y, vTerrainUp);", + "terrainWeights = mix(terrainWeights, terrainCliffLayer, terrainCliff * dot(terrainCliffLayer, vec4(1.0)));", + "", + "vec2 terrainUv = vTerrainWorldXY / terrainTextureSize;", + "vec3 terrainTexture0 = texture2D(terrainLayerMap0, terrainUv).rgb;", + "vec3 terrainTexture1 = texture2D(terrainLayerMap1, terrainUv).rgb;", + "vec3 terrainTexture2 = texture2D(terrainLayerMap2, terrainUv).rgb;", + "vec3 terrainTexture3 = texture2D(terrainLayerMap3, terrainUv).rgb;", + "", + "// Less visible repetition: in irregular patches, textures are replaced by a", + "// rotated and shifted copy of themselves, and colors slowly vary.", + "float terrainPatches = 0.0;", + "vec2 terrainUv2 = terrainUv;", + "float terrainColorVariation = 1.0;", + "if (terrainTextureVariation > 0.0) {", + " // Up to half of the ground is covered by the copies, each patch showing", + " // only one of them (mixing them would make them blurry).", + " float terrainPatchThreshold = 1.0 - 0.5 * terrainTextureVariation;", + " terrainPatches = smoothstep(terrainPatchThreshold - 0.05, terrainPatchThreshold + 0.05, terrainNoise(terrainUv * 0.23 + 7.3));", + " terrainUv2 = terrainCopyRotation * terrainUv + vec2(0.31, 0.73);", + " terrainTexture0 = mix(terrainTexture0, texture2D(terrainLayerMap0, terrainUv2).rgb, terrainPatches);", + " terrainTexture1 = mix(terrainTexture1, texture2D(terrainLayerMap1, terrainUv2).rgb, terrainPatches);", + " terrainTexture2 = mix(terrainTexture2, texture2D(terrainLayerMap2, terrainUv2).rgb, terrainPatches);", + " terrainTexture3 = mix(terrainTexture3, texture2D(terrainLayerMap3, terrainUv2).rgb, terrainPatches);", + " terrainColorVariation += terrainTextureVariation * 0.3 *", + " (terrainNoise(terrainUv * 0.11) + 0.5 * terrainNoise(terrainUv * 0.37 + 3.1) - 0.75);", + "}", + "", + "// Height-based blending: the brightness of a texture, compared to its", + "// average, is used as its height. Where layers meet, the high parts (stones,", + "// tufts) cover the low parts (sand, gaps) instead of fading into each other.", + "if (terrainHeightBlending > 0.0) {", + " const vec3 terrainLuminance = vec3(0.299, 0.587, 0.114);", + " vec4 terrainHeights = vec4(", + " dot(terrainTexture0 - textureLod(terrainLayerMap0, vec2(0.5), 16.0).rgb, terrainLuminance),", + " dot(terrainTexture1 - textureLod(terrainLayerMap1, vec2(0.5), 16.0).rgb, terrainLuminance),", + " dot(terrainTexture2 - textureLod(terrainLayerMap2, vec2(0.5), 16.0).rgb, terrainLuminance),", + " dot(terrainTexture3 - textureLod(terrainLayerMap3, vec2(0.5), 16.0).rgb, terrainLuminance)", + " );", + " vec4 terrainHeightWeights = terrainWeights + terrainHeights;", + " float terrainTop = max(max(terrainHeightWeights.x, terrainHeightWeights.y), max(terrainHeightWeights.z, terrainHeightWeights.w));", + " // Only painted layers (and the cliff layer) can show.", + " vec4 terrainHeightBlend = max(terrainHeightWeights - terrainTop + 0.1, 0.0) * step(0.004, terrainWeights);", + " terrainHeightBlend /= max(dot(terrainHeightBlend, vec4(1.0)), 0.00001);", + " terrainWeights = mix(terrainWeights, terrainHeightBlend, terrainHeightBlending);", + "}", + "", + "vec3 terrainColor = terrainColorVariation * (", + " terrainWeights.x * terrainLayerColors[0] * terrainTexture0 +", + " terrainWeights.y * terrainLayerColors[1] * terrainTexture1 +", + " terrainWeights.z * terrainLayerColors[2] * terrainTexture2 +", + " terrainWeights.w * terrainLayerColors[3] * terrainTexture3);", + "", + "// The painted tint multiplies all layers. Wet ground is darker (and", + "// shinier, see the roughness).", + "vec4 terrainTintAndWetness = texture2D(terrainColorMap, terrainSplatUv);", + "float terrainWetness = max(", + " terrainTintAndWetness.a,", + " terrainWetShore.z * (1.0 - smoothstep(terrainWetShore.x, terrainWetShore.x + terrainWetShore.y, vTerrainLocalZ))", + ");", + "terrainColor *= terrainTintAndWetness.rgb * (1.0 - 0.4 * terrainWetness);", + "diffuseColor.rgb *= terrainColor;", + "", + "if (terrainBrush.w > 0.5) {", + " float brushDistance = distance(vTerrainWorldXY, terrainBrush.xy);", + " float ringWidth = max(terrainBrush.z * 0.04, 1.5);", + " float ring = 1.0 - smoothstep(0.0, ringWidth, abs(brushDistance - terrainBrush.z));", + " float disc = brushDistance < terrainBrush.z ? 0.12 : 0.0;", + " diffuseColor.rgb = mix(diffuseColor.rgb, vec3(1.0), max(ring * 0.85, disc));", + "}", + "`;", + "", + "const fragmentShaderRoughnessCode = `", + "roughnessFactor = mix(roughnessFactor, 0.3, terrainWetness);", + "`;", + "", + "const fragmentShaderNormalCode = `", + "if (terrainHasNormalMaps > 0.5) {", + " vec3 terrainMapNormal =", + " terrainWeights.x * terrainSampleNormal(terrainLayerNormalMap0, terrainUv, terrainUv2, terrainPatches) +", + " terrainWeights.y * terrainSampleNormal(terrainLayerNormalMap1, terrainUv, terrainUv2, terrainPatches) +", + " terrainWeights.z * terrainSampleNormal(terrainLayerNormalMap2, terrainUv, terrainUv2, terrainPatches) +", + " terrainWeights.w * terrainSampleNormal(terrainLayerNormalMap3, terrainUv, terrainUv2, terrainPatches);", + " terrainMapNormal.xy *= terrainNormalMapStrength;", + " // The X and Y of textures follow the X and Y of the 3D world, projected on", + " // the ground (the camera space of the normal is used).", + " vec3 terrainTangent = (viewMatrix * vec4(1.0, 0.0, 0.0, 0.0)).xyz;", + " terrainTangent -= normal * dot(normal, terrainTangent);", + " // A ground facing the X axis (a rotated terrain) uses the Y axis instead.", + " if (dot(terrainTangent, terrainTangent) < 0.000001) {", + " terrainTangent = (viewMatrix * vec4(0.0, 1.0, 0.0, 0.0)).xyz;", + " terrainTangent -= normal * dot(normal, terrainTangent);", + " }", + " terrainTangent = normalize(terrainTangent);", + " vec3 terrainBitangent = cross(normal, terrainTangent);", + " normal = normalize(mat3(terrainTangent, terrainBitangent, normal) * terrainMapNormal);", + "}", + "`;", + "", + "let whiteTexture = null;", + "const getWhiteTexture = () => {", + " if (!whiteTexture) {", + " whiteTexture = new THREE.DataTexture(new Uint8Array([255, 255, 255, 255]), 1, 1);", + " whiteTexture.needsUpdate = true;", + " }", + " return whiteTexture;", + "};", + "", + "let flatNormalTexture = null;", + "const getFlatNormalTexture = () => {", + " if (!flatNormalTexture) {", + " flatNormalTexture = new THREE.DataTexture(new Uint8Array([128, 128, 255, 255]), 1, 1);", + " flatNormalTexture.needsUpdate = true;", + " }", + " return flatNormalTexture;", + "};", + "", + "const layerTextures = new WeakMap();", + "const layerNormalMaps = new WeakMap();", + "/**", + " * Textures of the image manager have no mipmaps: they would shimmer when", + " * repeated in the distance.", + " * @param {gdjs.RuntimeGame} game", + " * @param {string} resourceName", + " * @param {boolean} isNormalMap Normal maps are directions, not colors.", + " */", + "const getLayerTexture = (game, resourceName, isNormalMap) => {", + " if (!resourceName) return isNormalMap ? getFlatNormalTexture() : getWhiteTexture();", + " const imageTexture = game.getImageManager().getThreeTexture(resourceName);", + " const textures = isNormalMap ? layerNormalMaps : layerTextures;", + " let texture = textures.get(imageTexture);", + " if (!texture) {", + " texture = imageTexture.clone();", + " if (isNormalMap) texture.colorSpace = THREE.NoColorSpace;", + " texture.wrapS = THREE.RepeatWrapping;", + " texture.wrapT = THREE.RepeatWrapping;", + " texture.minFilter = THREE.LinearMipmapLinearFilter;", + " texture.generateMipmaps = true;", + " texture.anisotropy = 4;", + " texture.needsUpdate = true;", + " textures.set(imageTexture, texture);", + " }", + " return texture;", + "};", + "", + "/**", + " * A texture of 4 bytes per sample of a grid of `size * size` samples (layer", + " * weights, tint and wetness...), interpolated between samples.", + " * @param {Uint8Array} bytes", + " * @param {number} size", + " */", + "const createGridTexture = (bytes, size) => {", + " const texture = new THREE.DataTexture(bytes, size, size);", + " texture.magFilter = THREE.LinearFilter;", + " texture.minFilter = THREE.LinearFilter;", + " texture.userData.isUploaded = false;", + " texture.onUpdate = () => {", + " texture.userData.isUploaded = true;", + " };", + " return texture;", + "};", + "", + "/**", + " * Send the rows of the changed samples to the GPU, or the whole texture", + " * before its first upload (and in older GDevelop versions).", + " * @param {THREE.DataTexture} texture", + " * @param {SampleRectangle} samples", + " */", + "const uploadGridTextureRows = (texture, samples) => {", + " if (texture.userData.isUploaded && typeof texture.addUpdateRange === 'function') {", + " const size = texture.image.width;", + " for (let j = samples.minJ; j <= samples.maxJ; j++) {", + " texture.addUpdateRange((j * size + samples.minI) * 4, (samples.maxI - samples.minI + 1) * 4);", + " }", + " }", + " texture.needsUpdate = true;", + "};", + "", + "class TerrainRenderer {", + " /** @param {Terrain} terrain */", + " constructor(terrain) {", + " this.terrain = terrain;", + " this.group = new THREE.Group();", + " terrain.object.get3DRendererObject().add(this.group);", + " /** @type {Array<{mesh: THREE.Mesh, geometry: THREE.BufferGeometry, firstI: number, firstJ: number, center: THREE.Vector3, lod: number}>} */", + " this.chunks = [];", + " this.chunksPerSide = 0;", + " this.chunkCells = 0;", + " this.chunkSideVertices = 0;", + " this.chunkGridVertices = 0;", + " this.chunkVertices = 0;", + " /** @type {THREE.DataTexture | null} */", + " this.splatTexture = null;", + " /** @type {THREE.DataTexture | null} */", + " this.colorTexture = null;", + " this.uniforms = {", + " terrainSplatMap: { value: getWhiteTexture() },", + " terrainSplatUvScaleAndOffset: { value: new THREE.Vector2() },", + " terrainLayerColors: { value: [new THREE.Color(), new THREE.Color(), new THREE.Color(), new THREE.Color()] },", + " terrainLayerMap0: { value: getWhiteTexture() },", + " terrainLayerMap1: { value: getWhiteTexture() },", + " terrainLayerMap2: { value: getWhiteTexture() },", + " terrainLayerMap3: { value: getWhiteTexture() },", + " terrainLayerNormalMap0: { value: getFlatNormalTexture() },", + " terrainLayerNormalMap1: { value: getFlatNormalTexture() },", + " terrainLayerNormalMap2: { value: getFlatNormalTexture() },", + " terrainLayerNormalMap3: { value: getFlatNormalTexture() },", + " terrainHasNormalMaps: { value: 0 },", + " terrainNormalMapStrength: { value: 1 },", + " terrainTextureSize: { value: 256 },", + " terrainHeightBlending: { value: 0 },", + " terrainTextureVariation: { value: 0 },", + " terrainCliffLayer: { value: new THREE.Vector4() },", + " terrainCliffCosines: { value: new THREE.Vector2() },", + " terrainBrush: { value: new THREE.Vector4() },", + " terrainColorMap: { value: getWhiteTexture() },", + " // Z (in the terrain group) under which the ground is wet, fade height, and 1 if it's used.", + " terrainWetShore: { value: new THREE.Vector3() },", + " // Scene units by 3D world unit.", + " terrainWorldScale: { value: 1 },", + " };", + " this.material = new THREE.MeshStandardMaterial({ roughness: 1, metalness: 0 });", + " this.material.onBeforeCompile = (shader) => {", + " Object.assign(shader.uniforms, this.uniforms);", + " shader.vertexShader = patchShaderCode(shader.vertexShader, [", + " ['#include ', '#include \\n' + vertexShaderDeclarations],", + " ['#include ', '#include \\n' + vertexShaderCode],", + " ], 'Terrain3D');", + " shader.fragmentShader = patchShaderCode(shader.fragmentShader, [", + " ['#include ', '#include \\n' + fragmentShaderDeclarations],", + " ['#include ', fragmentShaderCode],", + " ['#include ', '#include \\n' + fragmentShaderRoughnessCode],", + " ['#include ', '#include \\n' + fragmentShaderNormalCode],", + " ], 'Terrain3D');", + " };", + " this.material.customProgramCacheKey = () => (this.hasHoles ? 'Terrain3DWithHoles' : 'Terrain3D');", + " this.hasHoles = false;", + " // Without them, holes would cast shadows.", + " this.depthMaterial = this._createHoleDepthMaterial(new THREE.MeshDepthMaterial());", + " this.distanceMaterial = this._createHoleDepthMaterial(new THREE.MeshDistanceMaterial());", + " this._cameraPosition = new THREE.Vector3();", + " this._chunkPosition = new THREE.Vector3();", + " this._scale = new THREE.Vector3();", + " }", + "", + " /**", + " * @param {THREE.MeshDepthMaterial | THREE.MeshDistanceMaterial} material", + " * @returns The material, drawing nothing in holes.", + " */", + " _createHoleDepthMaterial(material) {", + " material.defines = { TERRAIN_HOLES: '' };", + " material.onBeforeCompile = (shader) => {", + " Object.assign(shader.uniforms, this.uniforms);", + " shader.vertexShader = patchShaderCode(shader.vertexShader, [", + " ['#include ', '#include \\nvarying vec2 vTerrainLocalXY;'],", + " ['#include ', '#include \\nvTerrainLocalXY = position.xy;'],", + " ], 'Terrain3D shadows');", + " shader.fragmentShader = patchShaderCode(shader.fragmentShader, [", + " ['#include ', '#include \\n' + holeDepthShaderDeclarations],", + " ['#include ', holeShaderCode + '#include '],", + " ], 'Terrain3D shadows');", + " };", + " material.customProgramCacheKey = () => 'Terrain3DHoles';", + " return material;", + " }", + "", + " /**", + " * Use the shaders drawing holes, or the faster ones without them.", + " * @param {boolean} hasHoles", + " */", + " setHasHoles(hasHoles) {", + " if (hasHoles === this.hasHoles) return;", + " this.hasHoles = hasHoles;", + " // The material has its own defines (STANDARD).", + " if (hasHoles) this.material.defines.TERRAIN_HOLES = '';", + " else delete this.material.defines.TERRAIN_HOLES;", + " this.material.needsUpdate = true;", + " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", + " }", + "", + " /** @param {THREE.Mesh} mesh */", + " _setChunkShadowMaterials(mesh) {", + " mesh.customDepthMaterial = this.hasHoles ? this.depthMaterial : undefined;", + " mesh.customDistanceMaterial = this.hasHoles ? this.distanceMaterial : undefined;", + " }", + "", + " /** Recreate all the chunks, for example after the resolution changed. */", + " rebuild() {", + " this.disposeChunks();", + " const { data } = this.terrain;", + " this.chunkCells = getChunkCells(data.resolution);", + " this.chunkSideVertices = this.chunkCells + 1;", + " this.chunkGridVertices = this.chunkSideVertices * this.chunkSideVertices;", + " this.chunkVertices = this.chunkGridVertices + 4 * this.chunkSideVertices;", + " this.chunksPerSide = data.resolution / this.chunkCells;", + " const cellSize = AREA_SIZE / data.resolution;", + " // Sample centers are at the texel centers.", + " this.splatTexture = createGridTexture(data.splat, data.size);", + " this.uniforms.terrainSplatMap.value = this.splatTexture;", + " this.colorTexture = createGridTexture(data.colors, data.size);", + " // Tints are colors chosen like the layer colors.", + " this.colorTexture.colorSpace = THREE.SRGBColorSpace;", + " this.uniforms.terrainColorMap.value = this.colorTexture;", + " this.uniforms.terrainSplatUvScaleAndOffset.value.set(1 / (cellSize * data.size), 0.5 / data.size);", + " for (let chunkJ = 0; chunkJ < this.chunksPerSide; chunkJ++) {", + " for (let chunkI = 0; chunkI < this.chunksPerSide; chunkI++) {", + " const firstI = chunkI * this.chunkCells;", + " const firstJ = chunkJ * this.chunkCells;", + " const positions = new Float32Array(this.chunkVertices * 3);", + " for (let j = 0; j < this.chunkSideVertices; j++) {", + " for (let i = 0; i < this.chunkSideVertices; i++) {", + " const vertex = j * this.chunkSideVertices + i;", + " positions[vertex * 3] = (firstI + i) * cellSize;", + " positions[vertex * 3 + 1] = (firstJ + j) * cellSize;", + " }", + " }", + " // Skirt vertices are below border vertices.", + " this._forEachSkirtVertex((skirtVertex, borderVertex) => {", + " positions[skirtVertex * 3] = positions[borderVertex * 3];", + " positions[skirtVertex * 3 + 1] = positions[borderVertex * 3 + 1];", + " });", + " const geometry = new THREE.BufferGeometry();", + " geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));", + " geometry.setAttribute('normal', new THREE.BufferAttribute(new Float32Array(this.chunkVertices * 3), 3));", + " geometry.setIndex(getLodIndex(this.chunkCells, LOD_STEPS[0]));", + " geometry.boundingBox = new THREE.Box3();", + " geometry.boundingSphere = new THREE.Sphere();", + " const mesh = new THREE.Mesh(geometry, this.material);", + " mesh.matrixAutoUpdate = false;", + " // Rays hit the ground at its full resolution, whatever the level of", + " // detail drawn, and go through holes.", + " const chunkCells = this.chunkCells;", + " const terrainData = () => this.terrain.data;", + " const localPoint = new THREE.Vector3();", + " mesh.raycast = function (raycaster, intersects) {", + " const drawnIndex = geometry.index;", + " const firstIntersection = intersects.length;", + " geometry.setIndex(getLodIndex(chunkCells, LOD_STEPS[0]));", + " THREE.Mesh.prototype.raycast.call(this, raycaster, intersects);", + " geometry.setIndex(drawnIndex);", + " const data = terrainData();", + " const cellSize = AREA_SIZE / data.resolution;", + " for (let index = intersects.length - 1; index >= firstIntersection; index--) {", + " this.worldToLocal(localPoint.copy(intersects[index].point));", + " if (data.sampleSolidity(localPoint.x / cellSize, localPoint.y / cellSize) < 0.5) {", + " intersects.splice(index, 1);", + " }", + " }", + " };", + " this._setChunkShadowMaterials(mesh);", + " this.group.add(mesh);", + " this.chunks.push({", + " mesh,", + " geometry,", + " firstI,", + " firstJ,", + " center: new THREE.Vector3(", + " (firstI + this.chunkCells / 2) * cellSize,", + " (firstJ + this.chunkCells / 2) * cellSize,", + " 0", + " ),", + " lod: 0,", + " });", + " }", + " }", + " this.updateSamples(new SampleRectangle().set(0, 0, data.resolution, data.resolution), true);", + " this.updateMaterial();", + " }", + "", + " _forEachSkirtVertex(callback) {", + " for (let k = 0; k < this.chunkSideVertices; k++) {", + " callback(this.chunkGridVertices + k, k);", + " callback(this.chunkGridVertices + this.chunkSideVertices + k, this.chunkCells * this.chunkSideVertices + k);", + " callback(this.chunkGridVertices + 2 * this.chunkSideVertices + k, k * this.chunkSideVertices);", + " callback(", + " this.chunkGridVertices + 3 * this.chunkSideVertices + k,", + " k * this.chunkSideVertices + this.chunkCells", + " );", + " }", + " }", + "", + " /**", + " * Skirts go down only to the lowest point a coarser neighbor chunk can", + " * draw along the border, so that they can't be seen through holes.", + " * @param {Float32Array} positions", + " */", + " _updateSkirtHeights(positions) {", + " const coarsestStep = LOD_STEPS[LOD_STEPS.length - 1];", + " const borderVertices = [", + " (k) => k,", + " (k) => this.chunkCells * this.chunkSideVertices + k,", + " (k) => k * this.chunkSideVertices,", + " (k) => k * this.chunkSideVertices + this.chunkCells,", + " ];", + " borderVertices.forEach((borderVertex, border) => {", + " for (let k = 0; k < this.chunkSideVertices; k++) {", + " let minZ = Infinity;", + " const from = Math.max(k - coarsestStep, 0);", + " const to = Math.min(k + coarsestStep, this.chunkCells);", + " for (let other = from; other <= to; other++) {", + " minZ = Math.min(minZ, positions[borderVertex(other) * 3 + 2]);", + " }", + " const skirtVertex = this.chunkGridVertices + border * this.chunkSideVertices + k;", + " positions[skirtVertex * 3 + 2] = minZ;", + " }", + " });", + " }", + "", + " /**", + " * Update the vertices of the chunks sharing the given samples.", + " * @param {SampleRectangle} samples", + " * @param {boolean} hasPaintChanged", + " */", + " updateSamples(samples, hasPaintChanged) {", + " if (samples.isEmpty()) return;", + " const { data } = this.terrain;", + " const cellSize = AREA_SIZE / data.resolution;", + " const slopeFactor = AREA_DEPTH / cellSize;", + " // Normals of samples next to changed ones change too.", + " const minI = Math.max(samples.minI - 1, 0);", + " const minJ = Math.max(samples.minJ - 1, 0);", + " const maxI = Math.min(samples.maxI + 1, data.resolution);", + " const maxJ = Math.min(samples.maxJ + 1, data.resolution);", + " const firstChunkI = Math.max(Math.ceil(minI / this.chunkCells) - 1, 0);", + " const firstChunkJ = Math.max(Math.ceil(minJ / this.chunkCells) - 1, 0);", + " const lastChunkI = Math.min(Math.floor(maxI / this.chunkCells), this.chunksPerSide - 1);", + " const lastChunkJ = Math.min(Math.floor(maxJ / this.chunkCells), this.chunksPerSide - 1);", + "", + " for (let chunkJ = firstChunkJ; chunkJ <= lastChunkJ; chunkJ++) {", + " for (let chunkI = firstChunkI; chunkI <= lastChunkI; chunkI++) {", + " const chunk = this.chunks[chunkJ * this.chunksPerSide + chunkI];", + " const positions = chunk.geometry.attributes.position.array;", + " const normals = chunk.geometry.attributes.normal.array;", + " const fromI = Math.max(minI - chunk.firstI, 0);", + " const toI = Math.min(maxI - chunk.firstI, this.chunkCells);", + " const fromJ = Math.max(minJ - chunk.firstJ, 0);", + " const toJ = Math.min(maxJ - chunk.firstJ, this.chunkCells);", + " for (let j = fromJ; j <= toJ; j++) {", + " const gridJ = chunk.firstJ + j;", + " for (let i = fromI; i <= toI; i++) {", + " const gridI = chunk.firstI + i;", + " const vertex = j * this.chunkSideVertices + i;", + " const sample = gridJ * data.size + gridI;", + " positions[vertex * 3 + 2] = data.heights[sample] * AREA_DEPTH;", + "", + " const leftI = Math.max(gridI - 1, 0);", + " const rightI = Math.min(gridI + 1, data.resolution);", + " const upJ = Math.max(gridJ - 1, 0);", + " const downJ = Math.min(gridJ + 1, data.resolution);", + " const nx =", + " ((data.getHeight(leftI, gridJ) - data.getHeight(rightI, gridJ)) * slopeFactor) /", + " (rightI - leftI);", + " const ny =", + " ((data.getHeight(gridI, upJ) - data.getHeight(gridI, downJ)) * slopeFactor) /", + " (downJ - upJ);", + " const inverseLength = 1 / Math.sqrt(nx * nx + ny * ny + 1);", + " normals[vertex * 3] = nx * inverseLength;", + " normals[vertex * 3 + 1] = ny * inverseLength;", + " normals[vertex * 3 + 2] = inverseLength;", + " }", + " }", + " this._forEachSkirtVertex((skirtVertex, borderVertex) => {", + " for (let axis = 0; axis < 3; axis++) {", + " normals[skirtVertex * 3 + axis] = normals[borderVertex * 3 + axis];", + " }", + " });", + " this._updateSkirtHeights(positions);", + " let maxZ = 0;", + " for (let vertex = 0; vertex < this.chunkGridVertices; vertex++) {", + " maxZ = Math.max(maxZ, positions[vertex * 3 + 2]);", + " }", + " const chunkSize = this.chunkCells * cellSize;", + " chunk.geometry.boundingBox.min.set(chunk.firstI * cellSize, chunk.firstJ * cellSize, 0);", + " chunk.geometry.boundingBox.max.set(", + " chunk.firstI * cellSize + chunkSize,", + " chunk.firstJ * cellSize + chunkSize,", + " maxZ", + " );", + " chunk.geometry.boundingBox.getBoundingSphere(chunk.geometry.boundingSphere);", + " chunk.center.z = maxZ / 2;", + " chunk.geometry.attributes.position.needsUpdate = true;", + " chunk.geometry.attributes.normal.needsUpdate = true;", + " }", + " }", + " if (hasPaintChanged) {", + " // The data may have been replaced by one with the same resolution.", + " this.splatTexture.image.data = data.splat;", + " uploadGridTextureRows(this.splatTexture, samples);", + " this.colorTexture.image.data = data.colors;", + " uploadGridTextureRows(this.colorTexture, samples);", + " }", + " }", + "", + " updateMaterial() {", + " const { object } = this.terrain;", + " const game = object.getRuntimeScene().getGame();", + " const colors = [object._getLayer1Color(), object._getLayer2Color(), object._getLayer3Color(), object._getLayer4Color()];", + " const textures = [", + " object._getLayer1Texture(),", + " object._getLayer2Texture(),", + " object._getLayer3Texture(),", + " object._getLayer4Texture(),", + " ];", + " const normalMaps = [", + " object._getLayer1NormalMap(),", + " object._getLayer2NormalMap(),", + " object._getLayer3NormalMap(),", + " object._getLayer4NormalMap(),", + " ];", + " for (let layer = 0; layer < LAYER_COUNT; layer++) {", + " this.uniforms.terrainLayerColors.value[layer].set(gdjs.rgbOrHexStringToNumber(colors[layer]));", + " this.uniforms['terrainLayerMap' + layer].value = getLayerTexture(game, textures[layer], false);", + " this.uniforms['terrainLayerNormalMap' + layer].value = getLayerTexture(game, normalMaps[layer], true);", + " }", + " this.uniforms.terrainHasNormalMaps.value = normalMaps.some((normalMap) => !!normalMap) ? 1 : 0;", + " this.uniforms.terrainNormalMapStrength.value = Math.max(object._getNormalMapStrength(), 0);", + " this.uniforms.terrainTextureSize.value = Math.max(object._getTextureSize(), 1);", + " this.uniforms.terrainHeightBlending.value = clamp01(object._getHeightBlending());", + " this.uniforms.terrainTextureVariation.value = clamp01(object._getTextureVariation());", + "", + " const cliffLayer = parseInt(object._getCliffLayer(), 10);", + " const cliffAngle = gdjs.toRad(object._getCliffAngle());", + " const cliffBlendAngle = gdjs.toRad(4);", + " this.uniforms.terrainCliffLayer.value.set(", + " cliffLayer === 1 ? 1 : 0,", + " cliffLayer === 2 ? 1 : 0,", + " cliffLayer === 3 ? 1 : 0,", + " cliffLayer === 4 ? 1 : 0", + " );", + " this.uniforms.terrainCliffCosines.value.set(", + " Math.cos(cliffAngle + cliffBlendAngle),", + " Math.cos(cliffAngle - cliffBlendAngle)", + " );", + "", + " const castShadow = object._getCastShadow();", + " const receiveShadow = object._getReceiveShadow();", + " for (const chunk of this.chunks) {", + " chunk.mesh.castShadow = castShadow;", + " chunk.mesh.receiveShadow = receiveShadow;", + " }", + " }", + "", + " /**", + " * Show the brush circle of the editor.", + " * @param {THREE.Vector3 | null} localPosition", + " * @param {number} radius In scene units.", + " */", + " setBrush(localPosition, radius) {", + " const brush = this.uniforms.terrainBrush.value;", + " if (!localPosition) {", + " brush.w = 0;", + " return;", + " }", + " this.group.updateWorldMatrix(true, false);", + " const worldPosition = this._chunkPosition.copy(localPosition).applyMatrix4(this.group.matrixWorld);", + " const worldScale = this.uniforms.terrainWorldScale.value;", + " brush.set(worldPosition.x * worldScale, worldPosition.y * worldScale, radius, 1);", + " }", + "", + " /** @param {THREE.Camera | null} camera */", + " updateLevelsOfDetail(camera) {", + " if (!camera || !this.group.visible) return;", + " const matrixWorld = this.group.matrixWorld;", + " this._cameraPosition.setFromMatrixPosition(camera.matrixWorld);", + " this._scale.setFromMatrixScale(matrixWorld);", + " const cellSize = AREA_SIZE / this.terrain.data.resolution;", + " const chunkWorldSize = this.chunkCells * cellSize * Math.max(this._scale.x, this._scale.y);", + " for (const chunk of this.chunks) {", + " const distance =", + " this._chunkPosition.copy(chunk.center).applyMatrix4(matrixWorld).distanceTo(this._cameraPosition) /", + " chunkWorldSize;", + " let lod = LOD_DISTANCES.length - 1;", + " while (lod > 0 && distance < LOD_DISTANCES[lod]) lod--;", + " if (lod !== chunk.lod) {", + " chunk.lod = lod;", + " chunk.geometry.setIndex(getLodIndex(this.chunkCells, LOD_STEPS[lod]));", + " }", + " }", + " }", + "", + " disposeChunks() {", + " for (const chunk of this.chunks) {", + " chunk.mesh.removeFromParent();", + " // The index is shared by all chunks: it must stay on the GPU.", + " chunk.geometry.setIndex(null);", + " chunk.geometry.dispose();", + " }", + " this.chunks.length = 0;", + " if (this.splatTexture) this.splatTexture.dispose();", + " this.splatTexture = null;", + " if (this.colorTexture) this.colorTexture.dispose();", + " this.colorTexture = null;", + " }", + "", + " dispose() {", + " this.disposeChunks();", + " this.material.dispose();", + " this.depthMaterial.dispose();", + " this.distanceMaterial.dispose();", + " this.group.removeFromParent();", + " }", + "}", + "", + "/**", + " * Makes a Physics3D behavior use a height field shape following the terrain.", + " * Changes of heights are applied to the shape without recreating it.", + " * @implements {gdjs.Physics3DRuntimeBehavior.BodyUpdater}", + " */", + "class TerrainBodyUpdater {", + " /**", + " * @param {Terrain} terrain", + " * @param {gdjs.Physics3DRuntimeBehavior} physics", + " */", + " constructor(terrain, physics) {", + " this.terrain = terrain;", + " this.physics = physics;", + " this.defaultBodyUpdater = physics.bodyUpdater;", + " this.changedSamples = new SampleRectangle();", + " /** @type {Jolt.HeightFieldShape | null} */", + " this.heightField = null;", + " this.heightScale = 1;", + " this.heightsBufferPointer = 0;", + " this.heightsBufferLength = 0;", + " this.bodyX = 0;", + " this.bodyY = 0;", + " this.bodyZ = 0;", + " this.bodyHeight = 0;", + " physics.bodyType = 'Static';", + " physics.bodyUpdater = this;", + " }", + "", + " createShape() {", + " const { data, object } = this.terrain;", + " const worldInvScale = this.physics._sharedData.worldInvScale;", + " const { size } = data;", + " this.heightScale = object.getDepth() * worldInvScale;", + "", + " // Changes of heights (SetHeights) break collisions unless the sample", + " // count is a power of 2: extra samples have no collision.", + " const sampleCount = Math.pow(2, Math.ceil(Math.log2(size)));", + " const settings = new Jolt.HeightFieldShapeSettings();", + " settings.mScale.Set(", + " (object.getWidth() / data.resolution) * worldInvScale,", + " 1,", + " (object.getHeight() / data.resolution) * worldInvScale", + " );", + " settings.mSampleCount = sampleCount;", + " settings.mBlockSize = 4;", + " // Allows later changes of heights on the whole depth of the object.", + " settings.mMinHeightValue = 0;", + " settings.mMaxHeightValue = this.heightScale;", + " settings.mHeightSamples.resize(sampleCount * sampleCount);", + " const samples = new Float32Array(", + " Jolt.HEAPF32.buffer,", + " Jolt.getPointer(settings.mHeightSamples.data()),", + " sampleCount * sampleCount", + " );", + " const noCollision = Jolt.HeightFieldShapeConstantValues.prototype.cNoCollisionValue;", + " samples.fill(noCollision);", + " // Height fields go along X and Z: rows are flipped so that, once", + " // rotated to have heights along Z, rows go along Y.", + " for (let j = 0; j < size; j++) {", + " const row = (size - 1 - j) * sampleCount;", + " for (let i = 0; i < size; i++) {", + " samples[row + i] = this._getSampleHeight(j * size + i, noCollision);", + " }", + " }", + " const shape = settings.Create().Get();", + " Jolt.destroy(settings);", + " this.heightField = Jolt.castObject(shape, Jolt.HeightFieldShape);", + " this.changedSamples.clear();", + " return shape;", + " }", + "", + " /** @returns {number} The height of a sample in the shape, or no collision in a hole. */", + " _getSampleHeight(index, noCollision) {", + " const height = this.terrain.surfaceHeights[index];", + " return Number.isNaN(height) ? noCollision : height * this.heightScale;", + " }", + "", + " _getBodyPosition() {", + " const { object } = this.terrain;", + " const worldInvScale = this.physics._sharedData.worldInvScale;", + " this.bodyX = object.getX();", + " this.bodyY = object.getY();", + " this.bodyZ = object.getZ();", + " // The origin of the height field is at the bottom of the terrain (see createShape).", + " this.bodyHeight = object.getHeight();", + " return this.physics._sharedData.getRVec3(", + " object.getX() * worldInvScale,", + " (object.getY() + object.getHeight()) * worldInvScale,", + " object.getZ() * worldInvScale", + " );", + " }", + "", + " _getBodyRotation() {", + " // A quarter turn around X: heights go along Z.", + " return this.physics._sharedData.getQuat(Math.SQRT1_2, 0, 0, Math.SQRT1_2);", + " }", + "", + " createAndAddBody() {", + " const { physics } = this;", + " const { bodyInterface } = physics._sharedData;", + " const shape = this.createShape();", + " const settings = new Jolt.BodyCreationSettings(", + " shape,", + " this._getBodyPosition(),", + " this._getBodyRotation(),", + " Jolt.EMotionType_Static,", + " physics.getBodyLayer()", + " );", + " settings.mFriction = physics.friction;", + " settings.mRestitution = physics.restitution;", + " const body = bodyInterface.CreateBody(settings);", + " Jolt.destroy(settings);", + " bodyInterface.AddBody(body.GetID(), Jolt.EActivation_DontActivate);", + " return body;", + " }", + "", + " updateObjectFromBody() {", + " // The terrain is static: the body never moves it.", + " }", + "", + " updateBodyFromObject() {", + " const { physics } = this;", + " const body = physics._body;", + " if (!body) return;", + " const { object } = this.terrain;", + " const { bodyInterface } = physics._sharedData;", + " if (", + " object.getX() !== this.bodyX ||", + " object.getY() !== this.bodyY ||", + " object.getZ() !== this.bodyZ ||", + " object.getHeight() !== this.bodyHeight", + " ) {", + " bodyInterface.SetPositionAndRotation(", + " body.GetID(),", + " this._getBodyPosition(),", + " this._getBodyRotation(),", + " Jolt.EActivation_DontActivate", + " );", + " }", + " if (!this.changedSamples.isEmpty()) {", + " this._applyChangedHeights(body);", + " }", + " }", + "", + " _applyChangedHeights(body) {", + " const { data } = this.terrain;", + " const { size } = data;", + " const { minI, minJ, maxI, maxJ } = this.changedSamples;", + " // Jolt only changes blocks of samples. Rows are flipped and extra", + " // samples have no collision (see createShape).", + " const blockSize = this.heightField.GetBlockSize();", + " const sampleCount = this.heightField.GetSampleCount();", + " const alignDown = (value) => Math.floor(value / blockSize) * blockSize;", + " const alignUp = (value) => Math.min(Math.ceil(value / blockSize) * blockSize, sampleCount);", + " const firstX = alignDown(minI);", + " const firstRow = alignDown(size - 1 - maxJ);", + " const width = alignUp(maxI + 1) - firstX;", + " const height = alignUp(size - minJ) - firstRow;", + " if (width * height > this.heightsBufferLength) {", + " if (this.heightsBufferPointer) Jolt._webidl_free(this.heightsBufferPointer);", + " this.heightsBufferLength = width * height;", + " this.heightsBufferPointer = Jolt._webidl_malloc(this.heightsBufferLength * 4);", + " }", + " const heights = new Float32Array(Jolt.HEAPF32.buffer, this.heightsBufferPointer, width * height);", + " const noCollision = Jolt.HeightFieldShapeConstantValues.prototype.cNoCollisionValue;", + " for (let row = 0; row < height; row++) {", + " const j = size - 1 - (firstRow + row);", + " for (let column = 0; column < width; column++) {", + " const i = firstX + column;", + " heights[row * width + column] =", + " i < size && j >= 0 ? this._getSampleHeight(j * size + i, noCollision) : noCollision;", + " }", + " }", + " const sharedData = this.physics._sharedData;", + " this.heightField.SetHeights(", + " firstX,", + " firstRow,", + " width,", + " height,", + " this.heightsBufferPointer,", + " width,", + " sharedData.jolt.GetTempAllocator()", + " );", + " sharedData.bodyInterface.NotifyShapeChanged(", + " body.GetID(),", + " sharedData.getVec3(0, 0, 0),", + " false,", + " Jolt.EActivation_DontActivate", + " );", + " this._wakeUpBodiesOnChangedSamples();", + " this.changedSamples.clear();", + " }", + "", + " /** Bodies sleeping on the changed area would otherwise float or sink. */", + " _wakeUpBodiesOnChangedSamples() {", + " const { object, data } = this.terrain;", + " const worldInvScale = this.physics._sharedData.worldInvScale;", + " const cellWidth = object.getWidth() / data.resolution;", + " const cellHeight = object.getHeight() / data.resolution;", + " const { minI, minJ, maxI, maxJ } = this.changedSamples;", + " const min = new Jolt.Vec3(", + " (object.getX() + (minI - 1) * cellWidth) * worldInvScale,", + " (object.getY() + (minJ - 1) * cellHeight) * worldInvScale,", + " (object.getZ() - 1) * worldInvScale", + " );", + " const max = new Jolt.Vec3(", + " (object.getX() + (maxI + 1) * cellWidth) * worldInvScale,", + " (object.getY() + (maxJ + 1) * cellHeight) * worldInvScale,", + " (object.getZ() + object.getDepth() + 1) * worldInvScale", + " );", + " const box = new Jolt.AABox(min, max);", + " const broadPhaseLayerFilter = new Jolt.BroadPhaseLayerFilter();", + " const objectLayerFilter = new Jolt.ObjectLayerFilter();", + " this.physics._sharedData.bodyInterface.ActivateBodiesInAABox(box, broadPhaseLayerFilter, objectLayerFilter);", + " Jolt.destroy(min);", + " Jolt.destroy(max);", + " Jolt.destroy(box);", + " Jolt.destroy(broadPhaseLayerFilter);", + " Jolt.destroy(objectLayerFilter);", + " }", + "", + " recreateShape() {", + " const body = this.physics._body;", + " if (!body) return;", + " this.physics._sharedData.bodyInterface.SetShape(", + " body.GetID(),", + " this.createShape(),", + " false,", + " Jolt.EActivation_DontActivate", + " );", + " const { resolution } = this.terrain.data;", + " this.changedSamples.set(0, 0, resolution, resolution);", + " this._wakeUpBodiesOnChangedSamples();", + " this.changedSamples.clear();", + " }", + "", + " destroyBody() {", + " this.defaultBodyUpdater.destroyBody();", + " this.heightField = null;", + " }", + "", + " dispose() {", + " if (this.heightsBufferPointer) Jolt._webidl_free(this.heightsBufferPointer);", + " this.heightsBufferPointer = 0;", + " this.heightsBufferLength = 0;", + " }", + "}", + "", + "/** @param {TerrainData} data */", + "const copyTerrainData = (data) => {", + " const copy = new TerrainData(data.resolution);", + " copy.copyFrom(data);", + " return copy;", + "};", + "", + "// Generating a relief or reading an image is long for big terrains, and done", + "// for every instance and every hot-reload: the results are kept.", + "const baseDataCache = new Map();", + "const MAX_CACHED_BASE_DATA = 8;", + "// Changes of the shape of a terrain kept for consumers of its surface that", + "// missed some versions: older changes update the whole surface.", + "const MAX_SURFACE_CHANGES = 64;", + "// Unique among all terrains: a terrain replaced by another one never has its version.", + "let lastGroundVersion = 0;", + "", + "/**", + " * The terrain of a Terrain3D object: its data, how it's drawn and its", + " * surface (for collisions and navigation meshes). Positions are in the scene.", + " */", + "class Terrain {", + " /** @param {gdjs.CustomRuntimeObject3D} object */", + " constructor(object) {", + " this.object = object;", + " /** @type {TerrainData} */", + " this.data = new TerrainData(DEFAULT_RESOLUTION);", + " /** The data before any sculpting or painting, from the relief or heightmap. */", + " this.baseData = this.data;", + " this.renderer = new TerrainRenderer(this);", + " /** @type {TerrainBodyUpdater[]} */", + " this.bodyUpdaters = [];", + " this.changedSamples = new SampleRectangle();", + " this.hasPaintChanged = false;", + " /** Increased each time the ground changes, for objects following it (like grass). */", + " this.version = 0;", + " /** Increased each time the shape of the ground changes (not its paint or position). */", + " this.surfaceVersion = 0;", + " /**", + " * The last changes of the shape of the ground, for `getChangedArea`.", + " * @type {Array<{version: number, samples: SampleRectangle}>}", + " */", + " this.surfaceChanges = [];", + " /** The changes since this version are all in `surfaceChanges`. */", + " this.surfaceChangesStartVersion = 0;", + " this._stroke = { cellWidth: 1, cellHeight: 1, ax: 0, ay: 0, bx: 0, by: 0, radius: 0 };", + " this._strokeChangedSamples = new SampleRectangle();", + " /** The properties the data was loaded from, to reload it only if they change. */", + " this.loadedFrom = '';", + " /** The heightmap image being loaded: the relief is used until it is. */", + " this.loadingHeightmapImage = '';", + " this.isDisposed = false;", + " /** The last value of the sculpt data property read or saved. */", + " this.sculptData = '';", + " /**", + " * Values saved by the editor tools: the editor sends them back with a", + " * hot-reload, possibly after newer ones were saved.", + " * @type {string[]}", + " */", + " this.savedSculptData = [];", + " /** The heights of the surface: the heights of the data, and NaN in holes. */", + " this.surfaceHeights = new Float32Array(0);", + " this.loadFromProperties();", + " this._transform = this._getTransform();", + " getGrounds(object.getRuntimeScene()).add(this);", + " /**", + " * The shape of the ground, for the physics engine and navigation meshes.", + " * @type {gdjs.Surface}", + " */", + " this.surface = {", + " getVersion: () => this.surfaceVersion,", + " getChangedArea: (sinceVersion) => this.getChangedArea(sinceVersion),", + " getTriangles: () => null,", + " getHeightField: () => ({ columns: this.data.size, rows: this.data.size, heights: this.surfaceHeights }),", + " };", + " if (typeof object.setSurface === 'function') {", + " object.setSurface(this.surface);", + " } else if (gdjs.Physics3DRuntimeBehavior) {", + " // Older GDevelop versions don't use surfaces.", + " for (const behavior of object._behaviors) {", + " if (behavior instanceof gdjs.Physics3DRuntimeBehavior) {", + " this.bodyUpdaters.push(new TerrainBodyUpdater(this, behavior));", + " }", + " }", + " }", + " }", + "", + " _getSourceProperties() {", + " const { object } = this;", + " return [", + " object._getResolution(),", + " object._getRelief(),", + " object._getSeed(),", + " object._getHeightmapImage(),", + " object._getEdits(),", + " ].join('|');", + " }", + "", + " loadFromProperties() {", + " const { object } = this;", + " const resolution = parseInt(object._getResolution(), 10);", + " const validResolution = RESOLUTIONS.indexOf(resolution) !== -1 ? resolution : DEFAULT_RESOLUTION;", + " this.sculptData = object._getSculptData();", + " this.loadedFrom = this._getSourceProperties();", + "", + " this.baseData = this._getBaseData(validResolution);", + " let data = codec.decode(this.sculptData, (resolution) => copyTerrainData(this._getBaseData(resolution)));", + " if (data && data.resolution !== validResolution) {", + " data = data.resampled(validResolution);", + " }", + " this.setData(data || copyTerrainData(this.baseData));", + " }", + "", + " /**", + " * @param {number} resolution", + " * @returns {TerrainData} The data from the relief or the heightmap, shared: it must not be changed.", + " */", + " _getBaseData(resolution) {", + " const { object } = this;", + " const key = [resolution, object._getRelief(), object._getSeed(), object._getHeightmapImage(), object._getEdits()].join('|');", + " const cachedData = baseDataCache.get(key);", + " if (cachedData) return cachedData;", + "", + " const data = new TerrainData(resolution);", + " const heightmapImage = object._getHeightmapImage();", + " const isHeightmapLoaded =", + " !!heightmapImage && loadHeightsFromImage(data, object.getRuntimeScene().getGame(), heightmapImage);", + " if (!isHeightmapLoaded) {", + " relief.generate(data, object._getRelief(), object._getSeed());", + " if (heightmapImage) this._reloadOnceLoaded(heightmapImage);", + " }", + " edits.apply(data, object._getEdits());", + " // Not kept if the image is missing: it may be loaded later.", + " if (isHeightmapLoaded || !heightmapImage) baseDataCache.set(key, data);", + " if (baseDataCache.size > MAX_CACHED_BASE_DATA) {", + " baseDataCache.delete(baseDataCache.keys().next().value);", + " }", + " return data;", + " }", + "", + " /** @param {TerrainData} data */", + " setData(data) {", + " this.version = ++lastGroundVersion;", + " this.surfaceVersion = this.version;", + " this.surfaceChanges.length = 0;", + " this.surfaceChangesStartVersion = this.version;", + " const hasSameResolution = data.resolution === this.data.resolution && this.renderer.chunks.length > 0;", + " this.data = data;", + " const allSamples = new SampleRectangle().set(0, 0, data.resolution, data.resolution);", + " this.surfaceHeights = new Float32Array(data.heights.length);", + " this.renderer.setHasHoles(false);", + " this._updateSurfaceHeights(allSamples);", + " if (hasSameResolution) {", + " this.renderer.updateSamples(allSamples, true);", + " } else {", + " this.renderer.rebuild();", + " }", + " for (const bodyUpdater of this.bodyUpdaters) {", + " bodyUpdater.recreateShape();", + " }", + " }", + "", + " onHotReloading() {", + " const sculptData = this.object._getSculptData();", + " const savedIndex = this.savedSculptData.indexOf(sculptData);", + " if (savedIndex !== -1 && this._getSourceProperties() === this.loadedFrom) {", + " // The terrain already has this data, or newer changes.", + " this.savedSculptData.splice(0, savedIndex + 1);", + " } else if (sculptData !== this.sculptData || this._getSourceProperties() !== this.loadedFrom) {", + " this.savedSculptData.length = 0;", + " this.loadFromProperties();", + " }", + " this.renderer.updateMaterial();", + " }", + "", + " /** @returns {string} The data to save in the sculpt data property. */", + " save() {", + " this.sculptData = codec.encode(this.data, this.baseData);", + " this.savedSculptData.push(this.sculptData);", + " return this.sculptData;", + " }", + "", + " getCellWidth() {", + " return this.object.getWidth() / this.data.resolution;", + " }", + "", + " getCellHeight() {", + " return this.object.getHeight() / this.data.resolution;", + " }", + "", + " _toGridX(x) {", + " return (x - this.object.getX()) / this.getCellWidth();", + " }", + "", + " _toGridY(y) {", + " return (y - this.object.getY()) / this.getCellHeight();", + " }", + "", + " /** @returns {number} The Z position of the ground. */", + " getHeightAt(x, y) {", + " return this.object.getZ() + this.data.sampleHeight(this._toGridX(x), this._toGridY(y)) * this.object.getDepth();", + " }", + "", + " /** @returns {number} The angle of the ground with the horizontal, in degrees. */", + " getSlopeAt(x, y) {", + " const gridX = this._toGridX(x);", + " const gridY = this._toGridY(y);", + " const { data } = this;", + " const depth = this.object.getDepth();", + " const slopeX = ((data.sampleHeight(gridX + 0.5, gridY) - data.sampleHeight(gridX - 0.5, gridY)) * depth) / this.getCellWidth();", + " const slopeY = ((data.sampleHeight(gridX, gridY + 0.5) - data.sampleHeight(gridX, gridY - 0.5)) * depth) / this.getCellHeight();", + " return gdjs.toDegrees(Math.atan(Math.hypot(slopeX, slopeY)));", + " }", + "", + " /** @returns {number} The layer painted the most, from 1 to 4. */", + " getLayerAt(x, y) {", + " return this.data.getDominantLayer(this._toGridX(x), this._toGridY(y)) + 1;", + " }", + "", + " /**", + " * @param {number} layer From 1 to 4.", + " * @returns {number} How much the layer is painted, from 0 to 1.", + " */", + " getLayerWeightAt(x, y, layer) {", + " const layerIndex = clampInteger(layer - 1, 0, LAYER_COUNT - 1);", + " return this.data.sampleLayerWeight(this._toGridX(x), this._toGridY(y), layerIndex);", + " }", + "", + " /** @returns {number[]} The painted tint: red, green and blue, from 0 to 255 (white when there is none). */", + " getTintAt(x, y) {", + " const gridX = this._toGridX(x);", + " const gridY = this._toGridY(y);", + " return [0, 1, 2].map((channel) => Math.round(this.data.sampleColor(gridX, gridY, channel) * 255));", + " }", + "", + " /** @returns {number} How wet the ground is, from 0 (dry) to 1, painted or near the water (see the wet shore). */", + " getWetnessAt(x, y) {", + " const paintedWetness = this.data.sampleColor(this._toGridX(x), this._toGridY(y), 3);", + " const { object } = this;", + " if (!object._getWetShore()) return paintedWetness;", + " const fade = Math.max(object._getWetShoreFade(), 0.001);", + " const shoreWetness = clamp01((object._getWetShoreZ() + fade - this.getHeightAt(x, y)) / fade);", + " // Like the smoothstep of the shader.", + " return Math.max(paintedWetness, shoreWetness * shoreWetness * (3 - 2 * shoreWetness));", + " }", + "", + " _isInArea(x, y) {", + " const { object } = this;", + " return (", + " x >= object.getX() &&", + " x <= object.getX() + object.getWidth() &&", + " y >= object.getY() &&", + " y <= object.getY() + object.getHeight()", + " );", + " }", + "", + " /** @returns {boolean} true if the position is above or under the terrain, and not in a hole. */", + " containsPoint(x, y) {", + " return this._isInArea(x, y) && this.data.sampleSolidity(this._toGridX(x), this._toGridY(y)) >= 0.5;", + " }", + "", + " /** @returns {boolean} true if there is a hole in the terrain at the position, like what is drawn. */", + " isHoleAt(x, y) {", + " return this._isInArea(x, y) && this.data.sampleSolidity(this._toGridX(x), this._toGridY(y)) < 0.5;", + " }", + "", + " _getStroke(x1, y1, x2, y2, radius) {", + " const stroke = this._stroke;", + " stroke.cellWidth = this.getCellWidth();", + " stroke.cellHeight = this.getCellHeight();", + " stroke.ax = x1 - this.object.getX();", + " stroke.ay = y1 - this.object.getY();", + " stroke.bx = x2 - this.object.getX();", + " stroke.by = y2 - this.object.getY();", + " stroke.radius = Math.max(radius, 0);", + " return stroke;", + " }", + "", + " /**", + " * @param {SampleRectangle} samples", + " * @param {boolean} haveHeightsChanged false when only the paint changed.", + " */", + " _onSamplesChanged(samples, haveHeightsChanged) {", + " if (samples.isEmpty()) return;", + " this.version = ++lastGroundVersion;", + " this.changedSamples.add(samples);", + " // Painting changes the surface when it makes holes or fills them.", + " const hasSurfaceChanged = this._updateSurfaceHeights(samples);", + " if (!haveHeightsChanged && !hasSurfaceChanged) return;", + " this.surfaceVersion = this.version;", + " this.surfaceChanges.push({ version: this.version, samples: new SampleRectangle().set(samples.minI, samples.minJ, samples.maxI, samples.maxJ) });", + " if (this.surfaceChanges.length > MAX_SURFACE_CHANGES) {", + " this.surfaceChangesStartVersion = this.surfaceChanges.shift().version;", + " }", + " for (const bodyUpdater of this.bodyUpdaters) {", + " bodyUpdater.changedSamples.add(samples);", + " }", + " }", + "", + " /**", + " * @param {SampleRectangle} samples", + " * @returns {boolean} true if heights of the surface changed, or holes were made or filled.", + " */", + " _updateSurfaceHeights(samples) {", + " const { data, surfaceHeights } = this;", + " let hasChanged = false;", + " let hasHoles = false;", + " for (let j = samples.minJ; j <= samples.maxJ; j++) {", + " for (let i = samples.minI; i <= samples.maxI; i++) {", + " const index = j * data.size + i;", + " const isHole = data.isHole(index);", + " const height = isHole ? NaN : data.heights[index];", + " const previousHeight = surfaceHeights[index];", + " if (isHole) {", + " hasHoles = true;", + " if (Number.isNaN(previousHeight)) continue;", + " } else if (height === previousHeight) {", + " continue;", + " }", + " surfaceHeights[index] = height;", + " hasChanged = true;", + " }", + " }", + " // Filled holes keep the shaders drawing holes until the data is loaded again.", + " if (hasHoles) this.renderer.setHasHoles(true);", + " return hasChanged;", + " }", + "", + " /** @param {number} height In scene units, negative to lower the ground. */", + " raise(x1, y1, x2, y2, radius, height) {", + " const samples = this._strokeChangedSamples;", + " brushes.raise(this.data, this._getStroke(x1, y1, x2, y2, radius), height / this.object.getDepth(), samples);", + " this._onSamplesChanged(samples, true);", + " }", + "", + " /** @param {number} z The Z position to flatten the ground to. */", + " /** @param {number} [toZ] The Z position at the end of the segment, for a slope. The same as z by default. */", + " flatten(x1, y1, x2, y2, radius, z, strength, toZ = z) {", + " const samples = this._strokeChangedSamples;", + " const toHeight = (value) => (value - this.object.getZ()) / this.object.getDepth();", + " brushes.flatten(this.data, this._getStroke(x1, y1, x2, y2, radius), toHeight(z), strength, samples, toHeight(toZ));", + " this._onSamplesChanged(samples, true);", + " }", + "", + " smooth(x1, y1, x2, y2, radius, strength) {", + " const samples = this._strokeChangedSamples;", + " brushes.smooth(this.data, this._getStroke(x1, y1, x2, y2, radius), strength, samples);", + " this._onSamplesChanged(samples, true);", + " }", + "", + " /** @param {number} layer From 1 to 4. */", + " paint(x1, y1, x2, y2, radius, layer, strength) {", + " const layerIndex = clampInteger(layer - 1, 0, LAYER_COUNT - 1);", + " const samples = this._strokeChangedSamples;", + " brushes.paint(this.data, this._getStroke(x1, y1, x2, y2, radius), layerIndex, strength, samples);", + " this.hasPaintChanged = true;", + " this._onSamplesChanged(samples, false);", + " }", + "", + " /** @param {number[]} color The red, green and blue (0-255) of the tint: white removes it. */", + " tint(x1, y1, x2, y2, radius, color, strength) {", + " const samples = this._strokeChangedSamples;", + " brushes.tint(this.data, this._getStroke(x1, y1, x2, y2, radius), color, strength, samples);", + " this.hasPaintChanged = true;", + " this._onSamplesChanged(samples, false);", + " }", + "", + " /** @param {number} wetness From 0 (dry) to 1. */", + " wet(x1, y1, x2, y2, radius, wetness, strength) {", + " const samples = this._strokeChangedSamples;", + " brushes.wet(this.data, this._getStroke(x1, y1, x2, y2, radius), wetness, strength, samples);", + " this.hasPaintChanged = true;", + " this._onSamplesChanged(samples, false);", + " }", + "", + " /** Make a hole around a segment (`fillHoles` fills it again). */", + " makeHole(x1, y1, x2, y2, radius) {", + " const samples = this._strokeChangedSamples;", + " brushes.hole(this.data, this._getStroke(x1, y1, x2, y2, radius), samples);", + " this.hasPaintChanged = true;", + " this._onSamplesChanged(samples, false);", + " }", + "", + " /** Fill the holes around a segment with the layers of the ground around them. */", + " fillHoles(x1, y1, x2, y2, radius) {", + " const samples = this._strokeChangedSamples;", + " brushes.fill(this.data, this._getStroke(x1, y1, x2, y2, radius), samples);", + " this.hasPaintChanged = true;", + " this._onSamplesChanged(samples, false);", + " }", + "", + " /**", + " * Make the entrance of a tunnel or a cave: a hole, with the ground in", + " * front of it flattened to the floor of the tunnel.", + " * @param {number} angle The direction going into the ground, in degrees.", + " * @param {number} floorZ", + " * @param {number} length The length of the flat ground in front of the entrance.", + " */", + " carveEntrance(x, y, radius, angle, floorZ, length) {", + " const directionX = Math.cos(gdjs.toRad(angle));", + " const directionY = Math.sin(gdjs.toRad(angle));", + " this.flatten(x - directionX * length, y - directionY * length, x, y, radius * 1.5, floorZ, 1);", + " this.makeHole(x, y, x + directionX * radius, y + directionY * radius, radius);", + " }", + "", + " generate(reliefName, seed) {", + " this.baseData = new TerrainData(this.data.resolution);", + " relief.generate(this.baseData, reliefName, seed);", + " this.setData(copyTerrainData(this.baseData));", + " }", + "", + " loadHeightmap(imageResourceName) {", + " const baseData = new TerrainData(this.data.resolution);", + " if (loadHeightsFromImage(baseData, this.object.getRuntimeScene().getGame(), imageResourceName)) {", + " baseData.splat.set(this.data.splat);", + " baseData.colors.set(this.data.colors);", + " this.baseData = baseData;", + " this.setData(copyTerrainData(baseData));", + " }", + " }", + "", + " /** @param {gdjs.RuntimeObject[]} objects */", + " placeOnGround(objects) {", + " for (const object of objects) {", + " if (object === this.object || !gdjs.Base3DHandler.is3D(object)) continue;", + " const groundZ = this.getHeightAt(object.getCenterXInScene(), object.getCenterYInScene());", + " // The origin of the object is not always at its bottom.", + " object.setZ(groundZ + object.getZ() - object.getUnrotatedAABBMinZ());", + " }", + " }", + "", + " /**", + " * @returns {THREE.Vector3 | null} The point of the ground under the cursor,", + " * in the terrain group coordinates. Holes are hit too, so that brushes can fill them.", + " */", + " raycastFromCursor(game, raycaster, ndc) {", + " const layer = this.object.getInstanceContainer().getLayer(this.object.getLayer());", + " const camera = layer.getRenderer().getThreeCamera();", + " if (!camera) return null;", + " const inputManager = game.getInputManager();", + " ndc.set(", + " (inputManager.getCursorX() / game.getGameResolutionWidth()) * 2 - 1,", + " -(inputManager.getCursorY() / game.getGameResolutionHeight()) * 2 + 1", + " );", + " raycaster.setFromCamera(ndc, camera);", + " this.renderer.group.updateWorldMatrix(true, false);", + " const ray = raycaster.ray.clone().applyMatrix4(this.renderer.group.matrixWorld.clone().invert());", + " ray.direction.normalize();", + "", + " const box = new THREE.Box3(new THREE.Vector3(0, 0, 0), new THREE.Vector3(AREA_SIZE, AREA_SIZE, AREA_DEPTH));", + " const entry = box.containsPoint(ray.origin) ? ray.origin.clone() : ray.intersectBox(box, new THREE.Vector3());", + " if (!entry) return null;", + " const cellSize = AREA_SIZE / this.data.resolution;", + " const groundZAt = (point) => this.data.sampleHeight(point.x / cellSize, point.y / cellSize) * AREA_DEPTH;", + " const step = cellSize / 2;", + " const point = entry.clone();", + " const previousPoint = entry.clone();", + " for (let distance = 0; distance < AREA_SIZE * 2; distance += step) {", + " if (point.z <= groundZAt(point)) {", + " // Refine between the last point above the ground and this one.", + " for (let iteration = 0; iteration < 8; iteration++) {", + " const middle = previousPoint.clone().lerp(point, 0.5);", + " if (middle.z <= groundZAt(middle)) point.copy(middle);", + " else previousPoint.copy(middle);", + " }", + " return point;", + " }", + " if (!box.containsPoint(point)) return null;", + " previousPoint.copy(point);", + " point.addScaledVector(ray.direction, step);", + " }", + " return null;", + " }", + "", + " /**", + " * @param {THREE.Vector3} localPosition In the terrain group coordinates (like from `raycastFromCursor`).", + " * @returns {{x: number, y: number, z: number}} The position in the scene.", + " */", + " toScenePosition(localPosition) {", + " const { object } = this;", + " return {", + " x: object.getX() + (localPosition.x / AREA_SIZE) * object.getWidth(),", + " y: object.getY() + (localPosition.y / AREA_SIZE) * object.getHeight(),", + " z: object.getZ() + (localPosition.z / AREA_DEPTH) * object.getDepth(),", + " };", + " }", + "", + " /** Draw the changes of the ground (collisions are updated by the physics engine). */", + " applyChanges() {", + " this.renderer.updateSamples(this.changedSamples, this.hasPaintChanged);", + " this.changedSamples.clear();", + " this.hasPaintChanged = false;", + " }", + "", + " /** @returns {number} A number changing each time the ground changes (see `getGrounds`). */", + " getVersion() {", + " return this.version;", + " }", + "", + " /**", + " * @param {number} sinceVersion", + " * @returns {gdjs.SurfaceArea | null} The part of the ground whose shape changed since the version (see `gdjs.Surface`).", + " */", + " getChangedArea(sinceVersion) {", + " if (sinceVersion < this.surfaceChangesStartVersion) return null;", + " const samples = new SampleRectangle();", + " for (const change of this.surfaceChanges) {", + " if (change.version > sinceVersion) samples.add(change.samples);", + " }", + " if (samples.isEmpty()) return { minX: 0, minY: 0, maxX: 0, maxY: 0 };", + " const { resolution } = this.data;", + " return {", + " minX: samples.minI / resolution,", + " minY: samples.minJ / resolution,", + " maxX: samples.maxI / resolution,", + " maxY: samples.maxJ / resolution,", + " };", + " }", + "", + " _getTransform() {", + " const { object } = this;", + " return [object.getX(), object.getY(), object.getZ(), object.getWidth(), object.getHeight(), object.getDepth()].join(',');", + " }", + "", + " /** @param {string} imageResourceName A heightmap image that is not loaded yet. */", + " _reloadOnceLoaded(imageResourceName) {", + " if (this.loadingHeightmapImage === imageResourceName) return;", + " this.loadingHeightmapImage = imageResourceName;", + " const game = this.object.getRuntimeScene().getGame();", + " game.getImageManager()", + " .loadResource(imageResourceName)", + " .then(() => {", + " if (this.isDisposed || this.loadingHeightmapImage !== imageResourceName) return;", + " this.loadingHeightmapImage = '';", + " if (getLoadedImage(game, imageResourceName)) this.loadFromProperties();", + " })", + " .catch(() => {});", + " }", + "", + " update() {", + " const transform = this._getTransform();", + " if (transform !== this._transform) {", + " this._transform = transform;", + " this.version = ++lastGroundVersion;", + " }", + " this.applyChanges();", + " this.renderer.uniforms.terrainWorldScale.value = getWorldScale(this.object);", + " this._updateWetShore();", + " const layer = this.object.getInstanceContainer().getLayer(this.object.getLayer());", + " this.renderer.updateLevelsOfDetail(layer.getRenderer().getThreeCamera());", + " }", + "", + " _updateWetShore() {", + " const { object } = this;", + " // In the coordinates of the terrain group, like the vertices.", + " const depthScale = AREA_DEPTH / Math.max(object.getDepth(), 0.0001);", + " this.renderer.uniforms.terrainWetShore.value.set(", + " (object._getWetShoreZ() - object.getZ()) * depthScale,", + " Math.max(object._getWetShoreFade(), 0.001) * depthScale,", + " object._getWetShore() ? 1 : 0", + " );", + " }", + "", + " dispose() {", + " this.isDisposed = true;", + " getGrounds(this.object.getRuntimeScene()).delete(this);", + " if (typeof this.object.setSurface === 'function' && this.object.getSurface() === this.surface) {", + " this.object.setSurface(null);", + " }", + " this.renderer.dispose();", + " for (const bodyUpdater of this.bodyUpdaters) {", + " bodyUpdater.dispose();", + " }", + " }", + "}", + "", + "/**", + " * @param {gdjs.RuntimeObject} object", + " * @returns {number} The number of scene units by 3D world unit (1 in GDevelop versions before the world scale).", + " */", + "const getWorldScale = (object) => {", + " const scene = object.getRuntimeScene();", + " return typeof scene.getRenderer3DWorldScale === 'function' ? scene.getRenderer3DWorldScale() : 1;", + "};", + "", + "const averageColors = new WeakMap();", + "let white = null;", + "/**", + " * @param {THREE.Texture} texture", + " * @returns {THREE.Color} The average color of the texture (white when it's not loaded).", + " */", + "const getAverageColor = (texture) => {", + " const cachedColor = averageColors.get(texture);", + " if (cachedColor) return cachedColor;", + " const image = texture.image;", + " const isDrawable =", + " typeof document !== 'undefined' &&", + " ((image instanceof HTMLImageElement && image.complete && image.naturalWidth > 0) ||", + " image instanceof HTMLCanvasElement ||", + " (typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap));", + " if (!isDrawable) return white || (white = new THREE.Color(1, 1, 1));", + " const size = 8;", + " const canvas = document.createElement('canvas');", + " canvas.width = size;", + " canvas.height = size;", + " const context = canvas.getContext('2d');", + " context.drawImage(image, 0, 0, size, size);", + " const pixels = context.getImageData(0, 0, size, size).data;", + " let red = 0;", + " let green = 0;", + " let blue = 0;", + " for (let index = 0; index < pixels.length; index += 4) {", + " red += pixels[index];", + " green += pixels[index + 1];", + " blue += pixels[index + 2];", + " }", + " const count = size * size * 255;", + " const color = new THREE.Color().setRGB(red / count, green / count, blue / count, THREE.SRGBColorSpace);", + " averageColors.set(texture, color);", + " return color;", + "};", + "", + "const svgIcon = (content) =>", + " 'data:image/svg+xml,' +", + " encodeURIComponent('' + content + '');", + "const strokes = 'fill=\"none\" stroke=\"#000\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"';", + "const toolIcons = {", + " Raise: svgIcon(''),", + " Lower: svgIcon(''),", + " Smooth: svgIcon(''),", + " Flatten: svgIcon(''),", + " Size: svgIcon(''),", + " Strength: svgIcon(''),", + " Undo: svgIcon(''),", + " Hole: svgIcon(''),", + " FillHoles: svgIcon(''),", + " Wetness: svgIcon(''),", + "};", + "// Tints for the color brush when the editor has no color picker (older GDevelop versions).", + "const tintPresets = ['#e2c879', '#a5c45a', '#8a6a48', '#c98a62', '#9a9a92', '#ffffff'];", + "", + "/**", + " * The sculpt and paint tools shown in the scene editor when a terrain is", + " * selected. Without the needed editor functions (older GDevelop versions),", + " * nothing is shown: the terrain can still be changed with events.", + " */", + "class TerrainEditorTools {", + " constructor() {", + " /** '' when no brush is used. */", + " this.toolName = '';", + " this.sizePercent = 30;", + " this.strengthPercent = 50;", + " /** @type {Terrain | null} */", + " this.terrain = null;", + " /** @type {gdjs.InGameEditor | null} */", + " this.editor = null;", + " this.isStroking = false;", + " this.flattenHeight = 0;", + " /** The color of the \"Tint\" brush, as \"#rrggbb\". */", + " this.tintColor = tintPresets[0];", + " this.wetnessPercent = 100;", + " this.lastStrokeTime = 0;", + " /** @type {{data: TerrainData, baseData: TerrainData}[]} */", + " this.undoSteps = [];", + " this.raycaster = new THREE.Raycaster();", + " this.ndc = new THREE.Vector2();", + " this._swatchColor = new THREE.Color();", + " }", + "", + " /** @param {gdjs.InGameEditor} editor */", + " update(editor) {", + " if (typeof editor.showToolbar !== 'function') {", + " // Older GDevelop versions: the terrain can only be changed with events.", + " return;", + " }", + " this.editor = editor;", + " const selectedObjects = editor.getSelectedObjects();", + " const terrain =", + " selectedObjects.length === 1 && selectedObjects[0].__terrain3D ? selectedObjects[0].__terrain3D : null;", + " if (terrain !== this.terrain) {", + " this._endStroke();", + " if (this.terrain) this.terrain.renderer.setBrush(null, 0);", + " this.terrain = terrain;", + " this.undoSteps.length = 0;", + " }", + " if (terrain) editor.showToolbar('Terrain3D', this._getToolbarItems(terrain));", + " const game = editor.getRuntimeGame();", + " if (!terrain || !this.toolName) {", + " if (terrain) terrain.renderer.setBrush(null, 0);", + " return;", + " }", + "", + " const inputManager = game.getInputManager();", + " if (inputManager.wasKeyJustPressed(gdjs.evtTools.input.keysNameToCode.Escape)) {", + " this._endStroke();", + " this.toolName = '';", + " terrain.renderer.setBrush(null, 0);", + " return;", + " }", + " editor.captureLeftMouseButton();", + " const hit = terrain.raycastFromCursor(game, this.raycaster, this.ndc);", + " const radius = this._getRadius();", + " terrain.renderer.setBrush(hit, radius);", + "", + " // Older GDevelop versions don't have it.", + " const isPressedForTool =", + " typeof editor.isCapturedLeftMouseButtonPressed === 'function'", + " ? editor.isCapturedLeftMouseButtonPressed()", + " : inputManager.isMouseButtonPressed(0);", + " // The button is released outside the canvas when the mouse is on a toolbar.", + " const isPressed = isPressedForTool && inputManager.isMouseInsideCanvas();", + " if (isPressed && hit && !this.isStroking) {", + " this._startStroke(terrain, hit);", + " }", + " if (this.isStroking && isPressed && hit) {", + " this._applyBrush(terrain, hit, radius);", + " }", + " if (!isPressed) {", + " this._endStroke();", + " }", + " }", + "", + " _getRadius() {", + " const { object } = this.terrain;", + " const maxRadius = Math.max(object.getWidth(), object.getHeight()) / 4;", + " return Math.max(maxRadius * Math.pow(this.sizePercent / 100, 2), this.terrain.getCellWidth());", + " }", + "", + " _startStroke(terrain, hit) {", + " this.isStroking = true;", + " this.lastStrokeTime = performance.now();", + " this.flattenHeight = terrain.toScenePosition(hit).z;", + " this.undoSteps.push({ data: copyTerrainData(terrain.data), baseData: terrain.baseData });", + " const maxUndoSteps = Math.max(Math.floor(UNDO_STEPS_SAMPLES / terrain.data.heights.length), 2);", + " while (this.undoSteps.length > maxUndoSteps) this.undoSteps.shift();", + " }", + "", + " _endStroke() {", + " if (!this.isStroking) return;", + " this.isStroking = false;", + " this._save();", + " }", + "", + " _applyBrush(terrain, hit, radius) {", + " const now = performance.now();", + " const elapsedSeconds = Math.min((now - this.lastStrokeTime) / 1000, 0.1);", + " this.lastStrokeTime = now;", + " const { x, y } = terrain.toScenePosition(hit);", + " const amount = (this.strengthPercent / 100) * elapsedSeconds;", + " const depth = terrain.object.getDepth();", + " if (this.toolName === 'Raise') terrain.raise(x, y, x, y, radius, amount * depth * 0.5);", + " else if (this.toolName === 'Lower') terrain.raise(x, y, x, y, radius, -amount * depth * 0.5);", + " else if (this.toolName === 'Smooth') terrain.smooth(x, y, x, y, radius, amount * 10);", + " else if (this.toolName === 'Flatten') terrain.flatten(x, y, x, y, radius, this.flattenHeight, amount * 10);", + " else if (this.toolName.startsWith('Paint')) {", + " terrain.paint(x, y, x, y, radius, parseInt(this.toolName.slice(5), 10), amount * 8);", + " } else if (this.toolName === 'Hole') {", + " terrain.makeHole(x, y, x, y, radius);", + " } else if (this.toolName === 'FillHoles') {", + " terrain.fillHoles(x, y, x, y, radius);", + " } else if (this.toolName === 'Tint') {", + " terrain.tint(x, y, x, y, radius, gdjs.rgbOrHexToRGBColor(this.tintColor), amount * 8);", + " } else if (this.toolName === 'Wetness') {", + " terrain.wet(x, y, x, y, radius, this.wetnessPercent / 100, amount * 8);", + " }", + " // Drawn now, as the terrain was updated earlier in this frame.", + " terrain.applyChanges();", + " }", + "", + " _save() {", + " const { terrain, editor } = this;", + " if (!terrain || !editor) return;", + " editor.updateObjectProperties(terrain.object.getName(), { SculptData: terrain.save() });", + " }", + "", + " _undo() {", + " const undoStep = this.undoSteps.pop();", + " // The relief, the edits or the resolution may have changed since the stroke.", + " if (!this.terrain || !undoStep || undoStep.baseData !== this.terrain.baseData) {", + " this.undoSteps.length = 0;", + " return;", + " }", + " this.terrain.setData(undoStep.data);", + " this._save();", + " }", + "", + " /** @returns {Array} The settings of the tint or wetness brush, when it's used. */", + " _getColorToolItems() {", + " const { editor } = this;", + " if (this.toolName === 'Tint') {", + " if (editor && typeof editor.isToolbarItemTypeSupported === 'function' && editor.isToolbarItemTypeSupported('color')) {", + " return [", + " {", + " type: 'color',", + " id: 'TintColor',", + " tooltip: 'Color of the tint',", + " value: this.tintColor,", + " onChange: (color) => (this.tintColor = color),", + " },", + " ];", + " }", + " return tintPresets.map((color) => ({", + " type: 'button',", + " id: 'TintPreset' + color,", + " tooltip: color === '#ffffff' ? 'No tint' : 'Tint with this color',", + " color,", + " isActive: this.tintColor === color,", + " onClick: () => (this.tintColor = color),", + " }));", + " }", + " if (this.toolName === 'Wetness') {", + " return [", + " {", + " type: 'slider',", + " id: 'WetnessAmount',", + " tooltip: 'Wetness',", + " iconUrl: toolIcons.Wetness,", + " min: 0,", + " max: 100,", + " value: this.wetnessPercent,", + " onChange: (value) => (this.wetnessPercent = value),", + " },", + " ];", + " }", + " return [];", + " }", + "", + " /**", + " * @param {Terrain} terrain", + " * @returns {Array} The toolbar for the current state (the editor only", + " * updates what changed).", + " */", + " _getToolbarItems(terrain) {", + " const { object } = terrain;", + " const toolButton = (toolName, tooltip, color) => ({", + " type: 'button',", + " id: toolName,", + " tooltip,", + " iconUrl: toolIcons[toolName],", + " color,", + " isActive: this.toolName === toolName,", + " onClick: () => (this.toolName = this.toolName === toolName ? '' : toolName),", + " });", + " const layerColors = [object._getLayer1Color(), object._getLayer2Color(), object._getLayer3Color(), object._getLayer4Color()];", + " // Layers are shown with their color, tinting their texture.", + " const swatchColors = layerColors.map((color, index) =>", + " this._swatchColor", + " .set(gdjs.rgbOrHexStringToNumber(color))", + " .multiply(getAverageColor(terrain.renderer.uniforms['terrainLayerMap' + index].value))", + " .getHexString()", + " );", + " return [", + " toolButton('Raise', 'Raise the ground (drag on the terrain)'),", + " toolButton('Lower', 'Lower the ground'),", + " toolButton('Smooth', 'Smooth the ground'),", + " toolButton('Flatten', 'Flatten the ground to the height where the drag starts'),", + " { type: 'divider', id: 'PaintDivider' },", + " ...swatchColors.map((swatchColor, index) =>", + " toolButton('Paint' + (index + 1), 'Paint layer ' + (index + 1), '#' + swatchColor)", + " ),", + " toolButton('Hole', 'Make a hole, for the entrance of a tunnel or a cave'),", + " toolButton('FillHoles', 'Fill holes, with the layers of the ground around them'),", + " { type: 'divider', id: 'ColorDivider' },", + " toolButton('Tint', 'Tint the ground (white removes the tint)', this.tintColor),", + " toolButton('Wetness', 'Make the ground wet, or dry with a wetness of 0'),", + " ...this._getColorToolItems(),", + " { type: 'divider', id: 'BrushDivider' },", + " {", + " type: 'slider',", + " id: 'Size',", + " tooltip: 'Brush size',", + " iconUrl: toolIcons.Size,", + " min: 1,", + " max: 100,", + " value: this.sizePercent,", + " onChange: (value) => (this.sizePercent = value),", + " },", + " {", + " type: 'slider',", + " id: 'Strength',", + " tooltip: 'Brush strength',", + " iconUrl: toolIcons.Strength,", + " min: 5,", + " max: 100,", + " value: this.strengthPercent,", + " onChange: (value) => (this.strengthPercent = value),", + " },", + " { type: 'button', id: 'Undo', tooltip: 'Undo the last stroke on this terrain', iconUrl: toolIcons.Undo, onClick: () => this._undo() },", + " ];", + " }", + "}", + "", + "const editorTools = new TerrainEditorTools();", + "if (gdjs.registerInGameEditorPostStepCallback) {", + " gdjs.registerInGameEditorPostStepCallback((editor) => editorTools.update(editor));", + "}", + "", + "gdjs.__terrain3DExtension = {", + " Terrain,", + " // Used by the terrain grass and the scattered models.", + " getWorldScale,", + " getGrounds,", + " patchShaderCode,", + " createRandom,", + " findHighestGround,", + " GroundPlacement,", + " svgIcon,", + " toolIcons,", + " // Exposed for tests.", + " TerrainBodyUpdater,", + " TerrainData,", + " SampleRectangle,", + " brushes,", + " edits,", + " codec,", + " relief,", + "};" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [], + "description": "Define helper classes JavaScript code.", + "private": true + }, + { + "fullName": "Define grass helper classes", + "functionType": "Action", + "name": "DefineGrassHelperClasses", + "sentence": "Define grass helper classes JavaScript code", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (gdjs.__terrainGrassExtension) {", + " return;", + "}", + "// Blades grow on the grounds of the scene: the 3D terrains (see `getGrounds`", + "// in the terrain helper).", + "", + "// Size of the object inner area: instances are scaled from it.", + "const AREA_SIZE = 1000;", + "const AREA_DEPTH = 40;", + "const CHUNK_SIZE = 500;", + "const MAX_BENDING_OBJECTS = 8;", + "", + "/**", + " * A blade: a thin, tapered strip. Its width and height are 1: blades are", + " * scaled by the shader.", + " */", + "const createBladeGeometry = () => {", + " const halfWidths = [0.5, 0.42, 0.28, 0];", + " const heights = [0, 0.35, 0.7, 1];", + " const positions = [];", + " halfWidths.forEach((halfWidth, level) => {", + " positions.push(-halfWidth, 0, heights[level]);", + " if (halfWidth > 0) positions.push(halfWidth, 0, heights[level]);", + " });", + " const indices = [0, 1, 2, 1, 3, 2, 2, 3, 4, 3, 5, 4, 4, 5, 6];", + " const geometry = new THREE.InstancedBufferGeometry();", + " geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));", + " geometry.setAttribute('normal', new THREE.Float32BufferAttribute(new Array(positions.length).fill(0), 3));", + " geometry.setIndex(indices);", + " return geometry;", + "};", + "", + "const vertexShaderDeclarations = `", + "attribute vec4 grassRoot;", + "attribute vec2 grassShape;", + "// The tint of the ground at the root (linear, from 0 to 1).", + "attribute vec3 grassGroundTint;", + "uniform mat3 grassWorldToLocal;", + "uniform float grassWorldScale;", + "uniform float grassBladeWidth;", + "uniform float grassTime;", + "uniform float grassWindStrength;", + "uniform float grassWindSpeed;", + "uniform vec4 grassBenders[${MAX_BENDING_OBJECTS}];", + "uniform vec3 grassBaseColor;", + "uniform vec3 grassTipColor;", + "varying vec3 vGrassColor;", + "`;", + "const vertexShaderPosition = `", + "// Blades are shaped in scene units (with Y flipped like the 3D world), then", + "// moved back in the (scaled) object space.", + "float bladeHeight = position.z;", + "float grassAngle = grassRoot.w;", + "float bladeWidth = position.x * grassBladeWidth;", + "vec3 blade = vec3(", + " bladeWidth * cos(grassAngle),", + " bladeWidth * sin(grassAngle),", + " bladeHeight * grassShape.x", + ");", + "vec3 rootWorld = (modelMatrix * vec4(grassRoot.xyz, 1.0)).xyz * grassWorldScale;", + "// Tips bend more than the base.", + "float bendFactor = bladeHeight * bladeHeight * grassShape.x;", + "float gust = sin(grassTime * grassWindSpeed + rootWorld.x * 0.013 + rootWorld.y * 0.011);", + "float flutter = sin(grassTime * grassWindSpeed * 2.7 + grassShape.y * 40.0) * 0.3;", + "blade.xy += vec2(0.8, 0.5) * (gust + flutter + 0.6) * grassWindStrength * 0.35 * bendFactor;", + "for (int i = 0; i < ${MAX_BENDING_OBJECTS}; i++) {", + " vec4 bender = grassBenders[i];", + " if (bender.z <= 0.0) continue;", + " vec2 away = rootWorld.xy - bender.xy;", + " float distanceToBender = length(away);", + " float push = 1.0 - smoothstep(bender.z * 0.3, bender.z, distanceToBender);", + " if (push > 0.0) {", + " blade.xy += normalize(away + vec2(0.001)) * push * bendFactor * 0.9;", + " blade.z *= 1.0 - push * 0.5;", + " }", + "}", + "vec3 transformed = grassRoot.xyz + grassWorldToLocal * (blade / grassWorldScale);", + "vGrassColor = mix(grassBaseColor, grassTipColor, bladeHeight) * (0.85 + 0.3 * grassShape.y) * grassGroundTint;", + "`;", + "const vertexShaderNormal = `", + "// Blades are lit like the ground below them.", + "transformedNormal = normalize((viewMatrix * vec4(0.0, 0.0, 1.0, 0.0)).xyz);", + "`;", + "const fragmentShaderDeclarations = `", + "varying vec3 vGrassColor;", + "`;", + "const fragmentShaderColor = `", + "diffuseColor.rgb *= vGrassColor;", + "`;", + "// Both faces of blades are lit like the ground (not flipped for back faces).", + "const fragmentShaderNormal = `", + "#include ", + "normal = normalize(vNormal);", + "nonPerturbedNormal = normal;", + "`;", + "", + "const { getWorldScale, patchShaderCode, createRandom, findHighestGround, GroundPlacement } = gdjs.__terrain3DExtension;", + "", + "/**", + " * The grass of a terrain grass object: blades drawn with GPU instancing, in chunks", + " * that are hidden when off-screen and thinned out with the distance. They grow", + " * on the grounds below, like 3D terrains (only where a given layer is painted).", + " */", + "class Grass {", + " /** @param {gdjs.CustomRuntimeObject3D} object */", + " constructor(object) {", + " this.object = object;", + " this.group = new THREE.Group();", + " object.get3DRendererObject().add(this.group);", + " this.bladeGeometry = createBladeGeometry();", + " /** @type {Array<{mesh: THREE.Mesh, blades: number, center: THREE.Vector3}>} */", + " this.chunks = [];", + " this.placement = new GroundPlacement(object.getRuntimeScene());", + " this._groundAtBlade = { ground: null, z: 0 };", + " this.uniforms = {", + " grassWorldToLocal: { value: new THREE.Matrix3() },", + " // Scene units by 3D world unit.", + " grassWorldScale: { value: 1 },", + " grassBladeWidth: { value: 1 },", + " grassTime: { value: 0 },", + " grassWindStrength: { value: 1 },", + " grassWindSpeed: { value: 1 },", + " grassBenders: { value: Array.from({ length: MAX_BENDING_OBJECTS }, () => new THREE.Vector4()) },", + " grassBaseColor: { value: new THREE.Color() },", + " grassTipColor: { value: new THREE.Color() },", + " };", + " this.material = new THREE.MeshStandardMaterial({ roughness: 1, metalness: 0, side: THREE.DoubleSide });", + " this.material.onBeforeCompile = (shader) => {", + " Object.assign(shader.uniforms, this.uniforms);", + " shader.vertexShader = patchShaderCode(shader.vertexShader, [", + " ['#include ', '#include \\n' + vertexShaderDeclarations],", + " ['#include ', vertexShaderPosition],", + " ['#include ', '#include \\n' + vertexShaderNormal],", + " ], 'TerrainGrass');", + " shader.fragmentShader = patchShaderCode(shader.fragmentShader, [", + " ['#include ', '#include \\n' + fragmentShaderDeclarations],", + " ['#include ', '#include \\n' + fragmentShaderColor],", + " ['#include ', fragmentShaderNormal],", + " ], 'TerrainGrass');", + " };", + " this.material.customProgramCacheKey = () => 'TerrainGrass';", + "", + " this._matrix = new THREE.Matrix4();", + " this._cameraPosition = new THREE.Vector3();", + " this._chunkPosition = new THREE.Vector3();", + " this._scale = new THREE.Vector3();", + " this.updateFromProperties();", + " }", + "", + " updateFromProperties() {", + " const { object } = this;", + " this.uniforms.grassBaseColor.value.set(gdjs.rgbOrHexStringToNumber(object._getBaseColor()));", + " this.uniforms.grassTipColor.value.set(gdjs.rgbOrHexStringToNumber(object._getTipColor()));", + " this.uniforms.grassWindStrength.value = object._getWindStrength();", + " this.uniforms.grassWindSpeed.value = object._getWindSpeed();", + " this.uniforms.grassBladeWidth.value = Math.max(object._getBladeWidth(), 0);", + " this.material.needsUpdate = true;", + " this.placement.placeAgain();", + " }", + "", + " _getPlacementKey() {", + " const { object } = this;", + " return [", + " object.getX(), object.getY(), object.getZ(), object.getWidth(), object.getHeight(), object.getDepth(),", + " object._getDensity(), object._getBladeHeight(), object._getGroundLayer(), object._getMaxSlope(),", + " object._getSeed(),", + " ].join('|');", + " }", + "", + " /** Place the blades again, for example after the ground or the object changed. */", + " rebuild() {", + " const grounds = Array.from(this.placement.grounds);", + " for (const chunk of this.chunks) {", + " chunk.mesh.removeFromParent();", + " chunk.mesh.geometry.dispose();", + " }", + " this.chunks.length = 0;", + "", + " const { object } = this;", + " const width = object.getWidth();", + " const height = object.getHeight();", + " const scaleX = width / AREA_SIZE;", + " const scaleY = height / AREA_SIZE;", + " const scaleZ = Math.max(object.getDepth() / AREA_DEPTH, 0.0001);", + " const bladesPerChunk = Math.round((Math.max(object._getDensity(), 0) * CHUNK_SIZE * CHUNK_SIZE) / 10000);", + " const groundLayer = parseInt(object._getGroundLayer(), 10) || 0;", + " const maxSlope = object._getMaxSlope();", + " const chunksX = Math.max(Math.ceil(width / CHUNK_SIZE), 1);", + " const chunksY = Math.max(Math.ceil(height / CHUNK_SIZE), 1);", + " const nextRandom = createRandom(object._getSeed() * 7919 + 1);", + " const bladeHeight = Math.max(object._getBladeHeight(), 0);", + "", + " for (let chunkY = 0; chunkY < chunksY; chunkY++) {", + " for (let chunkX = 0; chunkX < chunksX; chunkX++) {", + " const roots = new Float32Array(bladesPerChunk * 4);", + " const shapes = new Float32Array(bladesPerChunk * 2);", + " const groundTints = new Uint8Array(bladesPerChunk * 3).fill(255);", + " let blades = 0;", + " let minZ = Infinity;", + " let maxZ = -Infinity;", + " for (let attempt = 0; attempt < bladesPerChunk; attempt++) {", + " // In scene units, relative to the object.", + " const x = (chunkX + nextRandom()) * CHUNK_SIZE;", + " const y = (chunkY + nextRandom()) * CHUNK_SIZE;", + " const angle = nextRandom() * Math.PI * 2;", + " const heightFactor = 0.6 + 0.8 * nextRandom();", + " const tint = nextRandom();", + " const growth = nextRandom();", + " if (x > width || y > height) continue;", + " const sceneX = object.getX() + x;", + " const sceneY = object.getY() + y;", + " const { ground, z } = findHighestGround(grounds, sceneX, sceneY, this._groundAtBlade);", + " const groundZ = ground ? z : object.getZ();", + " if (ground) {", + " if (groundLayer > 0 && growth > ground.getLayerWeightAt(sceneX, sceneY, groundLayer)) continue;", + " if (ground.getSlopeAt(sceneX, sceneY) > maxSlope) continue;", + " if (typeof ground.getTintAt === 'function') {", + " const tint = ground.getTintAt(sceneX, sceneY);", + " for (let channel = 0; channel < 3; channel++) {", + " groundTints[blades * 3 + channel] = Math.round(255 * Math.pow(tint[channel] / 255, 2.2));", + " }", + " }", + " }", + " const localZ = (groundZ - object.getZ()) / scaleZ;", + " roots[blades * 4] = x / scaleX;", + " roots[blades * 4 + 1] = y / scaleY;", + " roots[blades * 4 + 2] = localZ;", + " roots[blades * 4 + 3] = angle;", + " shapes[blades * 2] = heightFactor * bladeHeight;", + " shapes[blades * 2 + 1] = tint;", + " minZ = Math.min(minZ, localZ);", + " maxZ = Math.max(maxZ, localZ);", + " blades++;", + " }", + " if (blades === 0) continue;", + "", + " const geometry = new THREE.InstancedBufferGeometry();", + " geometry.index = this.bladeGeometry.index;", + " geometry.setAttribute('position', this.bladeGeometry.attributes.position);", + " geometry.setAttribute('normal', this.bladeGeometry.attributes.normal);", + " geometry.setAttribute('grassRoot', new THREE.InstancedBufferAttribute(roots.subarray(0, blades * 4), 4));", + " geometry.setAttribute('grassShape', new THREE.InstancedBufferAttribute(shapes.subarray(0, blades * 2), 2));", + " geometry.setAttribute('grassGroundTint', new THREE.InstancedBufferAttribute(groundTints.subarray(0, blades * 3), 3, true));", + " geometry.instanceCount = blades;", + " // The furthest tips can go with the wind and the bending objects (see the shader).", + " const maxBladeHeight = bladeHeight * 1.4;", + " const lean =", + " (0.35 * 1.9 * 0.943 * Math.max(object._getWindStrength(), 0) + 0.9) * maxBladeHeight +", + " Math.max(object._getBladeWidth(), 0) / 2;", + " const leanX = lean / scaleX;", + " const leanY = lean / scaleY;", + " geometry.boundingBox = new THREE.Box3(", + " new THREE.Vector3((chunkX * CHUNK_SIZE) / scaleX - leanX, (chunkY * CHUNK_SIZE) / scaleY - leanY, minZ),", + " new THREE.Vector3(", + " Math.min((chunkX + 1) * CHUNK_SIZE, width) / scaleX + leanX,", + " Math.min((chunkY + 1) * CHUNK_SIZE, height) / scaleY + leanY,", + " maxZ + (bladeHeight * 1.4) / scaleZ", + " )", + " );", + " geometry.boundingSphere = geometry.boundingBox.getBoundingSphere(new THREE.Sphere());", + " const mesh = new THREE.Mesh(geometry, this.material);", + " mesh.receiveShadow = true;", + " // Raycasts would only test one blade: the selection plane is used instead.", + " mesh.raycast = () => {};", + " this.group.add(mesh);", + " this.chunks.push({ mesh, blades, center: geometry.boundingSphere.center.clone() });", + " }", + " }", + " }", + "", + " _updateBendingObjects() {", + " const benders = this.uniforms.grassBenders.value;", + " const objectName = this.object._getBendingObject();", + " const bendingObjects = objectName ? this.object.getInstanceContainer().getObjects(objectName) || [] : [];", + " for (let index = 0; index < MAX_BENDING_OBJECTS; index++) {", + " const bendingObject = bendingObjects[index];", + " if (!bendingObject) {", + " benders[index].set(0, 0, 0, 0);", + " continue;", + " }", + " // Blades are pushed in the 3D scene, which is flipped on Y.", + " const radius = Math.max(bendingObject.getWidth(), bendingObject.getHeight()) * 1.2;", + " benders[index].set(bendingObject.getCenterXInScene(), -bendingObject.getCenterYInScene(), radius, 0);", + " }", + " }", + "", + " /**", + " * @param {number} elapsedSeconds", + " * @param {number} time In seconds, the same for all grass objects.", + " */", + " update(elapsedSeconds, time, camera) {", + " if (this.placement.shouldPlaceAgain(elapsedSeconds, this._getPlacementKey())) {", + " this.rebuild();", + " }", + "", + " this.uniforms.grassTime.value = time;", + " this.uniforms.grassWorldScale.value = getWorldScale(this.object);", + " // The object transformation is applied just before rendering, it's needed now.", + " this.object.getRenderer().ensureUpToDate();", + " this.group.updateWorldMatrix(true, false);", + " this.uniforms.grassWorldToLocal.value.setFromMatrix4(this._matrix.copy(this.group.matrixWorld).invert());", + " this._updateBendingObjects();", + " if (!camera) return;", + "", + " // Far chunks are hidden, and thinned out before (in 3D world units).", + " const fadeDistance = Math.max(this.object._getFadeDistance(), 1) / getWorldScale(this.object);", + " this._cameraPosition.setFromMatrixPosition(camera.matrixWorld);", + " for (const chunk of this.chunks) {", + " const distance = this._chunkPosition", + " .copy(chunk.center)", + " .applyMatrix4(this.group.matrixWorld)", + " .distanceTo(this._cameraPosition);", + " const density = gdjs.evtTools.common.clamp((fadeDistance - distance) / (fadeDistance * 0.5), 0, 1);", + " chunk.mesh.visible = density > 0;", + " chunk.mesh.geometry.instanceCount = Math.ceil(chunk.blades * density);", + " }", + " }", + "", + " dispose() {", + " for (const chunk of this.chunks) {", + " chunk.mesh.geometry.dispose();", + " }", + " this.bladeGeometry.dispose();", + " this.material.dispose();", + " this.group.removeFromParent();", + " }", + "}", + "", + "gdjs.__terrainGrassExtension = { Grass };" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [], + "description": "Define grass helper classes JavaScript code.", + "private": true + }, + { + "fullName": "Define scattered models helper classes", + "functionType": "Action", + "name": "DefineScatterHelperClasses", + "sentence": "Define scattered models helper classes JavaScript code", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (gdjs.__terrainScatterExtension) {", + " return;", + "}", + "// Copies grow on the grounds of the scene, like the terrain grass (see", + "// `getGrounds` in the terrain helper).", + "", + "const {", + " getWorldScale,", + " getGrounds,", + " patchShaderCode,", + " createRandom,", + " findHighestGround,", + " GroundPlacement,", + " SampleRectangle,", + " brushes,", + " codec,", + " svgIcon,", + " toolIcons,", + "} = gdjs.__terrain3DExtension;", + "", + "// Size of the object inner area: instances are scaled from it.", + "const AREA_SIZE = 2000;", + "const AREA_DEPTH = 200;", + "// The painted density is stored in a grid of this many cells on each side,", + "// whatever the size of the object.", + "const MASK_RESOLUTION = 256;", + "const MASK_FORMAT_VERSION = '1';", + "// Copies are hidden in chunks: big chunks mean fewer draw calls, small ones", + "// hide more copies out of the screen.", + "const MIN_CHUNK_SIZE = 500;", + "const MAX_CHUNK_SIZE = 2000;", + "// Copies shrink to nothing over this part of the draw distance, instead of popping out.", + "const FADE_PART = 0.15;", + "const MAX_COPIES = 200000;", + "// Prisms of the trunk collisions.", + "const TRUNK_SIDES = 6;", + "", + "const clamp01 = (value) => (value < 0 ? 0 : value > 1 ? 1 : value);", + "", + "/**", + " * @returns {number} A number for a cell of the copies grid, changing with the seed.", + " */", + "const hashCell = (seed, i, j) => {", + " let h = Math.imul(i, 374761393) + Math.imul(j, 668265263) + Math.imul(seed, 982451653);", + " h = Math.imul(h ^ (h >>> 13), 1274126177);", + " return (h ^ (h >>> 16)) >>> 0;", + "};", + "", + "/**", + " * How dense copies are, from 0 to 1, in a grid covering the object: painted", + " * with the editor brushes, the edits or events.", + " */", + "class DensityMask {", + " /** @param {number} fillValue From 0 to 255. */", + " constructor(fillValue) {", + " // `resolution` and `size` let terrain brushes paint it.", + " this.resolution = MASK_RESOLUTION;", + " this.size = MASK_RESOLUTION + 1;", + " this.values = new Uint8Array(this.size * this.size).fill(fillValue);", + " }", + "", + " /**", + " * @param {number} u From 0 to 1, along the object width.", + " * @param {number} v From 0 to 1, along the object height.", + " * @returns {number} The density, from 0 to 1, interpolated between samples.", + " */", + " sample(u, v) {", + " const x = clamp01(u) * this.resolution;", + " const y = clamp01(v) * this.resolution;", + " const i = Math.min(Math.floor(x), this.resolution - 1);", + " const j = Math.min(Math.floor(y), this.resolution - 1);", + " const fx = x - i;", + " const fy = y - j;", + " const value = (i, j) => this.values[j * this.size + i];", + " const top = value(i, j) + fx * (value(i + 1, j) - value(i, j));", + " const bottom = value(i, j + 1) + fx * (value(i + 1, j + 1) - value(i, j + 1));", + " return (top + fy * (bottom - top)) / 255;", + " }", + "", + " /**", + " * Bring the density towards a target around a segment: fully up to 70%", + " * of the radius, then fading out (forests have dense centers and soft edges).", + " * @param {{cellWidth: number, cellHeight: number, ax: number, ay: number, bx: number, by: number, radius: number}} stroke", + " * @param {number} density The target, from 0 to 1.", + " * @param {number} strength From 0 to 1.", + " * @param {SampleRectangle} changedSamples", + " */", + " paint(stroke, density, strength, changedSamples) {", + " const { values } = this;", + " const target = Math.round(clamp01(density) * 255);", + " brushes.forEachSample(", + " this,", + " stroke,", + " (index, falloff) => {", + " // The falloff of terrain brushes is (1 - distance² / radius²)².", + " const weight = clamp01(2 * Math.sqrt(falloff));", + " // Rounded towards the target, so that light strokes still paint.", + " const change = (target - values[index]) * clamp01(weight * strength);", + " values[index] += change > 0 ? Math.ceil(change) : Math.floor(change);", + " },", + " changedSamples", + " );", + " }", + "", + " /** @returns {DensityMask} */", + " clone() {", + " const mask = new DensityMask(0);", + " mask.values.set(this.values);", + " return mask;", + " }", + "}", + "", + "/**", + " * Edits written in the Edits property: a JSON list of brush strokes, like", + " * `[{\"tool\": \"add\", \"x\": 0.5, \"y\": 0.5, \"radius\": 0.2}]`. Positions and", + " * radius are fractions of the object size, like the terrain edits.", + " */", + "const applyEdits = (mask, text) => {", + " if (!text.trim()) return;", + " let editList;", + " try {", + " editList = JSON.parse(text);", + " } catch (error) {", + " console.warn('The edits of scattered models are not valid JSON: ' + error.message);", + " return;", + " }", + " if (!Array.isArray(editList)) {", + " console.warn('The edits of scattered models must be a JSON list (like [{\"tool\": \"add\", ...}]).');", + " return;", + " }", + " const numberOr = (value, defaultValue) => (typeof value === 'number' && isFinite(value) ? value : defaultValue);", + " const changedSamples = new SampleRectangle();", + " for (const edit of editList) {", + " if (!edit || typeof edit !== 'object') continue;", + " const x = numberOr(edit.x, 0.5);", + " const y = numberOr(edit.y, 0.5);", + " const stroke = {", + " cellWidth: 1 / mask.resolution,", + " cellHeight: 1 / mask.resolution,", + " ax: x,", + " ay: y,", + " bx: numberOr(edit.toX, x),", + " by: numberOr(edit.toY, y),", + " radius: numberOr(edit.radius, 0.1),", + " };", + " const strength = clamp01(numberOr(edit.strength, 1));", + " if (edit.tool === 'add') {", + " mask.paint(stroke, numberOr(edit.density, 1), strength, changedSamples);", + " } else if (edit.tool === 'erase') {", + " mask.paint(stroke, 0, strength, changedSamples);", + " } else {", + " console.warn('Unknown tool in the edits of scattered models: \"' + edit.tool + '\" (use add or erase).');", + " }", + " }", + "};", + "", + "/**", + " * The density painted in the editor, stored as its difference with the", + " * density from the properties (the coverage and the edits), like the terrain", + " * sculpt data.", + " */", + "const maskCodec = {", + " /**", + " * @param {DensityMask} mask", + " * @param {DensityMask} baseMask", + " */", + " encode(mask, baseMask) {", + " const changes = new Int32Array(mask.values.length);", + " let hasChanges = false;", + " for (let index = 0; index < changes.length; index++) {", + " changes[index] = mask.values[index] - baseMask.values[index];", + " if (changes[index] !== 0) hasChanges = true;", + " }", + " if (!hasChanges) return '';", + " return MASK_FORMAT_VERSION + ';' + MASK_RESOLUTION + ';' + codec.encodeGrids([changes], mask.size);", + " },", + "", + " /**", + " * @param {string} text", + " * @param {DensityMask} baseMask", + " * @returns {DensityMask | null} null if the text is empty or not valid.", + " */", + " decode(text, baseMask) {", + " if (!text) return null;", + " const parts = text.split(';');", + " if (parts[0] !== MASK_FORMAT_VERSION || parseInt(parts[1], 10) !== MASK_RESOLUTION) return null;", + " try {", + " const mask = baseMask.clone();", + " const [changes] = codec.decodeGrids(parts[2], mask.size, 1);", + " for (let index = 0; index < changes.length; index++) {", + " // The base may have changed since the data was saved.", + " mask.values[index] = Math.min(Math.max(mask.values[index] + changes[index], 0), 255);", + " }", + " return mask;", + " } catch (error) {", + " console.error('The painted data of scattered models could not be read:', error);", + " return null;", + " }", + " },", + "};", + "", + "const fadeShaderDeclarations = `", + "uniform float scatterDrawDistance;", + "`;", + "// Instance matrices are applied after this: copies shrink towards their base.", + "const fadeShaderCode = `", + "#ifdef USE_INSTANCING", + "vec3 scatterCopyPosition = (modelMatrix * instanceMatrix * vec4(0.0, 0.0, 0.0, 1.0)).xyz;", + "transformed *= clamp(", + " (scatterDrawDistance - distance(scatterCopyPosition, cameraPosition)) / (scatterDrawDistance * ${FADE_PART}),", + " 0.0,", + " 1.0", + ");", + "#endif", + "`;", + "", + "const flipTriangles = (geometry) => {", + " const index = geometry.index;", + " for (let offset = 0; offset + 2 < index.count; offset += 3) {", + " const second = index.getX(offset + 1);", + " index.setX(offset + 1, index.getX(offset + 2));", + " index.setX(offset + 2, second);", + " }", + "};", + "", + "/**", + " * The parts of a model, ready to be drawn with instancing: each glTF", + " * primitive with its material. Geometries are moved so that the model base", + " * is at the origin, its height is 1 and it stands along Z (like 3D model", + " * objects with a 90° rotation around X).", + " * @param {THREE.Object3D} modelScene", + " * @param {object} uniforms Uniforms added to the materials.", + " * @returns {{parts: Array<{geometry: THREE.BufferGeometry, material: THREE.Material | THREE.Material[]}>, width: number, depth: number}}", + " */", + "const createModelParts = (modelScene, uniforms) => {", + " modelScene.updateMatrixWorld(true);", + " const rootInverse = modelScene.matrixWorld.clone().invert();", + " const standUp = new THREE.Matrix4().makeRotationX(Math.PI / 2);", + " /** @type {Array<{mesh: THREE.Mesh, matrix: THREE.Matrix4}>} */", + " const meshes = [];", + " const box = new THREE.Box3();", + " modelScene.traverse((node) => {", + " if (!node.isMesh || !node.geometry || !node.geometry.attributes.position) return;", + " const matrix = standUp.clone().multiply(rootInverse).multiply(node.matrixWorld);", + " if (!node.geometry.boundingBox) node.geometry.computeBoundingBox();", + " box.union(node.geometry.boundingBox.clone().applyMatrix4(matrix));", + " meshes.push({ mesh: node, matrix });", + " });", + " if (box.isEmpty()) return { parts: [], width: 0, depth: 0 };", + "", + " const height = Math.max(box.max.z - box.min.z, 0.000001);", + " // The Y axis of the scene goes down: the model is flipped, like 3D model objects.", + " const normalization = new THREE.Matrix4()", + " .makeScale(1 / height, -1 / height, 1 / height)", + " .multiply(", + " new THREE.Matrix4().makeTranslation(", + " -(box.min.x + box.max.x) / 2,", + " -(box.min.y + box.max.y) / 2,", + " -box.min.z", + " )", + " );", + " const parts = meshes.map(({ mesh, matrix }) => {", + " let geometry = mesh.geometry.clone();", + " if (!geometry.index) {", + " const indices = Array.from({ length: geometry.attributes.position.count }, (_, index) => index);", + " geometry.setIndex(indices);", + " }", + " geometry.applyMatrix4(normalization.clone().multiply(matrix));", + " // The flip turned the faces inside out.", + " flipTriangles(geometry);", + " geometry.computeBoundingSphere();", + " geometry.computeBoundingBox();", + " const patchMaterial = (originalMaterial) => {", + " const material = originalMaterial.clone();", + " // Like 3D model objects (\"Standard without metalness\"), which look better without an environment map.", + " if (material.metalness) material.metalness = 0;", + " material.onBeforeCompile = (shader) => {", + " Object.assign(shader.uniforms, uniforms);", + " shader.vertexShader = patchShaderCode(shader.vertexShader, [", + " ['#include ', '#include \\n' + fadeShaderDeclarations],", + " ['#include ', '#include \\n' + fadeShaderCode],", + " ], 'ScatteredModels');", + " };", + " material.customProgramCacheKey = () => 'ScatteredModels';", + " return material;", + " };", + " const material = Array.isArray(mesh.material) ? mesh.material.map(patchMaterial) : patchMaterial(mesh.material);", + " return { geometry, material };", + " });", + " return {", + " parts,", + " width: (box.max.x - box.min.x) / height,", + " depth: (box.max.y - box.min.y) / height,", + " };", + "};", + "", + "/**", + " * A copy of the model: its base position in the scene, its scale (the", + " * height of the copy) and the key of its cell in the grid of copies.", + " * @typedef {{x: number, y: number, z: number, angle: number, height: number, key: number}} Copy", + " */", + "", + "/**", + " * The scattered models of an object: copies of a 3D model placed on the", + " * grounds of the scene, drawn with GPU instancing in chunks that are hidden", + " * when far or off-screen.", + " */", + "class Scatter {", + " /** @param {gdjs.CustomRuntimeObject3D} object */", + " constructor(object) {", + " this.object = object;", + " this.group = new THREE.Group();", + " object.get3DRendererObject().add(this.group);", + " /** @type {Array<{firstCellI: number, firstCellJ: number, meshes: THREE.InstancedMesh[], copies: Copy[], center: THREE.Vector3, radius: number}>} */", + " this.chunks = [];", + " /**", + " * The grid of copies: one copy at most in each cell, cells grouped in chunks.", + " * @type {{spacing: number, cellsPerChunk: number, cellsX: number, cellsY: number} | null}", + " */", + " this.grid = null;", + " this.placement = new GroundPlacement(object.getRuntimeScene());", + " this.uniforms = {", + " // In 3D world units.", + " scatterDrawDistance: { value: 1 },", + " };", + " /** @type {{parts: Array<{geometry: THREE.BufferGeometry, material: THREE.Material | THREE.Material[]}>, width: number, depth: number}} */", + " this.model = { parts: [], width: 0, depth: 0 };", + " this.modelResourceName = '';", + " this.loadingModelResourceName = '';", + " /**", + " * Copies removed by events (for example, cut trees), by key.", + " * @type {Map}", + " */", + " this.removedCopies = new Map();", + " /** Increased each time copies change, for the collisions. */", + " this.version = 0;", + " this.isDisposed = false;", + " /** The last value of the painted data property read or saved. */", + " this.paintData = '';", + " /**", + " * Values saved by the editor tools: the editor sends them back with a", + " * hot-reload, possibly after newer ones were saved.", + " * @type {string[]}", + " */", + " this.savedPaintData = [];", + " this.maskSource = '';", + " /** @type {DensityMask} */", + " this.baseMask = new DensityMask(255);", + " /** @type {DensityMask} */", + " this.mask = this.baseMask;", + " this.changedSamples = new SampleRectangle();", + " this._stroke = { cellWidth: 1, cellHeight: 1, ax: 0, ay: 0, bx: 0, by: 0, radius: 0 };", + " this._groundAtCopy = { ground: null, z: 0 };", + " this._matrix = new THREE.Matrix4();", + " this._raycastSphere = new THREE.Sphere();", + " this._raycastRay = new THREE.Ray();", + " this._raycastBox = new THREE.Box3();", + " this._raycastInverseMatrix = new THREE.Matrix4();", + " this._raycastScale = new THREE.Vector3();", + " /** Placed on a copy to test the triangles of the model. */", + " this._raycastMesh = new THREE.Mesh();", + " /** @type {THREE.Intersection[]} */", + " this._raycastHits = [];", + " this._color = new THREE.Color();", + " this._tint = new THREE.Color();", + " this._cameraPosition = new THREE.Vector3();", + " this._chunkPosition = new THREE.Vector3();", + " /**", + " * Trunks, for the 3D physics behavior and navigation meshes. Always given,", + " * empty without trunk collisions: otherwise these behaviors would use the", + " * object box, covering the whole area.", + " * @type {gdjs.Surface}", + " */", + " this.surface = {", + " getVersion: () => this.version,", + " getChangedArea: () => null,", + " getTriangles: () => this._getTrunkTriangles(),", + " getHeightField: () => null,", + " };", + " /** The trunk collision properties, changing the surface. */", + " this.trunkKey = '';", + " this.loadMaskFromProperties();", + " this.updateFromProperties();", + " // Before the behaviors create their shapes.", + " this._updateSurface();", + " }", + "", + " _getMaskSource() {", + " return this.object._getCoverage() + '|' + this.object._getEdits();", + " }", + "", + " loadMaskFromProperties() {", + " const { object } = this;", + " this.maskSource = this._getMaskSource();", + " this.paintData = object._getPaintData();", + " this.baseMask = new DensityMask(object._getCoverage() === 'Painted' ? 0 : 255);", + " applyEdits(this.baseMask, object._getEdits());", + " this.mask = maskCodec.decode(this.paintData, this.baseMask) || this.baseMask.clone();", + " this.placement.placeAgain();", + " }", + "", + " onHotReloading() {", + " const paintData = this.object._getPaintData();", + " const savedIndex = this.savedPaintData.indexOf(paintData);", + " if (savedIndex !== -1 && this._getMaskSource() === this.maskSource) {", + " // The object already has this data, or newer changes.", + " this.savedPaintData.splice(0, savedIndex + 1);", + " } else if (paintData !== this.paintData || this._getMaskSource() !== this.maskSource) {", + " this.savedPaintData.length = 0;", + " this.loadMaskFromProperties();", + " }", + " this.updateFromProperties();", + " }", + "", + " /** @returns {string} The data to save in the painted data property. */", + " save() {", + " this.paintData = maskCodec.encode(this.mask, this.baseMask);", + " this.savedPaintData.push(this.paintData);", + " return this.paintData;", + " }", + "", + " updateFromProperties() {", + " const { object } = this;", + " const modelResourceName = object._getModel();", + " if (modelResourceName !== this.modelResourceName) {", + " this.modelResourceName = modelResourceName;", + " this._loadModel();", + " }", + " this.placement.placeAgain();", + " }", + "", + " _disposeModel() {", + " for (const part of this.model.parts) {", + " part.geometry.dispose();", + " for (const material of Array.isArray(part.material) ? part.material : [part.material]) {", + " material.dispose();", + " }", + " }", + " this.model = { parts: [], width: 0, depth: 0 };", + " }", + "", + " _loadModel() {", + " this._disposeChunks(this.chunks);", + " this._disposeModel();", + " const resourceName = this.modelResourceName;", + " if (!resourceName) return;", + " const game = this.object.getRuntimeScene().getGame();", + " const modelManager = game.getModel3DManager();", + " const gltf = modelManager.getModel(resourceName);", + " if (gltf === modelManager._invalidModel) {", + " // Not loaded yet, for example a model just chosen in the editor.", + " if (this.loadingModelResourceName === resourceName) return;", + " this.loadingModelResourceName = resourceName;", + " game.getResourceLoader()", + " .loadResource(resourceName)", + " .then(() => {", + " if (this.isDisposed || this.modelResourceName !== resourceName) return;", + " this.loadingModelResourceName = '';", + " if (modelManager.getModel(resourceName) !== modelManager._invalidModel) {", + " this._loadModel();", + " this.placement.placeAgain();", + " }", + " })", + " .catch(() => {});", + " return;", + " }", + " this.model = createModelParts(gltf.scene, this.uniforms);", + " }", + "", + " _getPlacementKey() {", + " const { object } = this;", + " return [", + " object.getX(), object.getY(), object.getZ(), object.getWidth(), object.getHeight(), object.getDepth(),", + " object._getDensity(), object._getSize(), object._getMinScale(), object._getMaxScale(),", + " object._getRandomRotation(), object._getRandomTint(), object._getGroundLayer(), object._getMaxSlope(),", + " object._getMinGroundZ(), object._getMaxGroundZ(), object._getSeed(), object._getDrawDistance(),", + " object._getCastShadow(), object._getReceiveShadow(), this.model.parts.length,", + " ].join('|');", + " }", + "", + " _getChunkSize() {", + " return Math.min(Math.max(this.object._getDrawDistance() / 4, MIN_CHUNK_SIZE), MAX_CHUNK_SIZE);", + " }", + "", + " /** @returns {number} The distance between copies (one copy per cell of this size at most). */", + " _getSpacing() {", + " const { object } = this;", + " const density = Math.max(object._getDensity(), 0);", + " if (density <= 0) return 0;", + " const spacing = 100 / Math.sqrt(density);", + " // Too many cells would freeze the game: the density is lowered.", + " const cellCount = (object.getWidth() / spacing) * (object.getHeight() / spacing);", + " return cellCount > MAX_COPIES ? spacing * Math.sqrt(cellCount / MAX_COPIES) : spacing;", + " }", + "", + " /**", + " * Find the copy of a cell of the grid, if there is one.", + " * @returns {Copy | null}", + " */", + " _placeCopy(cellI, cellJ, spacing, grounds) {", + " const { object } = this;", + " const width = object.getWidth();", + " const height = object.getHeight();", + " const nextRandom = createRandom(hashCell(Math.floor(object._getSeed()), cellI, cellJ));", + " // Always drawn in the same order, so that a copy stays the same when properties change.", + " const x = (cellI + nextRandom()) * spacing;", + " const y = (cellJ + nextRandom()) * spacing;", + " const presence = nextRandom();", + " const growth = nextRandom();", + " const scaleFactor = nextRandom();", + " const angle = nextRandom() * Math.PI * 2;", + " if (x > width || y > height) return null;", + " if (presence >= this.mask.sample(x / width, y / height)) return null;", + " const key = cellJ * 65536 + cellI;", + " if (this.removedCopies.has(key)) return null;", + "", + " const sceneX = object.getX() + x;", + " const sceneY = object.getY() + y;", + " const { ground, z } = findHighestGround(grounds, sceneX, sceneY, this._groundAtCopy);", + " const groundZ = ground ? z : object.getZ();", + " if (groundZ < object._getMinGroundZ() || groundZ > object._getMaxGroundZ()) return null;", + " const copyHeight =", + " Math.max(object._getSize(), 0) *", + " (object._getMinScale() + (object._getMaxScale() - object._getMinScale()) * scaleFactor);", + " if (ground) {", + " if (ground.getSlopeAt(sceneX, sceneY) > object._getMaxSlope()) return null;", + " const groundLayer = parseInt(object._getGroundLayer(), 10) || 0;", + " if (groundLayer > 0) {", + " if (growth > ground.getLayerWeightAt(sceneX, sceneY, groundLayer)) return null;", + " // Copies don't stand at the border of their layer, so that trees don't cover roads.", + " const margin = (copyHeight * Math.max(this.model.width, this.model.depth)) / 4;", + " for (const [offsetX, offsetY] of [[margin, 0], [-margin, 0], [0, margin], [0, -margin]]) {", + " if (ground.getLayerWeightAt(sceneX + offsetX, sceneY + offsetY, groundLayer) < 0.5) return null;", + " }", + " }", + " }", + " return {", + " x: sceneX,", + " y: sceneY,", + " z: groundZ,", + " angle: object._getRandomRotation() ? angle : 0,", + " height: copyHeight,", + " key,", + " };", + " }", + "", + " _disposeChunks(chunks) {", + " for (const chunk of chunks) {", + " for (const mesh of chunk.meshes) {", + " mesh.removeFromParent();", + " // Geometries and materials are the ones of the model.", + " mesh.dispose();", + " }", + " }", + " if (chunks === this.chunks) this.chunks = [];", + " }", + "", + " /** Place the copies again, for example after the ground or the object changed. */", + " rebuild() {", + " this._disposeChunks(this.chunks);", + " this.version++;", + " const { object } = this;", + " const spacing = this._getSpacing();", + " if (spacing <= 0 || this.model.parts.length === 0) {", + " this.grid = null;", + " return;", + " }", + " const cellsPerChunk = Math.max(Math.round(this._getChunkSize() / spacing), 1);", + " const grid = {", + " spacing,", + " cellsPerChunk,", + " cellsX: Math.ceil(object.getWidth() / spacing),", + " cellsY: Math.ceil(object.getHeight() / spacing),", + " };", + " this.grid = grid;", + " for (let firstCellJ = 0; firstCellJ < grid.cellsY; firstCellJ += cellsPerChunk) {", + " for (let firstCellI = 0; firstCellI < grid.cellsX; firstCellI += cellsPerChunk) {", + " this._createChunk(firstCellI, firstCellJ);", + " }", + " }", + " }", + "", + " /**", + " * Place again the copies of the chunks touching a rectangle of the scene,", + " * for example after painting.", + " */", + " _rebuildArea(minX, minY, maxX, maxY) {", + " const { grid, object } = this;", + " if (!grid) return;", + " const chunkSize = grid.spacing * grid.cellsPerChunk;", + " const firstChunkI = Math.max(Math.floor((minX - object.getX()) / chunkSize), 0);", + " const firstChunkJ = Math.max(Math.floor((minY - object.getY()) / chunkSize), 0);", + " const lastChunkI = Math.min(Math.floor((maxX - object.getX()) / chunkSize), Math.ceil(grid.cellsX / grid.cellsPerChunk) - 1);", + " const lastChunkJ = Math.min(Math.floor((maxY - object.getY()) / chunkSize), Math.ceil(grid.cellsY / grid.cellsPerChunk) - 1);", + " if (firstChunkI > lastChunkI || firstChunkJ > lastChunkJ) return;", + " const isInArea = (chunk) => {", + " const chunkI = chunk.firstCellI / grid.cellsPerChunk;", + " const chunkJ = chunk.firstCellJ / grid.cellsPerChunk;", + " return chunkI >= firstChunkI && chunkI <= lastChunkI && chunkJ >= firstChunkJ && chunkJ <= lastChunkJ;", + " };", + " this._disposeChunks(this.chunks.filter(isInArea));", + " this.chunks = this.chunks.filter((chunk) => !isInArea(chunk));", + " for (let chunkJ = firstChunkJ; chunkJ <= lastChunkJ; chunkJ++) {", + " for (let chunkI = firstChunkI; chunkI <= lastChunkI; chunkI++) {", + " this._createChunk(chunkI * grid.cellsPerChunk, chunkJ * grid.cellsPerChunk);", + " }", + " }", + " this.version++;", + " }", + "", + " _createChunk(firstCellI, firstCellJ) {", + " const { grid } = this;", + " const grounds = Array.from(this.placement.grounds);", + " /** @type {Copy[]} */", + " const copies = [];", + " for (let cellJ = firstCellJ; cellJ < Math.min(firstCellJ + grid.cellsPerChunk, grid.cellsY); cellJ++) {", + " for (let cellI = firstCellI; cellI < Math.min(firstCellI + grid.cellsPerChunk, grid.cellsX); cellI++) {", + " const copy = this._placeCopy(cellI, cellJ, grid.spacing, grounds);", + " if (copy) copies.push(copy);", + " }", + " }", + " if (copies.length === 0) return;", + "", + " const { object } = this;", + " // Copies are placed in scene units, and the object group is scaled", + " // from its inner area: they are scaled back to keep their proportions.", + " const scaleX = Math.max(object.getWidth() / AREA_SIZE, 0.0001);", + " const scaleY = Math.max(object.getHeight() / AREA_SIZE, 0.0001);", + " const scaleZ = Math.max(object.getDepth() / AREA_DEPTH, 0.0001);", + " const matrices = new Float32Array(copies.length * 16);", + " const tintColor = gdjs.rgbOrHexStringToNumber(object._getRandomTint());", + " const hasTint = tintColor !== 0xffffff;", + " const colors = hasTint ? new Float32Array(copies.length * 3) : null;", + " this._tint.setHex(tintColor);", + " const min = new THREE.Vector3(Infinity, Infinity, Infinity);", + " const max = new THREE.Vector3(-Infinity, -Infinity, -Infinity);", + " copies.forEach((copy, index) => {", + " const cos = Math.cos(copy.angle) * copy.height;", + " const sin = Math.sin(copy.angle) * copy.height;", + " const localX = (copy.x - object.getX()) / scaleX;", + " const localY = (copy.y - object.getY()) / scaleY;", + " const localZ = (copy.z - object.getZ()) / scaleZ;", + " this._matrix.set(", + " cos / scaleX, -sin / scaleX, 0, localX,", + " sin / scaleY, cos / scaleY, 0, localY,", + " 0, 0, copy.height / scaleZ, localZ,", + " 0, 0, 0, 1", + " );", + " this._matrix.toArray(matrices, index * 16);", + " if (colors) {", + " // The tint of each copy is between white and the tint color.", + " this._color.setRGB(1, 1, 1).lerp(this._tint, createRandom(copy.key + 1)());", + " this._color.toArray(colors, index * 3);", + " }", + " const reach = (copy.height * Math.max(this.model.width, this.model.depth)) / 2;", + " min.min(this._chunkPosition.set(copy.x - reach, copy.y - reach, copy.z));", + " max.max(this._chunkPosition.set(copy.x + reach, copy.y + reach, copy.z + copy.height));", + " });", + " const instanceMatrix = new THREE.InstancedBufferAttribute(matrices, 16);", + " const instanceColor = colors ? new THREE.InstancedBufferAttribute(colors, 3) : null;", + " const castShadow = object._getCastShadow();", + " const receiveShadow = object._getReceiveShadow();", + " const meshes = this.model.parts.map((part) => {", + " const mesh = new THREE.InstancedMesh(part.geometry, part.material, 0);", + " // All the parts of the model share the positions of the copies.", + " mesh.instanceMatrix = instanceMatrix;", + " mesh.instanceColor = instanceColor;", + " mesh.count = copies.length;", + " mesh.computeBoundingSphere();", + " mesh.castShadow = castShadow;", + " mesh.receiveShadow = receiveShadow;", + " // The first mesh tests all the parts of the copies.", + " mesh.raycast = () => {};", + " this.group.add(mesh);", + " return mesh;", + " });", + " const center = new THREE.Vector3(", + " ((min.x + max.x) / 2 - object.getX()) / scaleX,", + " ((min.y + max.y) / 2 - object.getY()) / scaleY,", + " ((min.z + max.z) / 2 - object.getZ()) / scaleZ", + " );", + " const chunk = { firstCellI, firstCellJ, meshes, copies, center, radius: min.distanceTo(max) / 2 };", + " // Used by the scene editor to select the object when a copy is clicked.", + " meshes[0].raycast = (raycaster, intersects) => this._raycastChunk(chunk, raycaster, intersects);", + " this.chunks.push(chunk);", + " }", + "", + " /**", + " * Add the hits of a ray on the copies of a chunk, like they are drawn. There", + " * can be thousands of copies: only the ones whose box is crossed by the ray", + " * are tested with the triangles of the model.", + " * @param {{meshes: THREE.InstancedMesh[], copies: Copy[], center: THREE.Vector3, radius: number}} chunk", + " * @param {THREE.Raycaster} raycaster", + " * @param {THREE.Intersection[]} intersects", + " */", + " _raycastChunk(chunk, raycaster, intersects) {", + " const { object, group } = this;", + " this._raycastSphere.center.copy(chunk.center).applyMatrix4(group.matrixWorld);", + " this._raycastSphere.radius = chunk.radius / getWorldScale(object);", + " if (!raycaster.ray.intersectsSphere(this._raycastSphere)) return;", + "", + " // The ray in the scene coordinates, like the copies.", + " const scale = this._raycastScale.set(", + " Math.max(object.getWidth() / AREA_SIZE, 0.0001),", + " Math.max(object.getHeight() / AREA_SIZE, 0.0001),", + " Math.max(object.getDepth() / AREA_DEPTH, 0.0001)", + " );", + " const ray = this._raycastRay.copy(raycaster.ray).applyMatrix4(this._raycastInverseMatrix.copy(group.matrixWorld).invert());", + " ray.origin.multiply(scale).add(this._chunkPosition.set(object.getX(), object.getY(), object.getZ()));", + " ray.direction.multiply(scale);", + "", + " const reachPerHeight = Math.max(this.model.width, this.model.depth) / 2;", + " const instanceMatrices = chunk.meshes[0].instanceMatrix.array;", + " chunk.copies.forEach((copy, index) => {", + " const reach = copy.height * reachPerHeight;", + " this._raycastBox.min.set(copy.x - reach, copy.y - reach, copy.z);", + " this._raycastBox.max.set(copy.x + reach, copy.y + reach, copy.z + copy.height);", + " if (!ray.intersectsBox(this._raycastBox)) return;", + " this._raycastMesh.matrixWorld.fromArray(instanceMatrices, index * 16).premultiply(group.matrixWorld);", + " for (const mesh of chunk.meshes) {", + " this._raycastMesh.geometry = mesh.geometry;", + " this._raycastMesh.material = mesh.material;", + " this._raycastHits.length = 0;", + " this._raycastMesh.raycast(raycaster, this._raycastHits);", + " for (const hit of this._raycastHits) {", + " hit.object = mesh;", + " hit.instanceId = index;", + " intersects.push(hit);", + " }", + " }", + " });", + " }", + "", + " /** @param {THREE.Camera | null} camera */", + " _hideFarChunks(camera) {", + " if (!camera) return;", + " const worldScale = getWorldScale(this.object);", + " const drawDistance = Math.max(this.object._getDrawDistance(), 1) / worldScale;", + " this.uniforms.scatterDrawDistance.value = drawDistance;", + " this._cameraPosition.setFromMatrixPosition(camera.matrixWorld);", + " for (const chunk of this.chunks) {", + " const distance = this._chunkPosition", + " .copy(chunk.center)", + " .applyMatrix4(this.group.matrixWorld)", + " .distanceTo(this._cameraPosition);", + " const isVisible = distance - chunk.radius / worldScale < drawDistance;", + " for (const mesh of chunk.meshes) mesh.visible = isVisible;", + " }", + " }", + "", + " /**", + " * @param {number} elapsedSeconds", + " * @param {THREE.Camera | null} camera", + " */", + " update(elapsedSeconds, camera) {", + " if (this.placement.shouldPlaceAgain(elapsedSeconds, this._getPlacementKey())) {", + " this.rebuild();", + " }", + " if (!this.changedSamples.isEmpty()) {", + " const { object } = this;", + " const sampleWidth = object.getWidth() / MASK_RESOLUTION;", + " const sampleHeight = object.getHeight() / MASK_RESOLUTION;", + " const { minI, minJ, maxI, maxJ } = this.changedSamples;", + " // Copies between samples use the density of the samples around them.", + " this._rebuildArea(", + " object.getX() + (minI - 1) * sampleWidth,", + " object.getY() + (minJ - 1) * sampleHeight,", + " object.getX() + (maxI + 1) * sampleWidth,", + " object.getY() + (maxJ + 1) * sampleHeight", + " );", + " this.changedSamples.clear();", + " }", + " // The object transformation is applied just before rendering, it's needed now.", + " this.object.getRenderer().ensureUpToDate();", + " this.group.updateWorldMatrix(true, false);", + " this._hideFarChunks(camera);", + " this._updateSurface();", + " }", + "", + " _updateSurface() {", + " const { object } = this;", + " if (typeof object.setSurface !== 'function') return;", + " const trunkKey = object._getTrunkCollisions() + '|' + object._getTrunkDiameter();", + " if (trunkKey !== this.trunkKey) {", + " this.trunkKey = trunkKey;", + " this.version++;", + " }", + " if (object.getSurface() !== this.surface) object.setSurface(this.surface);", + " }", + "", + " /** @returns {Copy[]} */", + " getCopies() {", + " const copies = [];", + " for (const chunk of this.chunks) copies.push(...chunk.copies);", + " return copies;", + " }", + "", + " /**", + " * @returns {Copy | null} The copy whose base is the nearest to a position (on X and Y), if any.", + " */", + " findNearestCopy(x, y) {", + " let nearestCopy = null;", + " let nearestDistanceSquared = Infinity;", + " for (const chunk of this.chunks) {", + " for (const copy of chunk.copies) {", + " const distanceSquared = (copy.x - x) * (copy.x - x) + (copy.y - y) * (copy.y - y);", + " if (distanceSquared < nearestDistanceSquared) {", + " nearestDistanceSquared = distanceSquared;", + " nearestCopy = copy;", + " }", + " }", + " }", + " return nearestCopy;", + " }", + "", + " getCopyCount() {", + " let count = 0;", + " for (const chunk of this.chunks) count += chunk.copies.length;", + " return count;", + " }", + "", + " /** Remove the copies whose base is in a circle (they come back when the scene is restarted). */", + " removeCopies(x, y, radius) {", + " let hasRemovedCopies = false;", + " for (const chunk of this.chunks) {", + " for (const copy of chunk.copies) {", + " if ((copy.x - x) * (copy.x - x) + (copy.y - y) * (copy.y - y) <= radius * radius) {", + " this.removedCopies.set(copy.key, copy);", + " hasRemovedCopies = true;", + " }", + " }", + " }", + " if (hasRemovedCopies) this._rebuildArea(x - radius, y - radius, x + radius, y + radius);", + " }", + "", + " /** Paint the full density around a position, and bring back the copies removed there. */", + " addCopies(x, y, radius) {", + " for (const [key, copy] of this.removedCopies) {", + " if ((copy.x - x) * (copy.x - x) + (copy.y - y) * (copy.y - y) <= radius * radius) {", + " this.removedCopies.delete(key);", + " }", + " }", + " this.paint(x, y, x, y, radius, 1, 1);", + " }", + "", + " _getStroke(x1, y1, x2, y2, radius) {", + " const { object } = this;", + " const stroke = this._stroke;", + " stroke.cellWidth = object.getWidth() / MASK_RESOLUTION;", + " stroke.cellHeight = object.getHeight() / MASK_RESOLUTION;", + " stroke.ax = x1 - object.getX();", + " stroke.ay = y1 - object.getY();", + " stroke.bx = x2 - object.getX();", + " stroke.by = y2 - object.getY();", + " stroke.radius = Math.max(radius, 0);", + " return stroke;", + " }", + "", + " /**", + " * Paint the density around a segment (copies are placed again at the next frame).", + " * @param {number} density From 0 (erase) to 1 (as dense as the Density property).", + " * @param {number} strength From 0 to 1.", + " */", + " paint(x1, y1, x2, y2, radius, density, strength) {", + " const changedSamples = new SampleRectangle();", + " this.mask.paint(this._getStroke(x1, y1, x2, y2, radius), density, strength, changedSamples);", + " this.changedSamples.add(changedSamples);", + " }", + "", + " /** @param {DensityMask} mask */", + " setMask(mask) {", + " this.mask = mask;", + " this.rebuild();", + " }", + "", + " /**", + " * Prisms around the trunks, from 0 to 1 in the object box (see `gdjs.Surface`).", + " * @returns {gdjs.SurfaceTriangles}", + " */", + " _getTrunkTriangles() {", + " const { object } = this;", + " const copies = object._getTrunkCollisions() ? this.getCopies() : [];", + " const verticesPerCopy = TRUNK_SIDES * 2 + 1;", + " const positions = new Float32Array(copies.length * verticesPerCopy * 3);", + " const indices = new Uint32Array(copies.length * TRUNK_SIDES * 3 * 3);", + " const width = object.getWidth() || 1;", + " const height = object.getHeight() || 1;", + " const depth = object.getDepth() || 1;", + " let indexOffset = 0;", + " copies.forEach((copy, copyIndex) => {", + " const radius = (Math.max(object._getTrunkDiameter(), 0) / 2) * (copy.height / Math.max(object._getSize(), 1));", + " const top = copy.z + copy.height * 0.8;", + " const firstVertex = copyIndex * verticesPerCopy;", + " const setVertex = (vertex, x, y, z) => {", + " positions[vertex * 3] = (x - object.getX()) / width;", + " positions[vertex * 3 + 1] = (y - object.getY()) / height;", + " positions[vertex * 3 + 2] = (z - object.getZ()) / depth;", + " };", + " for (let side = 0; side < TRUNK_SIDES; side++) {", + " const angle = ((side + 0.5) / TRUNK_SIDES) * Math.PI * 2;", + " const x = copy.x + Math.cos(angle) * radius;", + " const y = copy.y + Math.sin(angle) * radius;", + " // Starting a bit under the ground, which can be sloped.", + " setVertex(firstVertex + side * 2, x, y, copy.z - radius);", + " setVertex(firstVertex + side * 2 + 1, x, y, top);", + " }", + " const center = firstVertex + TRUNK_SIDES * 2;", + " setVertex(center, copy.x, copy.y, top);", + " for (let side = 0; side < TRUNK_SIDES; side++) {", + " const bottom = firstVertex + side * 2;", + " const nextBottom = firstVertex + ((side + 1) % TRUNK_SIDES) * 2;", + " // Triangles face out of the prism (vertices at odd indices are at the top).", + " indices.set([bottom, nextBottom, bottom + 1, nextBottom, nextBottom + 1, bottom + 1], indexOffset);", + " indices.set([center, bottom + 1, nextBottom + 1], indexOffset + 6);", + " indexOffset += 9;", + " }", + " });", + " if (copies.length === 0) {", + " // Without triangles, the physics engine and navigation meshes would use", + " // the object box: a tiny triangle under the object is used instead.", + " return { positions: new Float32Array([0, 0, -1, 0.0001, 0, -1, 0, 0.0001, -1]), indices: new Uint32Array([0, 2, 1]) };", + " }", + " return { positions, indices };", + " }", + "", + " dispose() {", + " this.isDisposed = true;", + " if (typeof this.object.setSurface === 'function' && this.object.getSurface() === this.surface) {", + " this.object.setSurface(null);", + " }", + " this._disposeChunks(this.chunks);", + " this._disposeModel();", + " this.group.removeFromParent();", + " }", + "}", + "", + "const scatterToolIcons = {", + " Add: svgIcon(", + " ''", + " ),", + " Erase: svgIcon(", + " ''", + " ),", + "};", + "", + "/**", + " * The brushes shown in the scene editor when scattered models are selected,", + " * to add or erase copies. Without the needed editor functions (older GDevelop", + " * versions), nothing is shown: copies can still be painted with events.", + " */", + "class ScatterEditorTools {", + " constructor() {", + " /** '' when no brush is used. */", + " this.toolName = '';", + " this.sizePercent = 30;", + " this.strengthPercent = 50;", + " /** @type {Scatter | null} */", + " this.scatter = null;", + " /** @type {gdjs.InGameEditor | null} */", + " this.editor = null;", + " this.isStroking = false;", + " this.lastStrokeTime = 0;", + " /** @type {Array<{mask: DensityMask, baseMask: DensityMask}>} */", + " this.undoSteps = [];", + " this.raycaster = new THREE.Raycaster();", + " this.ndc = new THREE.Vector2();", + " this._groundAtCursor = { ground: null, z: 0 };", + " /** A circle following the ground under the cursor. */", + " this.brushRing = new THREE.LineLoop(", + " new THREE.BufferGeometry().setAttribute('position', new THREE.BufferAttribute(new Float32Array(64 * 3), 3)),", + " new THREE.LineBasicMaterial({ color: 0xffffff, depthTest: false, transparent: true })", + " );", + " this.brushRing.renderOrder = 1000;", + " this.brushRing.frustumCulled = false;", + " this.brushRing.raycast = () => {};", + " }", + "", + " /** @param {gdjs.InGameEditor} editor */", + " update(editor) {", + " if (typeof editor.showToolbar !== 'function') return;", + " this.editor = editor;", + " const selectedObjects = editor.getSelectedObjects();", + " const scatter =", + " selectedObjects.length === 1 && selectedObjects[0].__terrainScatter ? selectedObjects[0].__terrainScatter : null;", + " if (scatter !== this.scatter) {", + " this._endStroke();", + " this.scatter = scatter;", + " this.undoSteps.length = 0;", + " }", + " if (scatter) editor.showToolbar('ScatteredModels', this._getToolbarItems());", + " const game = editor.getRuntimeGame();", + " if (!scatter || !this.toolName) {", + " this.brushRing.removeFromParent();", + " return;", + " }", + " const inputManager = game.getInputManager();", + " if (inputManager.wasKeyJustPressed(gdjs.evtTools.input.keysNameToCode.Escape)) {", + " this._endStroke();", + " this.toolName = '';", + " this.brushRing.removeFromParent();", + " return;", + " }", + " editor.captureLeftMouseButton();", + " const hit = this._getPositionUnderCursor(scatter, game);", + " const radius = this._getRadius();", + " this._showBrushRing(scatter, hit, radius);", + "", + " const isPressedForTool =", + " typeof editor.isCapturedLeftMouseButtonPressed === 'function'", + " ? editor.isCapturedLeftMouseButtonPressed()", + " : inputManager.isMouseButtonPressed(0);", + " // The button is released outside the canvas when the mouse is on a toolbar.", + " const isPressed = isPressedForTool && inputManager.isMouseInsideCanvas();", + " if (isPressed && hit && !this.isStroking) {", + " this._startStroke(scatter);", + " }", + " if (this.isStroking && isPressed && hit) {", + " this._applyBrush(scatter, hit, radius);", + " }", + " if (!isPressed) {", + " this._endStroke();", + " }", + " }", + "", + " _getRadius() {", + " const { object } = this.scatter;", + " const maxRadius = Math.max(object.getWidth(), object.getHeight()) / 4;", + " return Math.max(maxRadius * Math.pow(this.sizePercent / 100, 2), 10);", + " }", + "", + " /**", + " * @returns {{x: number, y: number} | null} The position of the scene under the cursor: on the", + " * highest ground hit, or on the plane at the object Z.", + " */", + " _getPositionUnderCursor(scatter, game) {", + " let hit = null;", + " for (const ground of getGrounds(scatter.object.getRuntimeScene())) {", + " if (typeof ground.raycastFromCursor !== 'function') continue;", + " const localHit = ground.raycastFromCursor(game, this.raycaster, this.ndc);", + " if (!localHit) continue;", + " const sceneHit = ground.toScenePosition(localHit);", + " if (!hit || sceneHit.z > hit.z) hit = sceneHit;", + " }", + " if (hit) return hit;", + "", + " const { object } = scatter;", + " const layer = object.getInstanceContainer().getLayer(object.getLayer());", + " const camera = layer.getRenderer().getThreeCamera();", + " if (!camera) return null;", + " const inputManager = game.getInputManager();", + " this.ndc.set(", + " (inputManager.getCursorX() / game.getGameResolutionWidth()) * 2 - 1,", + " -(inputManager.getCursorY() / game.getGameResolutionHeight()) * 2 + 1", + " );", + " this.raycaster.setFromCamera(this.ndc, camera);", + " scatter.group.updateWorldMatrix(true, false);", + " const ray = this.raycaster.ray.clone().applyMatrix4(scatter.group.matrixWorld.clone().invert());", + " const point = ray.intersectPlane(new THREE.Plane(new THREE.Vector3(0, 0, 1), 0), new THREE.Vector3());", + " if (!point) return null;", + " return {", + " x: object.getX() + (point.x / AREA_SIZE) * object.getWidth(),", + " y: object.getY() + (point.y / AREA_SIZE) * object.getHeight(),", + " };", + " }", + "", + " _showBrushRing(scatter, hit, radius) {", + " if (!hit) {", + " this.brushRing.removeFromParent();", + " return;", + " }", + " const { object } = scatter;", + " if (this.brushRing.parent !== scatter.group) scatter.group.add(this.brushRing);", + " const grounds = getGrounds(object.getRuntimeScene());", + " const positions = this.brushRing.geometry.attributes.position;", + " for (let index = 0; index < positions.count; index++) {", + " const angle = (index / positions.count) * Math.PI * 2;", + " const x = hit.x + Math.cos(angle) * radius;", + " const y = hit.y + Math.sin(angle) * radius;", + " const { ground, z } = findHighestGround(grounds, x, y, this._groundAtCursor);", + " positions.setXYZ(", + " index,", + " ((x - object.getX()) / object.getWidth()) * AREA_SIZE,", + " ((y - object.getY()) / object.getHeight()) * AREA_SIZE,", + " (((ground ? z : object.getZ()) + 2 - object.getZ()) / Math.max(object.getDepth(), 0.0001)) * AREA_DEPTH", + " );", + " }", + " positions.needsUpdate = true;", + " }", + "", + " _startStroke(scatter) {", + " this.isStroking = true;", + " this.lastStrokeTime = performance.now();", + " this.undoSteps.push({ mask: scatter.mask.clone(), baseMask: scatter.baseMask });", + " while (this.undoSteps.length > 30) this.undoSteps.shift();", + " }", + "", + " _endStroke() {", + " if (!this.isStroking) return;", + " this.isStroking = false;", + " this._save();", + " }", + "", + " _applyBrush(scatter, hit, radius) {", + " const now = performance.now();", + " const elapsedSeconds = Math.min((now - this.lastStrokeTime) / 1000, 0.1);", + " this.lastStrokeTime = now;", + " const strength = (this.strengthPercent / 100) * elapsedSeconds * 8;", + " scatter.paint(hit.x, hit.y, hit.x, hit.y, radius, this.toolName === 'Add' ? 1 : 0, strength);", + " }", + "", + " _save() {", + " const { scatter, editor } = this;", + " if (!scatter || !editor) return;", + " editor.updateObjectProperties(scatter.object.getName(), { PaintData: scatter.save() });", + " }", + "", + " _undo() {", + " const undoStep = this.undoSteps.pop();", + " // The coverage or the edits may have changed since the stroke.", + " if (!this.scatter || !undoStep || undoStep.baseMask !== this.scatter.baseMask) {", + " this.undoSteps.length = 0;", + " return;", + " }", + " this.scatter.setMask(undoStep.mask);", + " this._save();", + " }", + "", + " /** @returns {Array} */", + " _getToolbarItems() {", + " const toolButton = (toolName, tooltip) => ({", + " type: 'button',", + " id: toolName,", + " tooltip,", + " iconUrl: scatterToolIcons[toolName],", + " isActive: this.toolName === toolName,", + " onClick: () => (this.toolName = this.toolName === toolName ? '' : toolName),", + " });", + " return [", + " toolButton('Add', 'Add copies (drag on the ground)'),", + " toolButton('Erase', 'Erase copies'),", + " { type: 'divider', id: 'BrushDivider' },", + " {", + " type: 'slider',", + " id: 'Size',", + " tooltip: 'Brush size',", + " iconUrl: toolIcons.Size,", + " min: 1,", + " max: 100,", + " value: this.sizePercent,", + " onChange: (value) => (this.sizePercent = value),", + " },", + " {", + " type: 'slider',", + " id: 'Strength',", + " tooltip: 'Brush strength',", + " iconUrl: toolIcons.Strength,", + " min: 5,", + " max: 100,", + " value: this.strengthPercent,", + " onChange: (value) => (this.strengthPercent = value),", + " },", + " {", + " type: 'button',", + " id: 'Undo',", + " tooltip: 'Undo the last stroke on these scattered models',", + " iconUrl: toolIcons.Undo,", + " onClick: () => this._undo(),", + " },", + " ];", + " }", + "}", + "", + "const editorTools = new ScatterEditorTools();", + "if (gdjs.registerInGameEditorPostStepCallback) {", + " gdjs.registerInGameEditorPostStepCallback((editor) => editorTools.update(editor));", + "}", + "", + "gdjs.__terrainScatterExtension = {", + " Scatter,", + " // Exposed for tests.", + " DensityMask,", + " maskCodec,", + " applyEdits,", + "};", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [], + "description": "Define scattered models helper classes JavaScript code.", + "private": true + } + ], + "eventsBasedBehaviors": [], + "eventsBasedObjects": [ + { + "areaMaxX": 4096, + "areaMaxY": 4096, + "areaMaxZ": 600, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "defaultName": "Terrain", + "assetStoreTag": "3d terrains", + "description": "A ground with hills, mountains or valleys that can be sculpted and painted in the scene editor, or changed with events. Add the 3D physics behavior to it to make objects walk or roll on it.", + "fullName": "3D terrain", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXRlcnJhaW4iIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTQsNkwxMC4yNSwxMUwxMy4xLDE0LjhMMTEuNSwxNkM5LjgxLDEzLjc1IDcsMTAgNywxMEwxLDE4SDIzTDE0LDZaIiAvPjwvc3ZnPg==", + "is3D": true, + "isUsingLegacyInstancesRenderer": true, + "name": "Terrain3D", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/10e0a26c0d500830dfe23b94138beb0ef61607ba62bca05a5aa5c849777f1b06_terrain.svg", + "objects": [ + { + "assetStoreId": "", + "name": "AreaPlaceholder", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 4096, + "height": 4096, + "depth": 600, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "Basic", + "tint": "255;255;255", + "frontFaceResourceName": "", + "frontFaceVisible": false, + "frontFaceResourceRepeat": false, + "backFaceResourceName": "", + "backFaceVisible": false, + "backFaceResourceRepeat": false, + "leftFaceResourceName": "", + "leftFaceVisible": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceName": "", + "rightFaceVisible": false, + "rightFaceResourceRepeat": false, + "topFaceResourceName": "", + "topFaceVisible": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceName": "", + "bottomFaceVisible": false, + "bottomFaceResourceRepeat": false + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "AreaPlaceholder" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [ + { + "angle": 0, + "customSize": true, + "width": 4096, + "height": 4096, + "depth": 600, + "keepRatio": true, + "layer": "", + "name": "AreaPlaceholder", + "persistentUuid": "5d7a8b1e-3c2f-4e6a-9b1d-004096004096", + "x": 0, + "y": 0, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": {}, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Terrain3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "for (const child of object.getChildrenContainer().getAdhocListOfAllInstances()) {", + " child.deleteFromScene();", + "}", + "if (object.__terrain3D) object.__terrain3D.dispose();", + "object.__terrain3D = new gdjs.__terrain3DExtension.Terrain(object);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDestroy", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrain3D.dispose();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrain3D.update();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrain3D.onHotReloading();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + } + ], + "objectGroups": [] + }, + { + "fullName": "Raise or lower the ground", + "functionType": "Action", + "name": "RaiseGround", + "sentence": "Raise the ground of _PARAM0_ by _PARAM4_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const height = eventsFunctionContext.getArgument(\"Height\");", + "object.__terrain3D.raise(x, y, x, y, radius, height);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Height to add (negative to lower the ground)", + "name": "Height", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Raise the ground around a position (or lower it with a negative height). It's raised the most at the center and less and less up to the radius.", + "group": "Sculpt" + }, + { + "fullName": "Raise or lower the ground along a line", + "functionType": "Action", + "name": "RaiseGroundAlongLine", + "sentence": "Raise the ground of _PARAM0_ by _PARAM6_ from _PARAM1_; _PARAM2_ to _PARAM3_; _PARAM4_ with a radius of _PARAM5_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x1 = eventsFunctionContext.getArgument(\"X1\");", + "const y1 = eventsFunctionContext.getArgument(\"Y1\");", + "const x2 = eventsFunctionContext.getArgument(\"X2\");", + "const y2 = eventsFunctionContext.getArgument(\"Y2\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const height = eventsFunctionContext.getArgument(\"Height\");", + "object.__terrain3D.raise(x1, y1, x2, y2, radius, height);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the start", + "name": "X1", + "type": "expression" + }, + { + "description": "Y position of the start", + "name": "Y1", + "type": "expression" + }, + { + "description": "X position of the end", + "name": "X2", + "type": "expression" + }, + { + "description": "Y position of the end", + "name": "Y2", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance from the line." + }, + { + "description": "Height to add (negative to lower the ground)", + "name": "Height", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Raise the ground along a line, for example for a ridge, or lower it with a negative height to dig a river bed or a valley.", + "group": "Sculpt" + }, + { + "fullName": "Flatten the ground", + "functionType": "Action", + "name": "FlattenGround", + "sentence": "Flatten the ground of _PARAM0_ to a Z of _PARAM4_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const z = eventsFunctionContext.getArgument(\"Z\");", + "object.__terrain3D.flatten(x, y, x, y, radius, z, 1);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Z position of the flat ground", + "name": "Z", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Flatten the ground around a position to a given height, for example to place a building.", + "group": "Sculpt" + }, + { + "fullName": "Smooth the ground", + "functionType": "Action", + "name": "SmoothGround", + "sentence": "Smooth the ground of _PARAM0_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_ (strength: _PARAM4_)", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const strength = eventsFunctionContext.getArgument(\"Strength\");", + "object.__terrain3D.smooth(x, y, x, y, radius, strength);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Strength (between 0 and 1)", + "name": "Strength", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Smooth the ground around a position, softening bumps and cliffs.", + "group": "Sculpt" + }, + { + "fullName": "Paint a layer", + "functionType": "Action", + "name": "PaintLayer", + "sentence": "Paint layer _PARAM4_ on _PARAM0_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const layer = eventsFunctionContext.getArgument(\"Layer\");", + "object.__terrain3D.paint(x, y, x, y, radius, layer, 1);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Layer (1 to 4)", + "name": "Layer", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Paint a layer (like grass, dirt, rock or sand) on the ground around a position.", + "group": "Paint" + }, + { + "fullName": "Paint a layer along a line", + "functionType": "Action", + "name": "PaintLayerAlongLine", + "sentence": "Paint layer _PARAM6_ on _PARAM0_ from _PARAM1_; _PARAM2_ to _PARAM3_; _PARAM4_ with a radius of _PARAM5_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x1 = eventsFunctionContext.getArgument(\"X1\");", + "const y1 = eventsFunctionContext.getArgument(\"Y1\");", + "const x2 = eventsFunctionContext.getArgument(\"X2\");", + "const y2 = eventsFunctionContext.getArgument(\"Y2\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const layer = eventsFunctionContext.getArgument(\"Layer\");", + "object.__terrain3D.paint(x1, y1, x2, y2, radius, layer, 1);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the start", + "name": "X1", + "type": "expression" + }, + { + "description": "Y position of the start", + "name": "Y1", + "type": "expression" + }, + { + "description": "X position of the end", + "name": "X2", + "type": "expression" + }, + { + "description": "Y position of the end", + "name": "Y2", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance from the line." + }, + { + "description": "Layer (1 to 4)", + "name": "Layer", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Paint a layer on the ground along a line, for example for a path or a road.", + "group": "Paint" + }, + { + "fullName": "Tint the ground", + "functionType": "Action", + "name": "TintGround", + "sentence": "Tint the ground of _PARAM0_ with _PARAM4_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const color = gdjs.rgbOrHexToRGBColor(eventsFunctionContext.getArgument(\"Color\"));", + "object.__terrain3D.tint(x, y, x, y, radius, color, 1);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Color", + "name": "Color", + "type": "color" + } + ], + "objectGroups": [], + "description": "Tint the ground around a position with a color multiplying its layers, like dry grass or darker mud. White removes the tint.", + "group": "Paint" + }, + { + "fullName": "Change the wetness of the ground", + "functionType": "Action", + "name": "WetGround", + "sentence": "Change the wetness of the ground of _PARAM0_ to _PARAM4_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const wetness = eventsFunctionContext.getArgument(\"Wetness\");", + "object.__terrain3D.wet(x, y, x, y, radius, wetness, 1);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Wetness (from 0 for dry to 1)", + "name": "Wetness", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Make the ground around a position wet (darker and shiny, like after rain or near water) or dry.", + "group": "Paint" + }, + { + "fullName": "Ground wetness", + "functionType": "ExpressionAndCondition", + "name": "GroundWetness", + "sentence": "the wetness of the ground at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.getWetnessAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the wetness of the ground at a position, from 0 (dry) to 1, painted or near the water (wet shore).", + "group": "Paint", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Ground tint", + "functionType": "StringExpression", + "name": "GroundTint", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.getTintAt(x, y).join(\";\");" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "The tint painted on the ground at a position, like \"255;255;255\" (white when there is none).", + "group": "Paint", + "expressionType": { + "type": "string" + } + }, + { + "fullName": "Make a hole", + "functionType": "Action", + "name": "MakeHole", + "sentence": "Make a hole in _PARAM0_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "object.__terrain3D.makeHole(x, y, x, y, radius);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Remove the ground in a circle, for the entrance of a tunnel, a cave or a well: there is nothing drawn and no collision there. Fill it again with the \"Fill holes\" action. A height field can't go under itself: put a 3D model (with a collision shape) for the tunnel or the cave under the ground. The physics engine removes the triangles touching a hole, so the hole in the collisions can be up to a cell larger than the one drawn: make the model go a bit further than the hole, or put rocks around the entrance.", + "group": "Holes" + }, + { + "fullName": "Fill holes", + "functionType": "Action", + "name": "FillHoles", + "sentence": "Fill the holes in _PARAM0_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "object.__terrain3D.fillHoles(x, y, x, y, radius);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Fill the holes in a circle with the layers of the ground around them: the ground is drawn and has collisions again.", + "group": "Holes" + }, + { + "fullName": "Carve an entrance", + "functionType": "Action", + "name": "CarveEntrance", + "sentence": "Carve an entrance in _PARAM0_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_, going in at _PARAM4_° with its floor at Z _PARAM5_ and _PARAM6_ pixels of flat ground in front", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const angle = eventsFunctionContext.getArgument(\"Angle\");", + "const floorZ = eventsFunctionContext.getArgument(\"FloorZ\");", + "const length = eventsFunctionContext.getArgument(\"Length\");", + "object.__terrain3D.carveEntrance(x, y, radius, angle, floorZ, length);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression" + }, + { + "description": "Direction going into the ground (in degrees)", + "name": "Angle", + "type": "expression" + }, + { + "description": "Z position of the floor", + "name": "FloorZ", + "type": "expression" + }, + { + "description": "Length of the flat ground in front of the entrance (in pixels)", + "name": "Length", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Make the entrance of a tunnel or a cave on a slope: a hole, with the ground in front of it flattened to the floor of the tunnel.", + "group": "Holes" + }, + { + "fullName": "Hole", + "functionType": "Condition", + "name": "IsHole", + "sentence": "There is a hole in _PARAM0_ at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.isHoleAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Check if there is a hole in the ground at a position.", + "group": "Holes" + }, + { + "fullName": "Ground Z position, with holes", + "functionType": "Expression", + "name": "GroundZWithHoles", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const terrain = object.__terrain3D;", + "eventsFunctionContext.returnValue = terrain.isHoleAt(x, y) ? eventsFunctionContext.getArgument(\"ZInHoles\") : terrain.getHeightAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + }, + { + "description": "Value in holes", + "name": "ZInHoles", + "type": "expression", + "longDescription": "For example, a Z position far under the ground." + } + ], + "objectGroups": [], + "description": "The Z position of the ground at a position, or the given value where there is a hole.", + "group": "Holes", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Generate a relief", + "functionType": "Action", + "name": "GenerateRelief", + "sentence": "Generate a relief of _PARAM1_ on _PARAM0_ with seed _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const relief = eventsFunctionContext.getArgument(\"Relief\");", + "const seed = eventsFunctionContext.getArgument(\"Seed\");", + "object.__terrain3D.generate(relief, seed);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "Relief", + "name": "Relief", + "type": "stringWithSelector", + "supplementaryInformation": "[\"Flat\", \"Hills\", \"Mountains\", \"Island\"]" + }, + { + "description": "Seed", + "name": "Seed", + "type": "expression", + "longDescription": "Each seed gives a different terrain with the same relief." + } + ], + "objectGroups": [], + "description": "Replace the ground with a generated relief. Painted layers are reset.", + "group": "Generation" + }, + { + "fullName": "Load a heightmap", + "functionType": "Action", + "name": "LoadHeightmap", + "sentence": "Load the heights of _PARAM0_ from _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const image = eventsFunctionContext.getArgument(\"Image\");", + "object.__terrain3D.loadHeightmap(image);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "Heightmap image", + "name": "Image", + "type": "imageResource", + "longDescription": "Black is the lowest, white the highest. For more precision, store the height on 16 bits in the red (most significant byte) and green channels." + } + ], + "objectGroups": [], + "description": "Replace the ground with the heights of an image. Painted layers are kept.", + "group": "Generation" + }, + { + "fullName": "Put objects on the ground", + "functionType": "Action", + "name": "PutOnGround", + "sentence": "Put _PARAM1_ on the ground of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrain3D.placeOnGround(eventsFunctionContext.getObjects(\"Objects\"));" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "Objects to put on the ground", + "name": "Objects", + "type": "objectList" + } + ], + "objectGroups": [], + "description": "Move objects up or down so that their bottom is on the ground, under their center. Useful to place trees, rocks or characters on a terrain.", + "group": "Position" + }, + { + "fullName": "Ground Z position", + "functionType": "ExpressionAndCondition", + "name": "GroundZ", + "sentence": "the ground Z position at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.getHeightAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the Z position of the ground at a position of the scene.", + "group": "Position", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Ground slope", + "functionType": "ExpressionAndCondition", + "name": "GroundSlope", + "sentence": "the ground slope at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.getSlopeAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the slope of the ground (angle with the horizontal, in degrees) at a position of the scene.", + "group": "Position", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Ground layer", + "functionType": "ExpressionAndCondition", + "name": "GroundLayer", + "sentence": "the ground layer at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.getLayerAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the layer (from 1 to 4) painted the most at a position of the scene. Useful to play footstep sounds or to slow down characters in water or mud.", + "group": "Paint", + "expressionType": { + "type": "expression" + } + } + ], + "propertyDescriptors": [ + { + "value": "Hills", + "type": "Choice", + "label": "Relief", + "description": "The initial shape, before sculpting.", + "group": "Shape", + "choices": [ + { + "label": "Flat", + "value": "Flat" + }, + { + "label": "Hills", + "value": "Hills" + }, + { + "label": "Mountains", + "value": "Mountains" + }, + { + "label": "Island", + "value": "Island" + } + ], + "name": "Relief" + }, + { + "value": "1", + "type": "Number", + "label": "Seed", + "description": "Each seed gives a different terrain with the same relief.", + "group": "Shape", + "name": "Seed" + }, + { + "value": "", + "type": "Resource", + "label": "Heightmap image", + "description": "Optional. Replaces the relief: black is the lowest, white the highest (the object depth).", + "group": "Shape", + "extraInformation": [ + "image" + ], + "name": "HeightmapImage" + }, + { + "value": "", + "type": "MultilineString", + "label": "Edits", + "description": "Changes of the relief or heightmap, applied in order, as a JSON list, for example [{\"tool\": \"raise\", \"x\": 0.3, \"y\": 0.4, \"radius\": 0.15, \"height\": 0.4}, {\"tool\": \"paint\", \"x\": 0.1, \"y\": 0.9, \"toX\": 0.8, \"toY\": 0.2, \"radius\": 0.02, \"layer\": 2}]. Tools: raise (height added to the ground, negative to lower), flatten (z: the ground is brought to this height, fully at the center, cutting what is above it and filling what is below; with toX and toY, toZ gives the height at the end of the line, for a road going up), smooth (strength), paint (layer, from 1 to 4, and strength), tint (color, like \"120;140;60\", multiplying the layers, white removing the tint, and strength), wetness (amount, from 0 for dry to 1 for wet, and strength), hole (no ground in the radius, for the entrances of tunnels and caves) and fill (the holes in the radius are filled with the layers of the ground around them). x, y and radius are fractions (0 to 1) of the terrain size (x is (scene X - terrain X) / terrain width). height and z are fractions of its depth: the ground at z is at Z = terrain Z + z * terrain depth. Add toX and toY to change the ground along a line (paths, rivers, ridges). Shown in the scene editor.", + "group": "Shape", + "advanced": true, + "name": "Edits" + }, + { + "value": "256", + "type": "Choice", + "label": "Resolution", + "description": "The number of cells on each side of the terrain. Higher is more detailed but slower.", + "group": "Shape", + "choices": [ + { + "label": "64 x 64", + "value": "64" + }, + { + "label": "128 x 128", + "value": "128" + }, + { + "label": "256 x 256", + "value": "256" + }, + { + "label": "512 x 512", + "value": "512" + }, + { + "label": "1024 x 1024", + "value": "1024" + } + ], + "name": "Resolution" + }, + { + "value": "96;160;64", + "type": "Color", + "label": "Layer 1 color (grass)", + "group": "Layers", + "name": "Layer1Color" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 1 texture", + "description": "Optional. Tinted by the layer color.", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer1Texture" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 1 normal map", + "description": "Optional. The bumps of the texture, lit by the lights (an OpenGL normal map, like the textures of ambientCG).", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer1NormalMap" + }, + { + "value": "150;112;72", + "type": "Color", + "label": "Layer 2 color (dirt)", + "group": "Layers", + "name": "Layer2Color" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 2 texture", + "description": "Optional. Tinted by the layer color.", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer2Texture" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 2 normal map", + "description": "Optional. The bumps of the texture, lit by the lights (an OpenGL normal map, like the textures of ambientCG).", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer2NormalMap" + }, + { + "value": "124;118;112", + "type": "Color", + "label": "Layer 3 color (rock)", + "group": "Layers", + "name": "Layer3Color" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 3 texture", + "description": "Optional. Tinted by the layer color.", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer3Texture" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 3 normal map", + "description": "Optional. The bumps of the texture, lit by the lights (an OpenGL normal map, like the textures of ambientCG).", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer3NormalMap" + }, + { + "value": "224;204;148", + "type": "Color", + "label": "Layer 4 color (sand)", + "group": "Layers", + "name": "Layer4Color" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 4 texture", + "description": "Optional. Tinted by the layer color.", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer4Texture" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 4 normal map", + "description": "Optional. The bumps of the texture, lit by the lights (an OpenGL normal map, like the textures of ambientCG).", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer4NormalMap" + }, + { + "value": "1", + "type": "Number", + "label": "Normal map strength", + "description": "How much the normal maps bump the ground: 0 for none, 1 as they are, more to exaggerate them.", + "group": "Layers", + "name": "NormalMapStrength" + }, + { + "value": "256", + "type": "Number", + "label": "Texture size", + "unit": "Pixel", + "description": "The size, in the scene, of one repetition of the layer textures.", + "group": "Layers", + "name": "TextureSize" + }, + { + "value": "0.5", + "type": "Number", + "label": "Height blending", + "description": "Where layers meet, the bright parts of textures (stones, tufts) cover the dark ones (gaps, sand) instead of fading into each other: 0 for none, 1 for the most.", + "group": "Layers", + "name": "HeightBlending" + }, + { + "value": "0.5", + "type": "Number", + "label": "Texture variation", + "description": "Makes the repetition of textures less visible, with patches of bigger and rotated textures and colors slowly varying: 0 for none, 1 for the most.", + "group": "Layers", + "name": "TextureVariation" + }, + { + "value": "false", + "type": "Boolean", + "label": "Wet shore", + "description": "Make the ground wet (darker and shiny) under a Z position, like the sand of a beach near the water.", + "group": "Wetness", + "name": "WetShore" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Wet below Z", + "description": "The ground is wet under this Z position (for example, the Z of a 3D water object).", + "group": "Wetness", + "name": "WetShoreZ" + }, + { + "value": "60", + "type": "Number", + "unit": "Pixel", + "label": "Wet shore fade", + "description": "The ground becomes dry over this height above \"Wet below Z\".", + "group": "Wetness", + "name": "WetShoreFade" + }, + { + "value": "3", + "type": "Choice", + "label": "Layer on steep slopes", + "description": "This layer is shown where the ground is steep, whatever is painted.", + "group": "Layers", + "choices": [ + { + "label": "None", + "value": "0" + }, + { + "label": "Layer 1", + "value": "1" + }, + { + "label": "Layer 2", + "value": "2" + }, + { + "label": "Layer 3", + "value": "3" + }, + { + "label": "Layer 4", + "value": "4" + } + ], + "name": "CliffLayer" + }, + { + "value": "45", + "type": "Number", + "label": "Steep slope angle", + "unit": "DegreeAngle", + "group": "Layers", + "name": "CliffAngle" + }, + { + "value": "true", + "type": "Boolean", + "label": "Cast shadows", + "group": "Rendering", + "name": "CastShadow" + }, + { + "value": "true", + "type": "Boolean", + "label": "Receive shadows", + "group": "Rendering", + "name": "ReceiveShadow" + }, + { + "value": "", + "type": "String", + "label": "Sculpted and painted data", + "description": "Written by the sculpt and paint tools of the scene editor, in a compact format not meant to be read or written by hand: use Edits instead. Clear it to go back to the relief, heightmap and edits.", + "advanced": true, + "name": "SculptData" + } + ] + }, + { + "areaMaxX": 1000, + "areaMaxY": 1000, + "areaMaxZ": 40, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "defaultName": "Grass", + "description": "A field of grass blades swaying in the wind and pushed aside by characters. They grow in the area of the object, on the highest 3D terrain at each point (one field can cover several terrains), or at the Z position of the object where there is no terrain.", + "fullName": "Terrain grass", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0zIDIxYzEtNSAxLTkgMC0xMyAyIDMgMyA3IDMgMTN6bTQgMGMwLTYgMS0xMSA0LTE2LTEgNS0xIDEwIDAgMTZ6bTUgMGMwLTUgMi05IDUtMTItMiA0LTIgOC0xIDEyem01IDBjMS00IDItNiA0LTgtMSAzLTEgNS0xIDh6Ii8+PC9zdmc+", + "is3D": true, + "isUsingLegacyInstancesRenderer": true, + "name": "TerrainGrass", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/732ef90b7fcf5dd9171fe95d0cf262e09159b487304915a4693baa893d9ce16c_grass.svg", + "objects": [ + { + "assetStoreId": "", + "name": "AreaPlaceholder", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 1000, + "height": 1000, + "depth": 40, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "Basic", + "tint": "255;255;255", + "frontFaceResourceName": "", + "frontFaceVisible": false, + "frontFaceResourceRepeat": false, + "backFaceResourceName": "", + "backFaceVisible": false, + "backFaceResourceRepeat": false, + "leftFaceResourceName": "", + "leftFaceVisible": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceName": "", + "rightFaceVisible": false, + "rightFaceResourceRepeat": false, + "topFaceResourceName": "", + "topFaceVisible": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceName": "", + "bottomFaceVisible": false, + "bottomFaceResourceRepeat": false + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "AreaPlaceholder" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [ + { + "angle": 0, + "customSize": true, + "width": 1000, + "height": 1000, + "depth": 40, + "keepRatio": true, + "layer": "", + "name": "AreaPlaceholder", + "persistentUuid": "5d7a8b1e-3c2f-4e6a-9b1d-001000001000", + "x": 0, + "y": 0, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": {}, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Terrain3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Terrain3D::DefineGrassHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "for (const child of object.getChildrenContainer().getAdhocListOfAllInstances()) {", + " child.deleteFromScene();", + "}", + "if (object.__terrainGrass) object.__terrainGrass.dispose();", + "object.__terrainGrass = new gdjs.__terrainGrassExtension.Grass(object);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::TerrainGrass" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDestroy", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrainGrass.dispose();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::TerrainGrass" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const game = runtimeScene.getGame();", + "// Time doesn't pass in the scene editor: real time is used to show the wind.", + "const isInGameEdition = game.isInGameEdition && game.isInGameEdition();", + "const elapsedTime = isInGameEdition ? 1 / 60 : object.getElapsedTime() / 1000;", + "const time = isInGameEdition", + " ? performance.now() / 1000", + " : object.getRuntimeScene().getTimeManager().getTimeFromStart() / 1000;", + "const layer = object.getInstanceContainer().getLayer(object.getLayer());", + "object.__terrainGrass.update(elapsedTime, time, layer.getRenderer().getThreeCamera());" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::TerrainGrass" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrainGrass.updateFromProperties();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::TerrainGrass" + } + ], + "objectGroups": [] + } + ], + "propertyDescriptors": [ + { + "value": "60", + "type": "Number", + "label": "Density", + "description": "The number of blades in a square of 100 x 100 pixels.", + "group": "Blades", + "name": "Density" + }, + { + "value": "36", + "type": "Number", + "label": "Blade height", + "unit": "Pixel", + "description": "The average height of blades (each blade is between 60% and 140% of it).", + "group": "Blades", + "name": "BladeHeight" + }, + { + "value": "5", + "type": "Number", + "label": "Blade width", + "unit": "Pixel", + "group": "Blades", + "name": "BladeWidth" + }, + { + "value": "52;104;36", + "type": "Color", + "label": "Base color", + "group": "Blades", + "name": "BaseColor" + }, + { + "value": "150;196;82", + "type": "Color", + "label": "Tip color", + "group": "Blades", + "name": "TipColor" + }, + { + "value": "1", + "type": "Number", + "label": "Seed", + "description": "Each seed places blades differently.", + "group": "Blades", + "name": "Seed" + }, + { + "value": "1", + "type": "Choice", + "label": "Grows on terrain layer", + "description": "On a 3D terrain, blades only grow where this layer is painted. A new terrain has layer 1 painted everywhere.", + "group": "Ground", + "choices": [ + { + "label": "Any", + "value": "0" + }, + { + "label": "Layer 1", + "value": "1" + }, + { + "label": "Layer 2", + "value": "2" + }, + { + "label": "Layer 3", + "value": "3" + }, + { + "label": "Layer 4", + "value": "4" + } + ], + "name": "GroundLayer" + }, + { + "value": "40", + "type": "Number", + "label": "Maximum slope", + "unit": "DegreeAngle", + "description": "No blades grow on steeper ground.", + "group": "Ground", + "name": "MaxSlope" + }, + { + "value": "1", + "type": "Number", + "label": "Wind strength", + "description": "0 for no wind.", + "group": "Wind", + "name": "WindStrength" + }, + { + "value": "2", + "type": "Number", + "label": "Wind speed", + "group": "Wind", + "name": "WindSpeed" + }, + { + "value": "", + "type": "String", + "label": "Object bending the grass", + "description": "The name of an object pushing blades aside when walking through the grass, like the player. The 8 first instances are used.", + "group": "Wind", + "name": "BendingObject" + }, + { + "value": "2500", + "type": "Number", + "label": "Fade distance", + "unit": "Pixel", + "description": "Blades are less and less dense up to this distance from the camera, and hidden after.", + "group": "Rendering", + "name": "FadeDistance" + } + ] + }, + { + "areaMaxX": 2000, + "areaMaxY": 2000, + "areaMaxZ": 200, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "defaultName": "ScatteredModels", + "assetStoreTag": "3d scattered models", + "description": "Many copies of a 3D model (trees, rocks, bushes, flowers...) placed in the area of the object and drawn all at once, which is much faster than an object for each one. They stand on the highest 3D terrain at each point, or at the Z position of the object where there is no terrain. Paint where they grow with the brushes of the scene editor.", + "fullName": "Scattered models (forests, rocks...)", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0xMCAyMXYtM0gzbDUtNUg1bDUtNUg3bDUtNSA1IDVoLTNsNSA1aC0zbDUgNWgtN3YzeiIvPjwvc3ZnPg==", + "is3D": true, + "isUsingLegacyInstancesRenderer": true, + "name": "ScatteredModels", + "previewIconUrl": "", + "objects": [ + { + "assetStoreId": "", + "name": "AreaPlaceholder", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 2000, + "height": 2000, + "depth": 200, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "Basic", + "tint": "255;255;255", + "frontFaceResourceName": "", + "frontFaceVisible": false, + "frontFaceResourceRepeat": false, + "backFaceResourceName": "", + "backFaceVisible": false, + "backFaceResourceRepeat": false, + "leftFaceResourceName": "", + "leftFaceVisible": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceName": "", + "rightFaceVisible": false, + "rightFaceResourceRepeat": false, + "topFaceResourceName": "", + "topFaceVisible": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceName": "", + "bottomFaceVisible": false, + "bottomFaceResourceRepeat": false + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "AreaPlaceholder" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [ + { + "angle": 0, + "customSize": true, + "width": 2000, + "height": 2000, + "depth": 200, + "keepRatio": true, + "layer": "", + "name": "AreaPlaceholder", + "persistentUuid": "5d7a8b1e-3c2f-4e6a-9b1d-002000002000", + "x": 0, + "y": 0, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": {}, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Terrain3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Terrain3D::DefineScatterHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "for (const child of object.getChildrenContainer().getAdhocListOfAllInstances()) {", + " child.deleteFromScene();", + "}", + "if (object.__terrainScatter) object.__terrainScatter.dispose();", + "object.__terrainScatter = new gdjs.__terrainScatterExtension.Scatter(object);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDestroy", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrainScatter.dispose();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const game = runtimeScene.getGame();", + "const isInGameEdition = game.isInGameEdition && game.isInGameEdition();", + "const elapsedTime = isInGameEdition ? 1 / 60 : object.getElapsedTime() / 1000;", + "const layer = object.getInstanceContainer().getLayer(object.getLayer());", + "object.__terrainScatter.update(elapsedTime, layer.getRenderer().getThreeCamera());" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrainScatter.onHotReloading();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + } + ], + "objectGroups": [] + }, + { + "fullName": "Add copies", + "functionType": "Action", + "name": "AddCopies", + "sentence": "Add copies of _PARAM0_ around _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "object.__terrainScatter.addCopies(x, y, radius);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Paint the density of copies around a position, up to the density of the object (copies removed with \"Erase copies\" come back). The effect fades out up to the radius.", + "group": "Paint" + }, + { + "fullName": "Erase copies", + "functionType": "Action", + "name": "EraseCopies", + "sentence": "Erase the copies of _PARAM0_ in a circle at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "object.__terrainScatter.removeCopies(x, y, radius);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Remove the copies standing in a circle, for example a tree cut by the player (use the position of the nearest copy and a small radius) or a forest cleared for a building. They come back when the scene is restarted.", + "group": "Paint" + }, + { + "fullName": "Number of copies", + "functionType": "ExpressionAndCondition", + "name": "CopyCount", + "sentence": "the number of copies", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "eventsFunctionContext.returnValue = object.__terrainScatter.getCopyCount();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + } + ], + "objectGroups": [], + "description": "the number of copies placed in the area.", + "group": "Copies", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "X position of the nearest copy", + "functionType": "Expression", + "name": "NearestCopyX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const copy = object.__terrainScatter.findNearestCopy(x, y);", + "eventsFunctionContext.returnValue = copy ? copy.x : x;" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "The X position of the base of the copy nearest to a position (the position itself if there is no copy).", + "group": "Copies", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Y position of the nearest copy", + "functionType": "Expression", + "name": "NearestCopyY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const copy = object.__terrainScatter.findNearestCopy(x, y);", + "eventsFunctionContext.returnValue = copy ? copy.y : y;" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "The Y position of the base of the copy nearest to a position (the position itself if there is no copy).", + "group": "Copies", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Z position of the nearest copy", + "functionType": "Expression", + "name": "NearestCopyZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const copy = object.__terrainScatter.findNearestCopy(x, y);", + "eventsFunctionContext.returnValue = copy ? copy.z : object.getZ();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "The Z position of the base of the copy nearest to a position (on the ground).", + "group": "Copies", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Distance to the nearest copy", + "functionType": "ExpressionAndCondition", + "name": "NearestCopyDistance", + "sentence": "the distance from _PARAM1_; _PARAM2_ to the nearest copy", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const copy = object.__terrainScatter.findNearestCopy(x, y);", + "eventsFunctionContext.returnValue = copy ? Math.hypot(copy.x - x, copy.y - y) : Number.MAX_VALUE;" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the distance (on X and Y) from a position to the base of the nearest copy. It's a very big number when there is no copy.", + "group": "Copies", + "expressionType": { + "type": "expression" + } + } + ], + "propertyDescriptors": [ + { + "value": "", + "type": "Resource", + "label": "3D model", + "description": "The model copied everywhere (a .glb file). Models standing on the Y axis, like most models of the asset store, are stood up along Z.", + "group": "Model", + "extraInformation": [ + "model3D" + ], + "name": "Model" + }, + { + "value": "300", + "type": "Number", + "label": "Height of a copy", + "unit": "Pixel", + "description": "Copies keep the proportions of the model.", + "group": "Model", + "name": "Size" + }, + { + "value": "0.8", + "type": "Number", + "label": "Smallest random scale", + "description": "Each copy is scaled at random between the smallest and the largest scale.", + "group": "Model", + "name": "MinScale" + }, + { + "value": "1.2", + "type": "Number", + "label": "Largest random scale", + "group": "Model", + "name": "MaxScale" + }, + { + "value": "true", + "type": "Boolean", + "label": "Random rotation", + "description": "Turn each copy around the Z axis at random.", + "group": "Model", + "name": "RandomRotation" + }, + { + "value": "255;255;255", + "type": "Color", + "label": "Random tint", + "description": "Each copy is tinted between white and this color, at random. White for no tint.", + "group": "Model", + "name": "RandomTint" + }, + { + "value": "0.3", + "type": "Number", + "label": "Density", + "description": "The number of copies in a square of 100 x 100 pixels where the density is fully painted (0.3 is about one copy every 180 pixels). Copies never overlap their neighbors much: they are placed at random in a grid.", + "group": "Placement", + "name": "Density" + }, + { + "value": "1", + "type": "Number", + "label": "Seed", + "description": "Each seed places copies differently.", + "group": "Placement", + "name": "Seed" + }, + { + "value": "Everywhere", + "type": "Choice", + "label": "Copies grow", + "description": "Everywhere in the area, or only where they are painted with the \"Add copies\" brush (or the edits).", + "group": "Placement", + "choices": [ + { + "label": "Everywhere (erase them with the brush)", + "value": "Everywhere" + }, + { + "label": "Only where painted", + "value": "Painted" + } + ], + "name": "Coverage" + }, + { + "value": "1", + "type": "Choice", + "label": "Grows on terrain layer", + "description": "On a 3D terrain, copies only grow where this layer is painted, away from its borders (so that trees don't stand on roads painted with another layer). A new terrain has layer 1 painted everywhere.", + "group": "Ground", + "choices": [ + { + "label": "Any", + "value": "0" + }, + { + "label": "Layer 1", + "value": "1" + }, + { + "label": "Layer 2", + "value": "2" + }, + { + "label": "Layer 3", + "value": "3" + }, + { + "label": "Layer 4", + "value": "4" + } + ], + "name": "GroundLayer" + }, + { + "value": "30", + "type": "Number", + "label": "Maximum slope", + "unit": "DegreeAngle", + "description": "No copies grow on steeper ground.", + "group": "Ground", + "name": "MaxSlope" + }, + { + "value": "-100000", + "type": "Number", + "label": "Minimum ground Z", + "unit": "Pixel", + "description": "No copies grow where the ground is lower (for example, under the water level).", + "group": "Ground", + "name": "MinGroundZ" + }, + { + "value": "100000", + "type": "Number", + "label": "Maximum ground Z", + "unit": "Pixel", + "description": "No copies grow where the ground is higher (for example, above the tree line).", + "group": "Ground", + "name": "MaxGroundZ" + }, + { + "value": "false", + "type": "Boolean", + "label": "Trunk collisions", + "description": "Give each copy a static collision shape around its trunk, for the 3D physics behavior (add it to this object, with the \"Static\" type) and navigation meshes. Without them, these behaviors have no effect on this object.", + "group": "Collisions", + "name": "TrunkCollisions" + }, + { + "value": "30", + "type": "Number", + "label": "Trunk diameter", + "unit": "Pixel", + "description": "The diameter of the collision shape of a copy at the height given in \"Height of a copy\" (it is scaled with the copy).", + "group": "Collisions", + "name": "TrunkDiameter" + }, + { + "value": "8000", + "type": "Number", + "label": "Draw distance", + "unit": "Pixel", + "description": "Copies shrink and are hidden at this distance from the camera.", + "group": "Rendering", + "name": "DrawDistance" + }, + { + "value": "true", + "type": "Boolean", + "label": "Cast shadows", + "group": "Rendering", + "name": "CastShadow" + }, + { + "value": "true", + "type": "Boolean", + "label": "Receive shadows", + "group": "Rendering", + "name": "ReceiveShadow" + }, + { + "value": "", + "type": "MultilineString", + "label": "Edits", + "description": "Where copies are added or erased, applied in order, as a JSON list, for example [{\"tool\": \"add\", \"x\": 0.3, \"y\": 0.4, \"radius\": 0.15}, {\"tool\": \"erase\", \"x\": 0.1, \"y\": 0.9, \"toX\": 0.8, \"toY\": 0.2, \"radius\": 0.02}]. Tools: add (density: from 0 to 1, 1 by default, how dense copies are compared to the Density property) and erase. strength (from 0 to 1) makes a stroke lighter. x, y and radius are fractions (0 to 1) of the object size (x is (scene X - object X) / object width). Add toX and toY to paint along a line (a row of trees, a path through a forest). With \"Copies grow: only where painted\", only the added areas have copies. Shown in the scene editor.", + "group": "Placement", + "advanced": true, + "name": "Edits" + }, + { + "value": "", + "type": "String", + "label": "Painted data", + "description": "Written by the brushes of the scene editor, in a compact format not meant to be read or written by hand: use Edits instead. Clear it to go back to the coverage and the edits.", + "advanced": true, + "name": "PaintData" + } + ] + } + ] + } + ], + "externalLayouts": [] +} diff --git a/examples/starting-expanded-3d-driving/theme-slots.json b/examples/starting-expanded-3d-driving/theme-slots.json new file mode 100644 index 000000000..55e57bb03 --- /dev/null +++ b/examples/starting-expanded-3d-driving/theme-slots.json @@ -0,0 +1,57 @@ +{ + "version": 1, + "objects": { + "scene:Game Scene/Road_3D": "env.road", + "scene:Game Scene/PlayerCar": "vehicle.car", + "scene:Game Scene/TrafficCone": "prop.obstacle", + "scene:Game Scene/Barrier": "prop.obstacle", + "scene:Game Scene/House_A": "building.tower", + "scene:Game Scene/House_B": "building.tower", + "scene:Game Scene/House_C": "building.tower", + "scene:Game Scene/House_D": "building.tower", + "scene:Game Scene/Trees": { + "Model": "nature.tree" + }, + "scene:Game Scene/OakTrees": { + "Model": "nature.tree" + }, + "scene:Game Scene/PineTrees": { + "Model": "nature.tree" + }, + "scene:Game Scene/Rocks": { + "Model": "nature.rock.big" + }, + "scene:Game Scene/Terrain": { + "Layer1Texture": { + "slot": "terrain.ground", + "normalMap": "Layer1NormalMap" + }, + "Layer2Texture": { + "slot": "terrain.road", + "normalMap": "Layer2NormalMap" + }, + "Layer3Texture": { + "slot": "terrain.rock", + "normalMap": "Layer3NormalMap" + }, + "Layer4Texture": { + "slot": "terrain.dirt", + "normalMap": "Layer4NormalMap" + } + }, + "scene:Game Scene/Grass": { + "BaseColor": "terrain.grass.base", + "TipColor": "terrain.grass.tip" + } + }, + "effects": { + "effect:Game Scene//SkyBox": "sky.day" + }, + "ignoredObjects": [ + "scene:Game Scene/StreetLight", + "scene:Game Scene/MapLimit", + "object:Terrain3D::Terrain3D/AreaPlaceholder", + "object:Terrain3D::TerrainGrass/AreaPlaceholder", + "object:Terrain3D::ScatteredModels/AreaPlaceholder" + ] +} diff --git a/examples/starting-expanded-3d-grand-theft-auto/README.md b/examples/starting-expanded-3d-grand-theft-auto/README.md new file mode 100644 index 000000000..41b50ae17 --- /dev/null +++ b/examples/starting-expanded-3d-grand-theft-auto/README.md @@ -0,0 +1,3 @@ +# 3D GTA Expanded + +A 3D open world made with a 3D terrain, where the player can walk, jump, get into a car and drive through a town, farms, forests and a tunnel through a hill, using keyboard or mobile controls. The cars use physics-based movement and can smash into other cars and obstacles. A starting point for open-world driving and action games inspired by Grand Theft Auto. diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/A_button.png b/examples/starting-expanded-3d-grand-theft-auto/assets/A_button.png similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/A_button.png rename to examples/starting-expanded-3d-grand-theft-auto/assets/A_button.png diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Barrier.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/Barrier.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Barrier.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/Barrier.glb diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Building_A.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/Building_A.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Building_A.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/Building_A.glb diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Building_C.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/Building_C.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Building_C.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/Building_C.glb diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Building_E.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/Building_E.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Building_E.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/Building_E.glb diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Building_G.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/Building_G.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Building_G.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/Building_G.glb diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Building_H.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/Building_H.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Building_H.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/Building_H.glb diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Building_K.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/Building_K.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Building_K.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/Building_K.glb diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Bush1.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/Bush1.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Bush1.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/Bush1.glb diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Cone.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/Cone.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Cone.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/Cone.glb diff --git a/examples/starting-expanded-3d-grand-theft-auto/assets/Flat dark joystick border horizontal.png b/examples/starting-expanded-3d-grand-theft-auto/assets/Flat dark joystick border horizontal.png new file mode 100644 index 000000000..4f772d873 Binary files /dev/null and b/examples/starting-expanded-3d-grand-theft-auto/assets/Flat dark joystick border horizontal.png differ diff --git a/examples/starting-expanded-3d-grand-theft-auto/assets/Flat dark joystick border vertical.png b/examples/starting-expanded-3d-grand-theft-auto/assets/Flat dark joystick border vertical.png new file mode 100644 index 000000000..ec315d4b6 Binary files /dev/null and b/examples/starting-expanded-3d-grand-theft-auto/assets/Flat dark joystick border vertical.png differ diff --git a/examples/starting-expanded-3d-grand-theft-auto/assets/Flat dark joystick border.png b/examples/starting-expanded-3d-grand-theft-auto/assets/Flat dark joystick border.png new file mode 100644 index 000000000..b4e83e112 Binary files /dev/null and b/examples/starting-expanded-3d-grand-theft-auto/assets/Flat dark joystick border.png differ diff --git a/examples/starting-expanded-3d-grand-theft-auto/assets/Flat dark joystick thumb.png b/examples/starting-expanded-3d-grand-theft-auto/assets/Flat dark joystick thumb.png new file mode 100644 index 000000000..8acf179f8 Binary files /dev/null and b/examples/starting-expanded-3d-grand-theft-auto/assets/Flat dark joystick thumb.png differ diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/House_A.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/House_A.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/House_A.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/House_A.glb diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/House_D.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/House_D.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/House_D.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/House_D.glb diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/House_N.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/House_N.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/House_N.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/House_N.glb diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/House_T.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/House_T.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/House_T.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/House_T.glb diff --git a/examples/starting-expanded-3d-grand-theft-auto/assets/Orange - Sedan Sports.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/Orange - Sedan Sports.glb new file mode 100644 index 000000000..5acb9bb41 Binary files /dev/null and b/examples/starting-expanded-3d-grand-theft-auto/assets/Orange - Sedan Sports.glb differ diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/PineTree1.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/PineTree1.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/PineTree1.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/PineTree1.glb diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/PineTree2.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/PineTree2.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/PineTree2.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/PineTree2.glb diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/RoadMarking.png b/examples/starting-expanded-3d-grand-theft-auto/assets/RoadMarking.png similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/RoadMarking.png rename to examples/starting-expanded-3d-grand-theft-auto/assets/RoadMarking.png diff --git a/examples/starting-expanded-3d-grand-theft-auto/assets/Skybox_Back.png b/examples/starting-expanded-3d-grand-theft-auto/assets/Skybox_Back.png new file mode 100644 index 000000000..16e34f3da Binary files /dev/null and b/examples/starting-expanded-3d-grand-theft-auto/assets/Skybox_Back.png differ diff --git a/examples/starting-expanded-3d-grand-theft-auto/assets/Skybox_Bottom.png b/examples/starting-expanded-3d-grand-theft-auto/assets/Skybox_Bottom.png new file mode 100644 index 000000000..aa0891ef2 Binary files /dev/null and b/examples/starting-expanded-3d-grand-theft-auto/assets/Skybox_Bottom.png differ diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Skybox_Front.png b/examples/starting-expanded-3d-grand-theft-auto/assets/Skybox_Front.png similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Skybox_Front.png rename to examples/starting-expanded-3d-grand-theft-auto/assets/Skybox_Front.png diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Skybox_Left.png b/examples/starting-expanded-3d-grand-theft-auto/assets/Skybox_Left.png similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Skybox_Left.png rename to examples/starting-expanded-3d-grand-theft-auto/assets/Skybox_Left.png diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Skybox_Right.png b/examples/starting-expanded-3d-grand-theft-auto/assets/Skybox_Right.png similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Skybox_Right.png rename to examples/starting-expanded-3d-grand-theft-auto/assets/Skybox_Right.png diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Skybox_Top.png b/examples/starting-expanded-3d-grand-theft-auto/assets/Skybox_Top.png similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Skybox_Top.png rename to examples/starting-expanded-3d-grand-theft-auto/assets/Skybox_Top.png diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Skyscraper_A.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/Skyscraper_A.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Skyscraper_A.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/Skyscraper_A.glb diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Skyscraper_C.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/Skyscraper_C.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Skyscraper_C.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/Skyscraper_C.glb diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Skyscraper_E.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/Skyscraper_E.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Skyscraper_E.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/Skyscraper_E.glb diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/StarterPushable.png b/examples/starting-expanded-3d-grand-theft-auto/assets/StarterPushable.png similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/StarterPushable.png rename to examples/starting-expanded-3d-grand-theft-auto/assets/StarterPushable.png diff --git a/examples/starting-expanded-3d-grand-theft-auto/assets/StarterToggle_Keyboard.png b/examples/starting-expanded-3d-grand-theft-auto/assets/StarterToggle_Keyboard.png new file mode 100644 index 000000000..aabe87250 Binary files /dev/null and b/examples/starting-expanded-3d-grand-theft-auto/assets/StarterToggle_Keyboard.png differ diff --git a/examples/starting-expanded-3d-grand-theft-auto/assets/StarterToggle_Touch.png b/examples/starting-expanded-3d-grand-theft-auto/assets/StarterToggle_Touch.png new file mode 100644 index 000000000..204462798 Binary files /dev/null and b/examples/starting-expanded-3d-grand-theft-auto/assets/StarterToggle_Touch.png differ diff --git a/examples/starting-expanded-3d-grand-theft-auto/assets/StartingCapsule.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/StartingCapsule.glb new file mode 100644 index 000000000..39ee5b589 Binary files /dev/null and b/examples/starting-expanded-3d-grand-theft-auto/assets/StartingCapsule.glb differ diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/StreetLight.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/StreetLight.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/StreetLight.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/StreetLight.glb diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/TerrainAsphalt.jpg b/examples/starting-expanded-3d-grand-theft-auto/assets/TerrainAsphalt.jpg similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/TerrainAsphalt.jpg rename to examples/starting-expanded-3d-grand-theft-auto/assets/TerrainAsphalt.jpg diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/TerrainAsphaltNormal.jpg b/examples/starting-expanded-3d-grand-theft-auto/assets/TerrainAsphaltNormal.jpg similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/TerrainAsphaltNormal.jpg rename to examples/starting-expanded-3d-grand-theft-auto/assets/TerrainAsphaltNormal.jpg diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/TerrainConcrete.jpg b/examples/starting-expanded-3d-grand-theft-auto/assets/TerrainConcrete.jpg similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/TerrainConcrete.jpg rename to examples/starting-expanded-3d-grand-theft-auto/assets/TerrainConcrete.jpg diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/TerrainConcreteNormal.jpg b/examples/starting-expanded-3d-grand-theft-auto/assets/TerrainConcreteNormal.jpg similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/TerrainConcreteNormal.jpg rename to examples/starting-expanded-3d-grand-theft-auto/assets/TerrainConcreteNormal.jpg diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/TerrainGrass.jpg b/examples/starting-expanded-3d-grand-theft-auto/assets/TerrainGrass.jpg similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/TerrainGrass.jpg rename to examples/starting-expanded-3d-grand-theft-auto/assets/TerrainGrass.jpg diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/TerrainGrassNormal.jpg b/examples/starting-expanded-3d-grand-theft-auto/assets/TerrainGrassNormal.jpg similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/TerrainGrassNormal.jpg rename to examples/starting-expanded-3d-grand-theft-auto/assets/TerrainGrassNormal.jpg diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/TerrainRock.jpg b/examples/starting-expanded-3d-grand-theft-auto/assets/TerrainRock.jpg similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/TerrainRock.jpg rename to examples/starting-expanded-3d-grand-theft-auto/assets/TerrainRock.jpg diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/TerrainRockNormal.jpg b/examples/starting-expanded-3d-grand-theft-auto/assets/TerrainRockNormal.jpg similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/TerrainRockNormal.jpg rename to examples/starting-expanded-3d-grand-theft-auto/assets/TerrainRockNormal.jpg diff --git a/examples/starting-expanded-3d-grand-theft-auto/assets/Top arrow button.png b/examples/starting-expanded-3d-grand-theft-auto/assets/Top arrow button.png new file mode 100644 index 000000000..e69188046 Binary files /dev/null and b/examples/starting-expanded-3d-grand-theft-auto/assets/Top arrow button.png differ diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/TownTree.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/TownTree.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/TownTree.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/TownTree.glb diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/assets/Tunnel.glb b/examples/starting-expanded-3d-grand-theft-auto/assets/Tunnel.glb similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/assets/Tunnel.glb rename to examples/starting-expanded-3d-grand-theft-auto/assets/Tunnel.glb diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/preview.png b/examples/starting-expanded-3d-grand-theft-auto/preview.png similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/preview.png rename to examples/starting-expanded-3d-grand-theft-auto/preview.png diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/starting-3d-grand-theft-auto-with-a-map.json b/examples/starting-expanded-3d-grand-theft-auto/starting-expanded-3d-grand-theft-auto.json similarity index 97% rename from examples/starting-3d-grand-theft-auto-with-a-map/starting-3d-grand-theft-auto-with-a-map.json rename to examples/starting-expanded-3d-grand-theft-auto/starting-expanded-3d-grand-theft-auto.json index f4473bf02..6181db416 100644 --- a/examples/starting-3d-grand-theft-auto-with-a-map/starting-3d-grand-theft-auto-with-a-map.json +++ b/examples/starting-expanded-3d-grand-theft-auto/starting-expanded-3d-grand-theft-auto.json @@ -19,7 +19,7 @@ "sizeOnStartupMode": "", "templateSlug": "", "version": "1.0.0", - "name": "3D GTA with a map", + "name": "3D GTA Expanded", "description": "A 3D open world with a town, roads, farms and a hill, made with a 3D terrain, where you can walk, jump, get into cars, drive around and smash into obstacles.", "author": "", "windowWidth": 1280, @@ -548,50 +548,14 @@ { "angle": 0, "customSize": true, - "height": 1700, - "width": 2700, + "height": 8192, + "width": 8192, "layer": "", "locked": false, "name": "Grass", "persistentUuid": "893e705d-fc00-584b-b004-12c68431ca50", - "x": 400, - "y": 650, - "z": 0, - "zOrder": 1, - "numberProperties": [], - "stringProperties": [], - "initialVariables": [], - "depth": 40 - }, - { - "angle": 0, - "customSize": true, - "height": 1300, - "width": 2200, - "layer": "", - "locked": false, - "name": "Grass", - "persistentUuid": "185f9177-8c13-58b1-a36b-eebb2db2b019", - "x": 1100, - "y": -1400, - "z": 0, - "zOrder": 1, - "numberProperties": [], - "stringProperties": [], - "initialVariables": [], - "depth": 40 - }, - { - "angle": 0, - "customSize": true, - "height": 940, - "width": 940, - "layer": "", - "locked": false, - "name": "Grass", - "persistentUuid": "58a89c9c-95e7-52ca-8a86-74d938bc35b0", - "x": -2970, - "y": -1270, + "x": -4096, + "y": -4096, "z": 0, "zOrder": 1, "numberProperties": [], @@ -4621,9 +4585,6 @@ } ] }, - { - "objectName": "PushableBox" - }, { "objectName": "ControlsToggle" }, @@ -4662,15 +4623,6 @@ { "objectName": "MapLimit" }, - { - "objectName": "PineForest" - }, - { - "objectName": "PineForest2" - }, - { - "objectName": "Bushes" - }, { "objectName": "Tunnel" } @@ -4731,6 +4683,9 @@ }, { "objectName": "Barrier" + }, + { + "objectName": "PushableBox" } ] }, @@ -4739,6 +4694,15 @@ "children": [ { "objectName": "TownTree" + }, + { + "objectName": "PineForest" + }, + { + "objectName": "PineForest2" + }, + { + "objectName": "Bushes" } ] } @@ -4756,7 +4720,7 @@ "type": "BuiltinCommonInstructions::Group", "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -4772,7 +4736,7 @@ "actions": [], "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Comment", "color": { "b": 109, @@ -4785,7 +4749,7 @@ "comment": "Set to Touch" }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -4841,7 +4805,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Comment", "color": { "b": 109, @@ -4854,7 +4818,7 @@ "comment": "Set to Keyboard" }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -4944,7 +4908,7 @@ "type": "BuiltinCommonInstructions::Group", "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Comment", "color": { "b": 109, @@ -4957,7 +4921,7 @@ "comment": "Enter Vehicle" }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -4975,7 +4939,7 @@ "actions": [], "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -5064,7 +5028,7 @@ ], "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Comment", "color": { "b": 109, @@ -5077,7 +5041,7 @@ "comment": "Create Driven_Vehicle" }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [], "actions": [ @@ -5129,7 +5093,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Comment", "color": { "b": 109, @@ -5142,7 +5106,7 @@ "comment": "If no car in range" }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Else", "conditions": [], "actions": [ @@ -5162,7 +5126,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Comment", "color": { "b": 109, @@ -5175,7 +5139,7 @@ "comment": "Exit Driven_Vehicle" }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -5193,7 +5157,7 @@ "actions": [], "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [], "actions": [ @@ -5283,7 +5247,7 @@ ], "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Comment", "color": { "b": 109, @@ -5296,7 +5260,7 @@ "comment": "Create Vehicle" }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [], "actions": [ @@ -5363,7 +5327,7 @@ "type": "BuiltinCommonInstructions::Group", "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -5381,7 +5345,7 @@ "actions": [], "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Comment", "color": { "b": 109, @@ -5394,7 +5358,7 @@ "comment": "Clamp camera angle" }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [], "actions": [ @@ -5424,7 +5388,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Comment", "color": { "b": 109, @@ -5437,7 +5401,7 @@ "comment": "Move camera when pointer locks" }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -5497,7 +5461,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Comment", "color": { "b": 109, @@ -5510,7 +5474,7 @@ "comment": "Prevent 3rd person auto-rotation while camera is being controlled" }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -5540,7 +5504,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Comment", "color": { "b": 109, @@ -5553,7 +5517,7 @@ "comment": "Prevent camera rotation when Player moves backwards" }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [], "actions": [ @@ -5572,7 +5536,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -5619,7 +5583,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -5664,7 +5628,7 @@ "type": "BuiltinCommonInstructions::Group", "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -5745,7 +5709,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -5762,7 +5726,7 @@ "actions": [], "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -5780,7 +5744,7 @@ "actions": [], "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Comment", "color": { "b": 109, @@ -5793,7 +5757,7 @@ "comment": "Set mobile controls" }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -5836,7 +5800,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -5854,7 +5818,7 @@ "actions": [], "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Comment", "color": { "b": 109, @@ -5867,7 +5831,7 @@ "comment": "Set mobile controls" }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -5908,7 +5872,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -5927,7 +5891,7 @@ "actions": [], "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Comment", "color": { "b": 109, @@ -5940,7 +5904,7 @@ "comment": "Show hidden movement joystick" }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -5968,7 +5932,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Comment", "color": { "b": 109, @@ -5981,7 +5945,7 @@ "comment": "Request pointer lock when touching the screen" }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -6003,7 +5967,7 @@ "actions": [], "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -6058,7 +6022,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Comment", "color": { "b": 109, @@ -6071,7 +6035,7 @@ "comment": "Unlock the camera when the Player object is being moved" }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -6087,7 +6051,7 @@ "actions": [], "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -6117,7 +6081,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [], "actions": [ @@ -6136,7 +6100,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -6164,7 +6128,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Comment", "color": { "b": 109, @@ -6177,7 +6141,7 @@ "comment": "Try to enter/exit vehicle" }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -6194,7 +6158,7 @@ "actions": [], "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -6224,7 +6188,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Else", "conditions": [ { @@ -6271,7 +6235,7 @@ "type": "BuiltinCommonInstructions::Group", "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -6288,7 +6252,7 @@ "actions": [], "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Comment", "color": { "b": 109, @@ -6301,7 +6265,7 @@ "comment": "Request pointer lock when touching the screen" }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -6349,7 +6313,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Comment", "color": { "b": 109, @@ -6362,7 +6326,7 @@ "comment": "Unlock the camera when the Player object is being moved" }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -6401,7 +6365,7 @@ ], "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -6431,7 +6395,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [], "actions": [ @@ -6450,7 +6414,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -6476,7 +6440,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Comment", "color": { "b": 109, @@ -6489,7 +6453,7 @@ "comment": "Try to enter/exit vehicle" }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -6505,7 +6469,7 @@ "actions": [], "events": [ { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Standard", "conditions": [ { @@ -6535,7 +6499,7 @@ ] }, { - "folded": true, + "folded": false, "type": "BuiltinCommonInstructions::Else", "conditions": [ { @@ -7099,475 +7063,6 @@ "harness.assert(carAfter.behaviors.PhysicsCar3D.state.IsOnFloor === true, 'Driven_Car stays on the ground while driving.');", "await harness.takeScreenshot('after driving');" ] - }, - { - "name": "The player and the cars start on the ground of the town", - "type": "gameplay", - "description": "Verifies the player and the cars rest on the 3D terrain after the start of the game.", - "source": [ - "await harness.goToScene('Game Scene');", - "// Faster in the software renderer used to run tests (grass is thousands of blades).", - "harness.setGameResolutionSize(160, 90);", - "harness.getCurrentRuntimeScene().getLayer('').setEffectBooleanParameter('Sun light', 'isCastingShadow', false);", - "for (const grass of harness.getCurrentRuntimeScene().getObjects('Grass')) grass.hide(true);", - "// 50ms frames: the longest frame the game accepts (minFPS is 20), so a second of game time costs 20 frames. CI renders in software and a 3D physics frame is slow there.", - "const DT = { dtMs: 50 };", - "const terrain = harness.getRuntimeObject(harness.getObjects('Terrain')[0].id).__terrain3D;", - "harness.assert(!!terrain, 'The terrain is created.');", - "await harness.stepFrames(20, DT);", - "const player = harness.getObjects('Player')[0];", - "const playerGroundZ = terrain.getHeightAt(player.centerX, player.centerY);", - "console.log('player z=' + player.z.toFixed(1) + ' ground=' + playerGroundZ.toFixed(1));", - "harness.assert(player.behaviors.PhysicsCharacter3D.state.IsOnFloor === true, 'The player stands on the ground.');", - "harness.assert(Math.abs(player.z - playerGroundZ) < 4, 'The player stands on the terrain (z=' + player.z.toFixed(1) + ', ground=' + playerGroundZ.toFixed(1) + ').');", - "const cars = harness.getObjects('Car');", - "harness.assert(cars.length >= 3, 'There are cars in the scene.');", - "for (const car of cars) {", - " const groundZ = terrain.getHeightAt(car.centerX, car.centerY);", - " console.log('car at ' + car.centerX.toFixed(0) + ';' + car.centerY.toFixed(0) + ' z=' + car.z.toFixed(1) + ' ground=' + groundZ.toFixed(1));", - " harness.assert(Math.abs(car.z - groundZ) < 20, 'The car at ' + car.centerX.toFixed(0) + ';' + car.centerY.toFixed(0) + ' rests on the terrain (z=' + car.z.toFixed(1) + ', ground=' + groundZ.toFixed(1) + ').');", - "}" - ] - }, - { - "name": "A car climbs the road of the hill", - "type": "gameplay", - "description": "Puts the driven car at the bottom of the road climbing the hill and holds W: the car climbs on its wheels.", - "source": [ - "await harness.goToScene('Game Scene');", - "// Faster in the software renderer used to run tests (grass is thousands of blades).", - "harness.setGameResolutionSize(160, 90);", - "harness.getCurrentRuntimeScene().getLayer('').setEffectBooleanParameter('Sun light', 'isCastingShadow', false);", - "for (const grass of harness.getCurrentRuntimeScene().getObjects('Grass')) grass.hide(true);", - "// 50ms frames: the longest frame the game accepts (minFPS is 20), so a second of game time costs 20 frames. CI renders in software and a 3D physics frame is slow there.", - "const DT = { dtMs: 50 };", - "await harness.stepFrames(3, DT);", - "const terrain = harness.getRuntimeObject(harness.getObjects('Terrain')[0].id).__terrain3D;", - "const getPlayer = () => harness.getObjects('Player')[0];", - "const car = harness.getObjects('Car').reduce((best, c) =>", - " Math.hypot(c.centerX - getPlayer().centerX, c.centerY - getPlayer().centerY) <", - " Math.hypot(best.centerX - getPlayer().centerX, best.centerY - getPlayer().centerY) ? c : best);", - "harness.setObjectPosition(getPlayer().id, car.centerX - 30, car.centerY, getPlayer().z);", - "await harness.stepFrames(3, DT);", - "harness.setKeyPressed('e', true);", - "await harness.stepFrames(2, DT);", - "harness.setKeyPressed('e', false);", - "await harness.stepFrames(3, DT);", - "const getCar = () => harness.getObjects('Driven_Car')[0];", - "harness.assert(!!getCar(), 'The player drives a car.');", - "// Put the car at the bottom of the straight part of the road climbing the hill (13% slope).", - "const roadX = 3447;", - "const roadY = -537;", - "harness.setObjectPosition(getCar().id, roadX, roadY, terrain.getHeightAt(roadX, roadY) + 20);", - "harness.getRuntimeObject(getCar().id).setAngle(-86);", - "await harness.stepFrames(10, DT);", - "const start = getCar();", - "harness.setKeyPressed('w', true);", - "await harness.stepFrames(60, DT);", - "harness.setKeyPressed('w', false);", - "const end = getCar();", - "const distance = Math.hypot(end.centerX - start.centerX, end.centerY - start.centerY);", - "console.log('drove ' + distance.toFixed(0) + ' from z=' + start.z.toFixed(0) + ' to z=' + end.z.toFixed(0) + ' at ' + end.centerX.toFixed(0) + ';' + end.centerY.toFixed(0));", - "harness.assert(distance > 150, 'The car drove up the road (' + distance.toFixed(0) + ').');", - "harness.assert(end.z - start.z > 15, 'The car climbed (from z=' + start.z.toFixed(0) + ' to z=' + end.z.toFixed(0) + ').');", - "harness.assert(end.behaviors.PhysicsCar3D.state.IsOnFloor === true, 'The car is still on its wheels.');" - ] - }, - { - "name": "The forests grow on the ground, away from roads and cliffs", - "type": "gameplay", - "description": "Checks the copies of the forests and bushes: they stand on the terrain, away from the asphalt and concrete layers and from slopes steeper than allowed, are the same after a restart, follow the ground when it is raised, and stand at the Z position of their object where there is no terrain.", - "source": [ - "await harness.goToScene('Game Scene');", - "// Faster in the software renderer used to run tests (grass is thousands of blades).", - "harness.setGameResolutionSize(160, 90);", - "harness.getCurrentRuntimeScene().getLayer('').setEffectBooleanParameter('Sun light', 'isCastingShadow', false);", - "for (const grass of harness.getCurrentRuntimeScene().getObjects('Grass')) grass.hide(true);", - "const DT = { dtMs: 50 };", - "await harness.stepFrames(2, DT);", - "const terrain = harness.getRuntimeObject('Terrain').__terrain3D;", - "const ASPHALT_LAYER = 2;", - "const CONCRETE_LAYER = 4;", - "const getForests = () => ['PineForest', 'PineForest2', 'Bushes'].map((name) => ({ name, object: harness.getRuntimeObject(name) }));", - "const describeCopies = () => getForests().map(({ name, object }) => name + ':' + object.__terrainScatter.getCopies().map((copy) => copy.x.toFixed(1) + ',' + copy.y.toFixed(1) + ',' + copy.z.toFixed(1)).join(';'));", - "for (const { name, object } of getForests()) {", - " const copies = object.__terrainScatter.getCopies();", - " let maxDistanceToGround = 0;", - " let maxSlope = 0;", - " let copiesOnRoads = 0;", - " for (const copy of copies) {", - " maxDistanceToGround = Math.max(maxDistanceToGround, Math.abs(copy.z - terrain.getHeightAt(copy.x, copy.y)));", - " maxSlope = Math.max(maxSlope, terrain.getSlopeAt(copy.x, copy.y));", - " if (terrain.getLayerWeightAt(copy.x, copy.y, ASPHALT_LAYER) > 0.5 || terrain.getLayerWeightAt(copy.x, copy.y, CONCRETE_LAYER) > 0.5) copiesOnRoads++;", - " }", - " console.log(name + ': ' + copies.length + ' copies, max slope ' + maxSlope.toFixed(1) + '°, max distance to the ground ' + maxDistanceToGround.toFixed(3));", - " harness.assert(copies.length > 50, name + ' has many copies (' + copies.length + ').');", - " harness.assert(maxDistanceToGround < 0.01, name + ' copies stand on the terrain.');", - " harness.assert(copiesOnRoads === 0, name + ' has no copies on roads and sidewalks (' + copiesOnRoads + ').');", - " harness.assert(maxSlope <= object._getMaxSlope() + 0.01, name + ' has no copies on steep slopes (' + maxSlope.toFixed(1) + '°).');", - "}", - "", - "// The same seed places the same copies.", - "const copiesBeforeRestart = describeCopies();", - "await harness.goToScene('Game Scene');", - "harness.setGameResolutionSize(160, 90);", - "await harness.stepFrames(2, DT);", - "const copiesAfterRestart = describeCopies();", - "harness.assert(copiesAfterRestart.join('|') === copiesBeforeRestart.join('|'), 'The copies are the same after restarting the scene.');", - "const counts = getForests().map(({ name, object }) => name + '=' + object.__terrainScatter.getCopyCount()).join(', ');", - "console.log('counts: ' + counts);", - "harness.assert(counts === 'PineForest=143, PineForest2=85, Bushes=141', 'The forests have the expected number of copies (' + counts + ').');", - "", - "// Copies follow the ground when it changes.", - "const restartedTerrain = harness.getRuntimeObject('Terrain').__terrain3D;", - "const forest = harness.getRuntimeObject('PineForest').__terrainScatter;", - "const tree = forest.findNearestCopy(350, 1800);", - "restartedTerrain.raise(tree.x, tree.y, tree.x, tree.y, 300, 120);", - "await harness.stepFrames(8, DT);", - "const raisedTree = forest.findNearestCopy(tree.x, tree.y);", - "const groundZ = restartedTerrain.getHeightAt(raisedTree.x, raisedTree.y);", - "console.log('tree z=' + tree.z.toFixed(1) + ' after raising the ground: z=' + raisedTree.z.toFixed(1) + ', ground=' + groundZ.toFixed(1));", - "harness.assert(raisedTree.x === tree.x && raisedTree.y === tree.y && Math.abs(raisedTree.z - groundZ) < 0.01 && raisedTree.z > tree.z + 50, 'The tree follows the raised ground.');", - "", - "// Without a terrain under them, copies stand at the Z position of the object.", - "const forestOutsideTerrain = harness.getRuntimeObject(harness.spawn('PineForest', 5000, 0, 77).id);", - "forestOutsideTerrain.setWidth(1500);", - "forestOutsideTerrain.setHeight(1500);", - "forestOutsideTerrain._setCoverage('Everywhere');", - "forestOutsideTerrain._setEdits('');", - "forestOutsideTerrain._setPaintData('');", - "forestOutsideTerrain.__terrainScatter.loadMaskFromProperties();", - "await harness.stepFrames(1, DT);", - "const copiesOutsideTerrain = forestOutsideTerrain.__terrainScatter.getCopies();", - "console.log(copiesOutsideTerrain.length + ' copies outside the terrain');", - "harness.assert(copiesOutsideTerrain.length > 20 && copiesOutsideTerrain.every((copy) => copy.z === 77), 'Without a terrain, copies stand at the Z position of the object.');" - ] - }, - { - "name": "Trees stop boxes pushed against them, and can be cut", - "type": "gameplay", - "description": "Pushes a physics box against the trunk of a tree of the forest: the trunk collision shape stops it. Once the tree is cut (removed with its nearest-copy position), the box goes through.", - "source": [ - "await harness.goToScene('Game Scene');", - "// Faster in the software renderer used to run tests (grass is thousands of blades).", - "harness.setGameResolutionSize(160, 90);", - "harness.getCurrentRuntimeScene().getLayer('').setEffectBooleanParameter('Sun light', 'isCastingShadow', false);", - "for (const grass of harness.getCurrentRuntimeScene().getObjects('Grass')) grass.hide(true);", - "const DT = { dtMs: 50 };", - "await harness.stepFrames(2, DT);", - "const forestObject = harness.getRuntimeObject('PineForest');", - "const forest = forestObject.__terrainScatter;", - "const terrain = harness.getRuntimeObject('Terrain').__terrain3D;", - "// A tree on flat ground, with no other tree in front of it.", - "const tree = forest.getCopies()", - " .filter((copy) => [0, 100, 200, 300].every((distance) => terrain.getSlopeAt(copy.x - distance, copy.y) < 3))", - " .find((copy) => ['PineForest', 'PineForest2'].every((name) => harness.getRuntimeObject(name).__terrainScatter.getCopies()", - " .every((other) => other === copy || other.x > copy.x + 40 || other.x < copy.x - 300 || Math.abs(other.y - copy.y) > 120)));", - "harness.assert(!!tree, 'There is a tree with free space in front of it.');", - "const trunkRadius = (forestObject._getTrunkDiameter() / 2) * (tree.height / forestObject._getSize());", - "console.log('tree at ' + tree.x.toFixed(0) + ';' + tree.y.toFixed(0) + ', trunk radius ' + trunkRadius.toFixed(1));", - "", - "const box = harness.getObjects('PushableBox')[0];", - "// The box is pushed towards the tree for a second, from 250 pixels away.", - "const pushBox = async (y) => {", - " const startX = tree.x - 250;", - " harness.setObjectPosition(box.id, startX - box.width / 2, y - box.height / 2, terrain.getHeightAt(startX, y) + 5);", - " const physics = harness.getRuntimeObject(box.id).getBehavior('Physics3D');", - " await harness.stepFrames(20, { ...DT, onFrame: () => physics.setLinearVelocityX(500) });", - " return harness.getObjects('PushableBox').find((b) => b.id === box.id).centerX;", - "};", - "const stoppedX = await pushBox(tree.y);", - "console.log('box pushed against the trunk stopped at x=' + stoppedX.toFixed(0) + ' (trunk at ' + tree.x.toFixed(0) + ')');", - "// Pushed every frame, the box sinks a bit in the trunk shape, but stays in front of it.", - "harness.assert(stoppedX < tree.x - box.width / 4, 'The trunk stops the box.');", - "", - "// Cutting the tree (like the \"Erase copies\" action at the nearest copy) lets the box through.", - "const nearest = forest.findNearestCopy(tree.x + 3, tree.y - 2);", - "harness.assert(nearest.x === tree.x && nearest.y === tree.y, 'The nearest copy is the tree.');", - "const countBefore = forest.getCopyCount();", - "forest.removeCopies(nearest.x, nearest.y, 1);", - "await harness.stepFrames(1, DT);", - "harness.assert(forest.getCopyCount() === countBefore - 1, 'Only the cut tree is removed.');", - "const passedX = await pushBox(tree.y);", - "console.log('box pushed after the tree was cut went to x=' + passedX.toFixed(0));", - "harness.assert(passedX > tree.x + trunkRadius, 'The box goes where the tree was.');" - ] - }, - { - "name": "Trees are added and erased with the brushes of the scene editor", - "type": "gameplay", - "description": "Uses the brushes shown in the scene editor for the forest (with a stand-in for the editor): erasing removes the trees under the brush and saves the painted data, undo brings them back, and the saved data gives back the erased forest.", - "source": [ - "await harness.goToScene('Game Scene');", - "// Faster in the software renderer used to run tests (grass is thousands of blades).", - "harness.setGameResolutionSize(160, 90);", - "harness.getCurrentRuntimeScene().getLayer('').setEffectBooleanParameter('Sun light', 'isCastingShadow', false);", - "for (const grass of harness.getCurrentRuntimeScene().getObjects('Grass')) grass.hide(true);", - "await harness.stepFrames(2);", - "const game = harness.getRuntimeGame();", - "const scene = harness.getCurrentRuntimeScene();", - "scene.getObjects('Player')[0].getBehavior('ThirdPersonCamera').activate(false);", - "const forestObject = scene.getObjects('PineForest')[0];", - "const forest = forestObject.__terrainScatter;", - "const terrain = scene.getObjects('Terrain')[0].__terrain3D;", - "// In the middle of the forest of the hill.", - "const target = forest.findNearestCopy(2400, -1300);", - "const copiesAround = () => forest.getCopies().filter((copy) => Math.hypot(copy.x - target.x, copy.y - target.y) < 250).length;", - "gdjs.evtTools.camera.setCameraX(scene, target.x, '', 0);", - "gdjs.evtTools.camera.setCameraY(scene, target.y, '', 0);", - "gdjs.scene3d.camera.setCameraZ(scene, target.z + 1500, '', 0);", - "gdjs.scene3d.camera.setCameraRotationX(scene, 0, '', 0);", - "gdjs.evtTools.camera.setCameraRotation(scene, 0, '', 0);", - "await harness.stepFrames(1);", - "", - "const savedProperties = [];", - "let toolbarItems = null;", - "const editor = {", - " getRuntimeGame: () => game,", - " getSelectedObjects: () => [forestObject],", - " captureLeftMouseButton: () => {},", - " isCapturedLeftMouseButtonPressed: () => game.getInputManager().isMouseButtonPressed(0),", - " updateObjectProperties: (objectName, properties) => savedProperties.push({ objectName, properties }),", - " showToolbar: (toolbarId, items) => (toolbarItems = items),", - "};", - "const updateTools = () => gdjs.callbacksInGameEditorPostStep.forEach((callback) => callback(editor));", - "const drag = async (frames) => {", - " harness.setMousePositionScreen(game.getGameResolutionWidth() / 2, game.getGameResolutionHeight() / 2);", - " harness.setMouseButtonPressed(true);", - " for (let frame = 0; frame < frames; frame++) {", - " await harness.stepFrames(1);", - " updateTools();", - " // Brushes paint more when the mouse is pressed longer.", - " await new Promise((resolve) => setTimeout(resolve, 40));", - " }", - " harness.setMouseButtonPressed(false);", - " await harness.stepFrames(1, { onFrame: updateTools });", - "};", - "updateTools();", - "harness.assert(!!toolbarItems && ['Add', 'Erase', 'Size', 'Strength', 'Undo'].every((id) => toolbarItems.some((item) => item.id === id)), 'The brushes are shown for the selected scattered models.');", - "toolbarItems.find((item) => item.id === 'Size').onChange(45);", - "toolbarItems.find((item) => item.id === 'Strength').onChange(100);", - "toolbarItems.find((item) => item.id === 'Erase').onClick();", - "updateTools();", - "const copiesBefore = copiesAround();", - "await drag(20);", - "const copiesAfterErasing = copiesAround();", - "console.log('copies around the brush: ' + copiesBefore + ' before, ' + copiesAfterErasing + ' after erasing, saves: ' + savedProperties.length);", - "harness.assert(copiesBefore > 0 && copiesAfterErasing === 0, 'The trees are erased under the brush.');", - "harness.assert(savedProperties.length === 1 && savedProperties[0].objectName === 'PineForest' && savedProperties[0].properties.PaintData.length > 0, 'The stroke is saved.');", - "", - "const erasedCount = forest.getCopyCount();", - "// Undo brings the trees back.", - "toolbarItems.find((item) => item.id === 'Undo').onClick();", - "await harness.stepFrames(1);", - "harness.assert(copiesAround() === copiesBefore, 'Undo brings the trees back.');", - "harness.assert(savedProperties.length === 2 && savedProperties[1].properties.PaintData === '', 'Undo is saved (nothing is painted anymore).');", - "", - "// The saved data gives back the erased forest.", - "forestObject._setPaintData(savedProperties[0].properties.PaintData);", - "forest.loadMaskFromProperties();", - "await harness.stepFrames(1);", - "harness.assert(forest.getCopyCount() === erasedCount && copiesAround() === 0, 'The saved data gives back the erased forest.');" - ] - }, - { - "name": "Holes in the ground are seen through, and let objects fall until they are filled", - "type": "gameplay", - "description": "The entrances of the tunnel are holes in the ground of the hill: rays and objects go through them. A box falls through a new hole made in a field, and lands on the ground once the hole is filled by painting it.", - "source": [ - "await harness.goToScene('Game Scene');", - "// Faster in the software renderer used to run tests (grass is thousands of blades).", - "harness.setGameResolutionSize(160, 90);", - "harness.getCurrentRuntimeScene().getLayer('').setEffectBooleanParameter('Sun light', 'isCastingShadow', false);", - "for (const grass of harness.getCurrentRuntimeScene().getObjects('Grass')) grass.hide(true);", - "const DT = { dtMs: 50 };", - "await harness.stepFrames(2, DT);", - "const terrainObject = harness.getRuntimeObject('Terrain');", - "const terrain = terrainObject.__terrain3D;", - "", - "const tunnelY = -1344;", - "for (const x of [1600, 2848]) {", - " harness.assert(terrain.isHoleAt(x, tunnelY) && !terrain.containsPoint(x, tunnelY), 'There is a hole at the entrance of the tunnel at x=' + x + '.');", - "}", - "harness.assert(terrain.containsPoint(1400, tunnelY), 'The road in front of the tunnel is ground.');", - "harness.assert(terrain.containsPoint(2200, tunnelY), 'The top of the hill, above the tunnel, is ground.');", - "const countHoleSamples = () => terrainObject.getSurface().getHeightField().heights.filter(Number.isNaN).length;", - "const entranceHoleSamples = countHoleSamples();", - "console.log('samples without collisions at the entrances: ' + entranceHoleSamples);", - "harness.assert(entranceHoleSamples > 0, 'The collision surface of the terrain has holes.');", - "", - "// Rays (like the one of the editor finding what is under the cursor) go through holes.", - "const group = terrain.renderer.group;", - "group.updateWorldMatrix(true, true);", - "const origin = terrain.toScenePosition(new THREE.Vector3(0, 0, 0));", - "const unit = terrain.toScenePosition(new THREE.Vector3(1, 1, 1));", - "const toWorld = (x, y, z) =>", - " group.localToWorld(new THREE.Vector3((x - origin.x) / (unit.x - origin.x), (y - origin.y) / (unit.y - origin.y), (z - origin.z) / (unit.z - origin.z)));", - "const raycaster = new THREE.Raycaster();", - "const doesRayHitGround = (x, y) => {", - " const from = toWorld(x, y, 3000);", - " raycaster.set(from, toWorld(x, y, -100).sub(from).normalize());", - " return raycaster.intersectObject(group, true).length > 0;", - "};", - "harness.assert(!doesRayHitGround(1600, tunnelY), 'A ray goes through the hole at the entrance.');", - "harness.assert(doesRayHitGround(1400, tunnelY), 'A ray hits the road in front of the tunnel.');", - "", - "// A box falls through a new hole made in a field.", - "const field = { x: -2500, y: -1000 };", - "const groundZ = terrain.getHeightAt(field.x, field.y);", - "console.log('field ground z=' + groundZ.toFixed(0) + ' slope ' + terrain.getSlopeAt(field.x, field.y).toFixed(1));", - "terrain.makeHole(field.x - 100, field.y, field.x + 100, field.y, 150);", - "await harness.stepFrames(2, DT);", - "harness.assert(terrain.isHoleAt(field.x, field.y), 'The new hole is made.');", - "harness.assert(countHoleSamples() > entranceHoleSamples, 'The new hole is in the collision surface.');", - "const box = harness.getObjects('PushableBox')[0];", - "const dropBox = async () => {", - " harness.setObjectPosition(box.id, field.x - box.width / 2, field.y - box.height / 2, groundZ + 40);", - " const physics = harness.getRuntimeObject(box.id).getBehavior('Physics3D');", - " physics.setLinearVelocityX(0);", - " physics.setLinearVelocityY(0);", - " physics.setLinearVelocityZ(0);", - " await harness.stepFrames(30, DT);", - " return harness.getObjects('PushableBox').find((b) => b.id === box.id).z;", - "};", - "const fallenZ = await dropBox();", - "console.log('box dropped in the hole: z=' + fallenZ.toFixed(0));", - "harness.assert(fallenZ < groundZ - 100, 'The box falls through the hole.');", - "", - "// Painting fills the hole: the collisions of the ground are updated.", - "terrain.paint(field.x - 100, field.y, field.x + 100, field.y, 200, 1, 1);", - "await harness.stepFrames(2, DT);", - "harness.assert(!terrain.isHoleAt(field.x, field.y), 'The hole is filled.');", - "harness.assert(countHoleSamples() === entranceHoleSamples, 'The filled hole is removed from the collision surface.');", - "const landedZ = await dropBox();", - "console.log('box dropped on the filled hole: z=' + landedZ.toFixed(0));", - "harness.assert(Math.abs(landedZ - groundZ) < 20, 'The box lands on the ground where the hole was.');" - ] - }, - { - "name": "A car drives through the tunnel and comes out on the other side, on its wheels", - "type": "gameplay", - "description": "Puts the driven car on the road going to the tunnel and holds W: the car goes into the hill through the hole of the entrance, drives in the tunnel under the ground, and comes out of the other entrance on its wheels.", - "source": [ - "harness.setTimeoutMs(120000);", - "await harness.goToScene('Game Scene');", - "// Faster in the software renderer used to run tests (grass is thousands of blades).", - "harness.setGameResolutionSize(160, 90);", - "harness.getCurrentRuntimeScene().getLayer('').setEffectBooleanParameter('Sun light', 'isCastingShadow', false);", - "for (const grass of harness.getCurrentRuntimeScene().getObjects('Grass')) grass.hide(true);", - "// 50ms frames: the longest frame the game accepts (minFPS is 20), so a second of game time costs 20 frames.", - "const DT = { dtMs: 50 };", - "await harness.stepFrames(3, DT);", - "const terrain = harness.getRuntimeObject('Terrain').__terrain3D;", - "const getPlayer = () => harness.getObjects('Player')[0];", - "const car = harness.getObjects('Car').reduce((best, c) =>", - " Math.hypot(c.centerX - getPlayer().centerX, c.centerY - getPlayer().centerY) <", - " Math.hypot(best.centerX - getPlayer().centerX, best.centerY - getPlayer().centerY) ? c : best);", - "harness.setObjectPosition(getPlayer().id, car.centerX - 30, car.centerY, getPlayer().z);", - "await harness.stepFrames(3, DT);", - "harness.setKeyPressed('e', true);", - "await harness.stepFrames(2, DT);", - "harness.setKeyPressed('e', false);", - "await harness.stepFrames(3, DT);", - "const getCar = () => harness.getObjects('Driven_Car')[0];", - "harness.assert(!!getCar(), 'The player drives a car.');", - "", - "// The tunnel goes east, from x=1504 to x=2944.", - "const tunnelY = -1344;", - "const startX = 1150;", - "harness.setObjectPosition(getCar().id, startX - getCar().width / 2, tunnelY - getCar().height / 2, terrain.getHeightAt(startX, tunnelY) + 20);", - "harness.getRuntimeObject(getCar().id).setAngle(0);", - "await harness.stepFrames(10, DT);", - "", - "let deepestUnderGround = 0;", - "let farthestFromMiddle = 0;", - "let hasTakenScreenshot = false;", - "harness.setKeyPressed('w', true);", - "for (let frame = 0; frame < 400 && getCar().centerX < 3150; frame++) {", - " await harness.stepFrames(1, DT);", - " const { centerX, centerY, z } = getCar();", - " if (centerX > 1504 && centerX < 2944) {", - " deepestUnderGround = Math.max(deepestUnderGround, terrain.getHeightAt(centerX, centerY) - z);", - " farthestFromMiddle = Math.max(farthestFromMiddle, Math.abs(centerY - tunnelY));", - " }", - " if (!hasTakenScreenshot && centerX > 2200) {", - " hasTakenScreenshot = true;", - " await harness.takeScreenshot('in the tunnel');", - " }", - "}", - "harness.setKeyPressed('w', false);", - "await harness.stepFrames(10, DT);", - "const end = getCar();", - "const endObject = harness.getRuntimeObject(end.id);", - "console.log('car at ' + end.centerX.toFixed(0) + ';' + end.centerY.toFixed(0) + ' z=' + end.z.toFixed(0) +", - " ', at most ' + deepestUnderGround.toFixed(0) + ' under the ground and ' + farthestFromMiddle.toFixed(0) + ' from the middle of the tunnel' +", - " ', rotation ' + endObject.getRotationX().toFixed(1) + ';' + endObject.getRotationY().toFixed(1));", - "harness.assert(end.centerX > 3000, 'The car came out of the east entrance (x=' + end.centerX.toFixed(0) + ').');", - "harness.assert(deepestUnderGround > 150, 'The car drove under the hill.');", - "harness.assert(farthestFromMiddle < 100, 'The car stayed in the tunnel.');", - "harness.assert(end.behaviors.PhysicsCar3D.state.IsOnFloor === true, 'The car is on the ground.');", - "harness.assert(Math.abs(endObject.getRotationX()) < 20 && Math.abs(endObject.getRotationY()) < 20, 'The car is on its wheels.');", - "await harness.takeScreenshot('out of the tunnel');" - ] - }, - { - "name": "Clicking a tree in the scene editor selects its forest", - "type": "gameplay", - "description": "Casts rays like the scene editor does to find the object under the cursor: a ray on a tree hits the forest (so that it can be selected and painted with its brushes), and a ray on the ground next to it hits the terrain.", - "source": [ - "await harness.goToScene('Game Scene');", - "// Faster in the software renderer used to run tests (grass is thousands of blades).", - "harness.setGameResolutionSize(160, 90);", - "const scene = harness.getCurrentRuntimeScene();", - "scene.getLayer('').setEffectBooleanParameter('Sun light', 'isCastingShadow', false);", - "for (const grass of scene.getObjects('Grass')) grass.hide(true);", - "scene.getObjects('Player')[0].getBehavior('ThirdPersonCamera').activate(false);", - "await harness.stepFrames(2);", - "const terrain = harness.getRuntimeObject('Terrain').__terrain3D;", - "const forestObject = harness.getRuntimeObject('PineForest');", - "const allCopies = ['PineForest', 'PineForest2', 'Bushes'].flatMap((name) => harness.getRuntimeObject(name).__terrainScatter.getCopies());", - "// A tree on flat ground, with nothing in front of it.", - "const tree = forestObject.__terrainScatter.getCopies().find((copy) =>", - " [0, 200, 400].every((distance) => terrain.getSlopeAt(copy.x - distance, copy.y) < 5) &&", - " allCopies.every((other) => other === copy || other.x > copy.x + 100 || other.x < copy.x - 450 || Math.abs(other.y - copy.y) > 150));", - "harness.assert(!!tree, 'There is a tree with free space in front of it.');", - "const groundZ = terrain.getHeightAt(tree.x, tree.y);", - "", - "// Looking at the tree from 400 pixels west of it.", - "gdjs.evtTools.camera.setCameraX(scene, tree.x - 400, '', 0);", - "gdjs.evtTools.camera.setCameraY(scene, tree.y, '', 0);", - "gdjs.scene3d.camera.setCameraZ(scene, groundZ + tree.height * 0.5, '', 0);", - "gdjs.scene3d.camera.setCameraRotationX(scene, 90, '', 0);", - "gdjs.evtTools.camera.setCameraRotation(scene, 90, '', 0);", - "await harness.stepFrames(1);", - "", - "const layerRenderer = scene.getLayer('').getRenderer();", - "const camera = layerRenderer.getThreeCamera();", - "const threeGroup = layerRenderer.getThreeGroup();", - "const worldScale = scene.getRenderer3DWorldScale ? scene.getRenderer3DWorldScale() : 100;", - "const raycaster = new THREE.Raycaster();", - "const isVisible = (threeObject) => {", - " for (let current = threeObject; current; current = current.parent) if (!current.visible) return false;", - " return true;", - "};", - "const objectsByThreeObject = new Map(scene.getAdhocListOfAllInstances().filter((object) => object.get3DRendererObject).map((object) => [object.get3DRendererObject(), object]));", - "// The object under a point of the scene, like the editor finds it.", - "const getObjectAt = (x, y, z) => {", - " const point = new THREE.Vector3(x / worldScale, -y / worldScale, z / worldScale).project(camera);", - " raycaster.setFromCamera(new THREE.Vector2(point.x, point.y), camera);", - " const hit = raycaster.intersectObjects(threeGroup.children, true).find((intersect) => isVisible(intersect.object));", - " for (let current = hit ? hit.object : null; current; current = current.parent) {", - " if (objectsByThreeObject.has(current)) return objectsByThreeObject.get(current).getName();", - " }", - " return null;", - "};", - "const startTime = performance.now();", - "const objectOnTree = getObjectAt(tree.x, tree.y, groundZ + tree.height * 0.5);", - "const objectOnGround = getObjectAt(tree.x - 200, tree.y, groundZ);", - "console.log('tree at ' + tree.x.toFixed(0) + ';' + tree.y.toFixed(0) + ': ' + objectOnTree + ' under the tree, ' + objectOnGround + ' under the ground next to it (' + (performance.now() - startTime).toFixed(1) + ' ms for both rays)');", - "harness.assert(objectOnTree === 'PineForest', 'The tree is part of the forest under the cursor.');", - "harness.assert(objectOnGround === 'Terrain', 'The ground next to the tree is the terrain under the cursor.');" - ] } ], "eventsFunctionsExtensions": [ @@ -36959,6 +36454,7 @@ "areaMinY": 0, "areaMinZ": 0, "defaultName": "Terrain", + "assetStoreTag": "3d terrains", "description": "A ground with hills, mountains or valleys that can be sculpted and painted in the scene editor, or changed with events. Add the 3D physics behavior to it to make objects walk or roll on it.", "fullName": "3D terrain", "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXRlcnJhaW4iIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTQsNkwxMC4yNSwxMUwxMy4xLDE0LjhMMTEuNSwxNkM5LjgxLDEzLjc1IDcsMTAgNywxMEwxLDE4SDIzTDE0LDZaIiAvPjwvc3ZnPg==", diff --git a/examples/starting-3d-grand-theft-auto-with-a-map/theme-slots.json b/examples/starting-expanded-3d-grand-theft-auto/theme-slots.json similarity index 100% rename from examples/starting-3d-grand-theft-auto-with-a-map/theme-slots.json rename to examples/starting-expanded-3d-grand-theft-auto/theme-slots.json diff --git a/examples/starting-expanded-3d-tank/README.md b/examples/starting-expanded-3d-tank/README.md new file mode 100644 index 000000000..42e9aa907 --- /dev/null +++ b/examples/starting-expanded-3d-tank/README.md @@ -0,0 +1,3 @@ +# 3D Tank Expanded + +A 3D physics-based game where the player controls a tank on a battlefield made with a 3D terrain: hills, trenches, craters, ruined walls, dirt tracks and woods. The tank has an independently aimed turret. Firing launches a shell that follows the turret's angle and inclination and explodes on the ground, the walls or the targets. Stationary enemy targets can be shot and react realistically to explosions, which are visualized with 3D particle effects and screen shake. A third-person camera follows the tank. The game supports both keyboard and mobile controls. diff --git a/examples/starting-expanded-3d-tank/assets/Brick Wall 1.glb b/examples/starting-expanded-3d-tank/assets/Brick Wall 1.glb new file mode 100644 index 000000000..2b5296b64 Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Brick Wall 1.glb differ diff --git a/examples/starting-expanded-3d-tank/assets/Brick Wall 2.glb b/examples/starting-expanded-3d-tank/assets/Brick Wall 2.glb new file mode 100644 index 000000000..4d5cabfc7 Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Brick Wall 2.glb differ diff --git a/examples/starting-expanded-3d-tank/assets/Container Long.glb b/examples/starting-expanded-3d-tank/assets/Container Long.glb new file mode 100644 index 000000000..e3b2e6622 Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Container Long.glb differ diff --git a/examples/starting-expanded-3d-tank/assets/Debris Broken Car.glb b/examples/starting-expanded-3d-tank/assets/Debris Broken Car.glb new file mode 100644 index 000000000..647afedbb Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Debris Broken Car.glb differ diff --git a/examples/starting-expanded-3d-tank/assets/Explosion.wav b/examples/starting-expanded-3d-tank/assets/Explosion.wav new file mode 100644 index 000000000..3abcfc32e Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Explosion.wav differ diff --git a/examples/starting-expanded-3d-tank/assets/Flat dark joystick border.png b/examples/starting-expanded-3d-tank/assets/Flat dark joystick border.png new file mode 100644 index 000000000..b4e83e112 Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Flat dark joystick border.png differ diff --git a/examples/starting-expanded-3d-tank/assets/Flat dark joystick thumb.png b/examples/starting-expanded-3d-tank/assets/Flat dark joystick thumb.png new file mode 100644 index 000000000..8acf179f8 Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Flat dark joystick thumb.png differ diff --git a/examples/starting-expanded-3d-tank/assets/LightGlow.png b/examples/starting-expanded-3d-tank/assets/LightGlow.png new file mode 100644 index 000000000..871c194cc Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/LightGlow.png differ diff --git a/examples/starting-expanded-3d-tank/assets/Sack Trench.glb b/examples/starting-expanded-3d-tank/assets/Sack Trench.glb new file mode 100644 index 000000000..5538e4c3f Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Sack Trench.glb differ diff --git a/examples/starting-expanded-3d-tank/assets/Skybox_Back.png b/examples/starting-expanded-3d-tank/assets/Skybox_Back.png new file mode 100644 index 000000000..aa0891ef2 Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Skybox_Back.png differ diff --git a/examples/starting-expanded-3d-tank/assets/Skybox_Bottom.png b/examples/starting-expanded-3d-tank/assets/Skybox_Bottom.png new file mode 100644 index 000000000..16e34f3da Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Skybox_Bottom.png differ diff --git a/examples/starting-expanded-3d-tank/assets/Skybox_Front.png b/examples/starting-expanded-3d-tank/assets/Skybox_Front.png new file mode 100644 index 000000000..018d5faac Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Skybox_Front.png differ diff --git a/examples/starting-expanded-3d-tank/assets/Skybox_Left.png b/examples/starting-expanded-3d-tank/assets/Skybox_Left.png new file mode 100644 index 000000000..e4053f388 Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Skybox_Left.png differ diff --git a/examples/starting-expanded-3d-tank/assets/Skybox_Right.png b/examples/starting-expanded-3d-tank/assets/Skybox_Right.png new file mode 100644 index 000000000..d14c192b1 Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Skybox_Right.png differ diff --git a/examples/starting-expanded-3d-tank/assets/Skybox_Top.png b/examples/starting-expanded-3d-tank/assets/Skybox_Top.png new file mode 100644 index 000000000..e83ef6a1b Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Skybox_Top.png differ diff --git a/examples/starting-expanded-3d-tank/assets/Tank_Orange_Base.glb b/examples/starting-expanded-3d-tank/assets/Tank_Orange_Base.glb new file mode 100644 index 000000000..d4c34280d Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Tank_Orange_Base.glb differ diff --git a/examples/starting-expanded-3d-tank/assets/Tank_Orange_Cannon.glb b/examples/starting-expanded-3d-tank/assets/Tank_Orange_Cannon.glb new file mode 100644 index 000000000..79b9ae763 Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Tank_Orange_Cannon.glb differ diff --git a/examples/starting-expanded-3d-tank/assets/Tank_Orange_upperbase.glb b/examples/starting-expanded-3d-tank/assets/Tank_Orange_upperbase.glb new file mode 100644 index 000000000..dec615691 Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Tank_Orange_upperbase.glb differ diff --git a/examples/starting-expanded-3d-tank/assets/Tankfire.wav b/examples/starting-expanded-3d-tank/assets/Tankfire.wav new file mode 100644 index 000000000..cdaa6ab80 Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Tankfire.wav differ diff --git a/examples/starting-expanded-3d-tank/assets/Target round button.png b/examples/starting-expanded-3d-tank/assets/Target round button.png new file mode 100644 index 000000000..e61579880 Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Target round button.png differ diff --git a/examples/starting-expanded-3d-tank/assets/TerrainDirt.jpg b/examples/starting-expanded-3d-tank/assets/TerrainDirt.jpg new file mode 100644 index 000000000..71221d59d Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/TerrainDirt.jpg differ diff --git a/examples/starting-expanded-3d-tank/assets/TerrainDirtNormal.jpg b/examples/starting-expanded-3d-tank/assets/TerrainDirtNormal.jpg new file mode 100644 index 000000000..f701da364 Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/TerrainDirtNormal.jpg differ diff --git a/examples/starting-expanded-3d-tank/assets/TerrainGrass.jpg b/examples/starting-expanded-3d-tank/assets/TerrainGrass.jpg new file mode 100644 index 000000000..167ebfd97 Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/TerrainGrass.jpg differ diff --git a/examples/starting-expanded-3d-tank/assets/TerrainGrassNormal.jpg b/examples/starting-expanded-3d-tank/assets/TerrainGrassNormal.jpg new file mode 100644 index 000000000..2b4eadc6b Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/TerrainGrassNormal.jpg differ diff --git a/examples/starting-expanded-3d-tank/assets/TerrainMud.jpg b/examples/starting-expanded-3d-tank/assets/TerrainMud.jpg new file mode 100644 index 000000000..5f970dffe Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/TerrainMud.jpg differ diff --git a/examples/starting-expanded-3d-tank/assets/TerrainMudNormal.jpg b/examples/starting-expanded-3d-tank/assets/TerrainMudNormal.jpg new file mode 100644 index 000000000..8bda654bb Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/TerrainMudNormal.jpg differ diff --git a/examples/starting-expanded-3d-tank/assets/TerrainRock.jpg b/examples/starting-expanded-3d-tank/assets/TerrainRock.jpg new file mode 100644 index 000000000..0d8fcf9b6 Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/TerrainRock.jpg differ diff --git a/examples/starting-expanded-3d-tank/assets/TerrainRockNormal.jpg b/examples/starting-expanded-3d-tank/assets/TerrainRockNormal.jpg new file mode 100644 index 000000000..b7f02366b Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/TerrainRockNormal.jpg differ diff --git a/examples/starting-expanded-3d-tank/assets/Tree 1.glb b/examples/starting-expanded-3d-tank/assets/Tree 1.glb new file mode 100644 index 000000000..a9d8ec14e Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Tree 1.glb differ diff --git a/examples/starting-expanded-3d-tank/assets/Tree 2.glb b/examples/starting-expanded-3d-tank/assets/Tree 2.glb new file mode 100644 index 000000000..5c10f89ee Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Tree 2.glb differ diff --git a/examples/starting-expanded-3d-tank/assets/Water Tank Platform.glb b/examples/starting-expanded-3d-tank/assets/Water Tank Platform.glb new file mode 100644 index 000000000..88d92d270 Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/Water Tank Platform.glb differ diff --git a/examples/starting-expanded-3d-tank/assets/missile_orange.glb b/examples/starting-expanded-3d-tank/assets/missile_orange.glb new file mode 100644 index 000000000..bd5d93a4a Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/missile_orange.glb differ diff --git a/examples/starting-expanded-3d-tank/assets/starting_unit_red.glb b/examples/starting-expanded-3d-tank/assets/starting_unit_red.glb new file mode 100644 index 000000000..4282106b7 Binary files /dev/null and b/examples/starting-expanded-3d-tank/assets/starting_unit_red.glb differ diff --git a/examples/starting-expanded-3d-tank/preview.png b/examples/starting-expanded-3d-tank/preview.png new file mode 100644 index 000000000..5e4d61ad7 Binary files /dev/null and b/examples/starting-expanded-3d-tank/preview.png differ diff --git a/examples/starting-expanded-3d-tank/starting-expanded-3d-tank.json b/examples/starting-expanded-3d-tank/starting-expanded-3d-tank.json new file mode 100644 index 000000000..524a63ee3 --- /dev/null +++ b/examples/starting-expanded-3d-tank/starting-expanded-3d-tank.json @@ -0,0 +1,45511 @@ +{ + "firstLayout": "", + "gdVersion": { + "build": 283, + "major": 5, + "minor": 6, + "revision": 0 + }, + "properties": { + "adaptGameResolutionAtRuntime": true, + "antialiasingMode": "MSAA", + "antialisingEnabledOnMobile": false, + "folderProject": false, + "orientation": "landscape", + "packageName": "com.example.gamename", + "pixelsRounding": false, + "projectUuid": "092cdf6d-7ef5-50ef-b20a-c05eaa205fe6", + "scaleMode": "linear", + "sizeOnStartupMode": "", + "templateSlug": "starting-3d-driving", + "version": "1.0.0", + "name": "3D Tank Expanded", + "description": "A 3D tank game on a battlefield made with a 3D terrain: hills, trenches, craters and ruined walls, with targets to blow away.", + "author": "", + "windowWidth": 1280, + "windowHeight": 720, + "latestCompilationDirectory": "", + "maxFPS": 60, + "minFPS": 20, + "verticalSync": false, + "platformSpecificAssets": { + "android-icon-144": "", + "android-icon-192": "", + "android-icon-36": "", + "android-icon-48": "", + "android-icon-72": "", + "android-icon-96": "", + "android-windowSplashScreenAnimatedIcon": "", + "desktop-icon-512": "", + "ios-icon-100": "", + "ios-icon-1024": "", + "ios-icon-114": "", + "ios-icon-120": "", + "ios-icon-144": "", + "ios-icon-152": "", + "ios-icon-167": "", + "ios-icon-180": "", + "ios-icon-20": "", + "ios-icon-29": "", + "ios-icon-40": "", + "ios-icon-50": "", + "ios-icon-57": "", + "ios-icon-58": "", + "ios-icon-60": "", + "ios-icon-72": "", + "ios-icon-76": "", + "ios-icon-80": "", + "ios-icon-87": "" + }, + "loadingScreen": { + "backgroundColor": 0, + "backgroundFadeInDuration": 0.2, + "backgroundImageResourceName": "", + "gdevelopLogoStyle": "light", + "logoAndProgressFadeInDuration": 0.2, + "logoAndProgressLogoFadeInDelay": 0, + "minDuration": 1.5, + "progressBarColor": 16777215, + "progressBarHeight": 20, + "progressBarMaxWidth": 200, + "progressBarMinWidth": 40, + "progressBarWidthPercent": 30, + "showGDevelopSplash": true, + "showProgressBar": true + }, + "watermark": { + "placement": "bottom-left", + "showWatermark": true + }, + "authorIds": [], + "authorUsernames": [], + "categories": [], + "playableDevices": [ + "mobile" + ], + "extensionProperties": [], + "platforms": [ + { + "name": "GDevelop JS platform" + } + ], + "currentPlatform": "GDevelop JS platform" + }, + "resources": { + "resources": [ + { + "file": "assets/Flat dark joystick thumb.png", + "kind": "image", + "metadata": "", + "name": "assets\\Flat dark joystick thumb.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Flat dark joystick border.png", + "kind": "image", + "metadata": "", + "name": "assets\\Flat dark joystick border.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Tank_Orange_Base.glb", + "kind": "model3D", + "metadata": "", + "name": "assets\\Tank_Orange_Base.glb", + "userAdded": false + }, + { + "file": "assets/Tank_Orange_upperbase.glb", + "kind": "model3D", + "metadata": "", + "name": "assets\\Tank_Orange_upperbase.glb", + "userAdded": false + }, + { + "file": "assets/Tank_Orange_Cannon.glb", + "kind": "model3D", + "metadata": "", + "name": "assets\\Tank_Orange_Cannon.glb", + "userAdded": false + }, + { + "file": "assets/LightGlow.png", + "kind": "image", + "metadata": "", + "name": "LightGlow.png", + "smoothed": true, + "userAdded": false, + "origin": { + "identifier": "https://asset-resources.gdevelop.io/public-resources/3D Particles/c17e2d500015e598624b763f4942b26e3aac96efca094cd20257a61e15b27fcb_LightGlow.png", + "name": "LightGlow.png" + } + }, + { + "file": "assets/missile_orange.glb", + "kind": "model3D", + "metadata": "", + "name": "assets\\missile_orange.glb", + "userAdded": false + }, + { + "file": "assets/starting_unit_red.glb", + "kind": "model3D", + "metadata": "", + "name": "assets\\starting_unit_red.glb", + "userAdded": false + }, + { + "file": "assets/Explosion.wav", + "kind": "audio", + "metadata": "", + "name": "assets\\Explosion.wav", + "preloadAsMusic": false, + "preloadAsSound": true, + "preloadInCache": false, + "userAdded": false + }, + { + "file": "assets/Tankfire.wav", + "kind": "audio", + "metadata": "", + "name": "assets\\Tankfire.wav", + "preloadAsMusic": false, + "preloadAsSound": true, + "preloadInCache": false, + "userAdded": false + }, + { + "file": "assets/Target round button.png", + "kind": "image", + "metadata": "", + "name": "assets\\Target round button.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Front.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Front.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Back.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Back.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Top.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Top.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Bottom.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Bottom.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Left.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Left.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/Skybox_Right.png", + "kind": "image", + "metadata": "", + "name": "assets/Skybox_Right.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/TerrainGrass.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainGrass.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainGrassNormal.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainGrassNormal.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainDirt.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainDirt.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainDirtNormal.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainDirtNormal.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainRock.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainRock.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainRockNormal.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainRockNormal.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainMud.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainMud.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainMudNormal.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainMudNormal.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/Brick Wall 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Brick Wall 1.glb", + "userAdded": true + }, + { + "file": "assets/Brick Wall 2.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Brick Wall 2.glb", + "userAdded": true + }, + { + "file": "assets/Sack Trench.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Sack Trench.glb", + "userAdded": true + }, + { + "file": "assets/Container Long.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Container Long.glb", + "userAdded": true + }, + { + "file": "assets/Debris Broken Car.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Debris Broken Car.glb", + "userAdded": true + }, + { + "file": "assets/Water Tank Platform.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Water Tank Platform.glb", + "userAdded": true + }, + { + "file": "assets/Tree 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Tree 1.glb", + "userAdded": true + }, + { + "file": "assets/Tree 2.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Tree 2.glb", + "userAdded": true + } + ] + }, + "objects": [], + "objectsFolderStructure": { + "folderName": "__ROOT" + }, + "objectsGroups": [], + "variables": [], + "layouts": [ + { + "b": 255, + "disableInputWhenNotFocused": true, + "mangledName": "Game_32Scene", + "name": "Game Scene", + "r": 255, + "standardSortMethod": true, + "stopSoundsOnStartup": true, + "title": "", + "v": 255, + "uiSettings": { + "grid": false, + "gridType": "rectangular", + "gridWidth": 64, + "gridHeight": 64, + "gridDepth": 32, + "gridOffsetX": 0, + "gridOffsetY": 0, + "gridOffsetZ": 0, + "gridColor": 10401023, + "gridAlpha": 0.8, + "snap": false, + "zoomFactor": 0.5290357402157136, + "windowMask": false, + "selectedLayer": "", + "gameEditorMode": "embedded-game" + }, + "objectsGroups": [ + { + "name": "BulletObstacles", + "objects": [ + { + "name": "Target" + }, + { + "name": "Terrain" + }, + { + "name": "BrickWall" + }, + { + "name": "BrickWallBroken" + }, + { + "name": "SackTrench" + }, + { + "name": "Container" + }, + { + "name": "Wreck" + }, + { + "name": "WaterTank" + } + ] + } + ], + "variables": [], + "instances": [ + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "MobileControls", + "locked": true, + "name": "Steer_Joystick", + "persistentUuid": "6c0d6cbc-a8d1-4930-b2e4-a83ac76f2d7e", + "width": 0, + "x": 208, + "y": 537, + "zOrder": 10, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 36.00000000000001, + "height": 120, + "keepRatio": true, + "layer": "MobileControls", + "locked": true, + "name": "Cannon_Joystick", + "persistentUuid": "3e95fba0-5d98-4fa9-8634-039f385edfb5", + "width": 120, + "x": 1072, + "y": 537, + "zOrder": 9, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 0, + "height": 80, + "keepRatio": true, + "layer": "MobileControls", + "locked": true, + "name": "ShootButton", + "persistentUuid": "2af6e7f3-a49a-4d91-815e-fbecad137ec2", + "width": 80, + "x": 1168, + "y": 441, + "zOrder": 15, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 6144, + "layer": "", + "locked": false, + "name": "Terrain", + "persistentUuid": "baf99f38-906a-5f2f-a75b-45f7ed391bcb", + "x": -3072, + "y": -3072, + "z": -150, + "zOrder": 0, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 1400 + }, + { + "angle": 0, + "customSize": true, + "height": 200, + "width": 6544, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "691ecbdf-f298-5e5a-bd5f-7711856aa8c4", + "x": -3272, + "y": -3272, + "z": -500, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 4000 + }, + { + "angle": 0, + "customSize": true, + "height": 200, + "width": 6544, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "d6c5f67a-ce8f-5240-9adb-c9611e141a4d", + "x": -3272, + "y": 3072, + "z": -500, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 4000 + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 200, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "95b87ed3-2c21-5353-baf1-b86221b50783", + "x": -3272, + "y": -3072, + "z": -500, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 4000 + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 200, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "3b0d68cd-bde1-55fb-a9f6-e8254ffa8817", + "x": 3072, + "y": -3072, + "z": -500, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 4000 + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 6144, + "layer": "", + "locked": false, + "name": "Grass", + "persistentUuid": "6a5295f9-f96a-5242-97db-1add8268b1d3", + "x": -3072, + "y": -3072, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 40 + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "PlayerTank", + "persistentUuid": "be411ed5-d0bf-53c0-a139-e95f14daec27", + "x": -700, + "y": 2068, + "z": 0, + "zOrder": 5, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Target", + "persistentUuid": "3ada0629-0b1a-56ac-8abc-943b9c7fa260", + "x": 50, + "y": 1760, + "z": -0.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Target", + "persistentUuid": "0ef074da-ee74-5ee5-826a-2ea23ba2a28a", + "x": 110, + "y": 1820, + "z": -0.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Target", + "persistentUuid": "f7b308bc-2980-5587-8058-6cf2aa4f9a93", + "x": 50, + "y": 2440, + "z": 1.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Target", + "persistentUuid": "8eaf3d8e-9d24-55cd-88af-0c2512167d00", + "x": 110, + "y": 2380, + "z": -0.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 20, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BrickWall", + "persistentUuid": "97c8f17b-e230-5c0c-a681-767bfc2a6b5c", + "x": -200, + "y": -500, + "z": -2.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 110, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BrickWallBroken", + "persistentUuid": "757663b6-e40e-5a79-8ca4-c00a349e6ba1", + "x": -335, + "y": -421.4, + "z": -4.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Target", + "persistentUuid": "64bc4ff4-1eb7-5dc2-88bf-f7d32464778f", + "x": -140, + "y": -340, + "z": 17.7, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -40, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BrickWall", + "persistentUuid": "8baad16b-6b31-5658-b3ea-c9c6f7744246", + "x": 400, + "y": -900, + "z": -4.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 50, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BrickWallBroken", + "persistentUuid": "4fcfc7ed-a4b8-548f-ada0-21edf50b3e28", + "x": 400.5, + "y": -743.8, + "z": -4.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Target", + "persistentUuid": "a7b2f5e7-88b7-5dc6-8347-1cee6b5135cf", + "x": 460, + "y": -740, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 70, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BrickWall", + "persistentUuid": "79ce61dd-e732-5c5c-abf4-69bebc504cbb", + "x": -1600, + "y": -600, + "z": -14.9, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 160, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BrickWallBroken", + "persistentUuid": "e5b2b452-111b-5392-bc8a-e4340b363a79", + "x": -1747, + "y": -652.9, + "z": -4.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Target", + "persistentUuid": "2db07e4b-c575-5b87-83f7-ffb853c3cd54", + "x": -1540, + "y": -440, + "z": -0.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 10, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BrickWall", + "persistentUuid": "b9d14982-2e49-5083-9c46-447c044bdd00", + "x": 1100, + "y": -500, + "z": -7.3, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 100, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BrickWallBroken", + "persistentUuid": "2d234c4e-1eef-509f-bebd-975ee3a94ce2", + "x": 980.7, + "y": -399.2, + "z": -4.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Target", + "persistentUuid": "d3d930ce-a2f1-5fa6-a0d8-09578dc6c75b", + "x": 1160, + "y": -340, + "z": -2.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 30, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BrickWall", + "persistentUuid": "90e3cb3b-9446-5c68-bb31-5095d07f19b9", + "x": -2100, + "y": -2100, + "z": -4.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 120, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BrickWallBroken", + "persistentUuid": "bbbb5551-90c6-59f5-9e00-1e347df9ade5", + "x": -2246.6, + "y": -2046.1, + "z": -4.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Target", + "persistentUuid": "949104fd-9213-5bc2-a8e2-0cb4d729427b", + "x": -2040, + "y": -1940, + "z": 2.3, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -20, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BrickWall", + "persistentUuid": "fff69315-aede-507b-b5f8-6dd20169422c", + "x": 600, + "y": 1300, + "z": -4.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 70, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BrickWallBroken", + "persistentUuid": "1abddfb9-8eac-5f68-971f-629df2fc6937", + "x": 547.1, + "y": 1447, + "z": -4.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Target", + "persistentUuid": "364856b5-3841-5436-b7d9-afa1ecf64b60", + "x": 660, + "y": 1460, + "z": -0.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 50, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BrickWall", + "persistentUuid": "6e7222f1-c8ae-5fbd-850c-e47b15de3d7e", + "x": -1100, + "y": 300, + "z": -4.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 140, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "BrickWallBroken", + "persistentUuid": "0ce37914-d5c7-5be5-bcff-c73ca302d45c", + "x": -1256.2, + "y": 300.5, + "z": -17.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Target", + "persistentUuid": "1d6b2b44-8bf3-5eb7-9a62-2431f58bdf0b", + "x": -1040, + "y": 460, + "z": -0.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -40.6, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "SackTrench", + "persistentUuid": "891dcf45-9e72-52d4-9c75-973de70c47c9", + "x": -2225, + "y": 620, + "z": -34.7, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 45, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "SackTrench", + "persistentUuid": "03942f3c-3fe7-5026-9d10-aa4edb92e60b", + "x": -1875, + "y": 645, + "z": -40.7, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -45, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "SackTrench", + "persistentUuid": "39df45c9-0487-5889-a9bb-98c480651a25", + "x": -1525, + "y": 645, + "z": -41.9, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Target", + "persistentUuid": "897eaeb4-1ea8-561e-b7c6-19d6bc994b06", + "x": -1700, + "y": 430, + "z": -0.2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -36.9, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "SackTrench", + "persistentUuid": "f03d2bc5-dd47-593b-8421-c2c966477ebe", + "x": 600, + "y": 520, + "z": -29.5, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 41.2, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "SackTrench", + "persistentUuid": "d8f3f200-8a1f-5077-b544-a29cf48ce800", + "x": 1000, + "y": 545, + "z": -35.2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -49.4, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "SackTrench", + "persistentUuid": "1801675b-c78c-58c7-906c-bb929dfb98bf", + "x": 1350, + "y": 545, + "z": -54.9, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Target", + "persistentUuid": "afc45822-e4a5-5337-b170-045445789fd4", + "x": 1200, + "y": 330, + "z": -0.5, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 36.9, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "SackTrench", + "persistentUuid": "20cdaebd-3e55-58a4-9e59-1e467e4a564b", + "x": -100, + "y": -1380, + "z": -29.3, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -41.2, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "SackTrench", + "persistentUuid": "bee460ea-8547-57b8-a4e5-af0412cae94a", + "x": 300, + "y": -1405, + "z": -35.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 36.9, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "SackTrench", + "persistentUuid": "02faafbe-febb-5fb8-899d-d6f3dfffc759", + "x": 700, + "y": -1430, + "z": -27.9, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Target", + "persistentUuid": "16019d98-1ffe-5020-996b-9ae3a8d9ca23", + "x": 500, + "y": -1970, + "z": 6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 30, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Container", + "persistentUuid": "6231b492-2214-5959-a1af-d2ccf88ca97b", + "x": 1500, + "y": 600, + "z": -3.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 80, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Container", + "persistentUuid": "08624b79-ac31-53c3-bcc6-169b21689e61", + "x": -500, + "y": -2400, + "z": -2.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Container", + "persistentUuid": "dacb6dc8-6a06-5451-8c36-d521037c95f6", + "x": 2200, + "y": -1900, + "z": -3.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 40, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Wreck", + "persistentUuid": "20858193-81d2-52d4-8663-a92367ccfee3", + "x": -2100, + "y": 1900, + "z": -3.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 120, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Wreck", + "persistentUuid": "7d75762b-ac6a-5668-94ce-b1ec9a1ffc5c", + "x": 900, + "y": -2300, + "z": 1.8, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 200, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Wreck", + "persistentUuid": "3ab94e0c-fc7f-51e1-9e75-d9221be0532d", + "x": -1800, + "y": 1000, + "z": -25.8, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 300, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Wreck", + "persistentUuid": "bea16abd-87a8-5100-bd8b-ae6c62669023", + "x": 1900, + "y": 2400, + "z": -3.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "WaterTank", + "persistentUuid": "bc41aa6e-06cf-5ab9-8979-62e2086b2631", + "x": -2300, + "y": -1000, + "z": -0.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 6144, + "layer": "", + "locked": true, + "name": "Trees", + "persistentUuid": "f2a5838d-bfa2-5cf1-905b-df8368596603", + "x": -3072, + "y": -3072, + "z": -150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 6144, + "layer": "", + "locked": true, + "name": "TallTrees", + "persistentUuid": "62de3506-a268-52b4-b5d6-d8605ed41454", + "x": -3072, + "y": -3072, + "z": -150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + } + ], + "objects": [ + { + "assetStoreId": "", + "name": "PlayerTank", + "persistentUuid": "6f320449-d4fe-484d-afa7-1f1dc8845380", + "type": "TankConfiguration::CombinedTank", + "variant": "", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Dynamic", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "shapeOrientation": "Z", + "shapeDimensionA": 95, + "shapeDimensionB": 65, + "shapeDimensionC": 25, + "density": 1, + "friction": 10, + "restitution": 0.2, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 5, + "layers": 17, + "masks": 17, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": -25, + "massOverride": 2000 + }, + { + "name": "PhysicsCar3D", + "type": "Physics3D::PhysicsCar3D", + "physics3D": "Physics3D", + "steerAngleMax": 60, + "beginningSteerSpeed": 140, + "endSteerSpeed": 10, + "mass": 1500, + "engineTorqueMax": 6000, + "engineSpeedMax": 75, + "engineInertia": 0.05, + "reverseGearRatio1": -1, + "gearRatio1": 1, + "gearRatio2": 1, + "gearRatio3": 1, + "gearRatio4": 1, + "gearRatio5": 1, + "gearRatio6": 1, + "backWheelOffsetX": -2, + "frontWheelOffsetX": -2, + "wheelOffsetY": 5, + "wheelOffsetZ": 6, + "wheelRadius": 7, + "wheelWidth": 20, + "brakeTorqueMax": 3000, + "handBrakeTorqueMax": 6000, + "hasBackWheelDrive": true, + "hasFrontWheelDrive": true, + "pitchRollAngleMax": 89 + }, + { + "isFolded": true, + "name": "PhysicsCar3DKeyboardMapper", + "type": "PhysicsCar3DKeyMapper::PhysicsCar3DKeyboardMapper", + "PhysicsCar3D": "PhysicsCar3D", + "HandBrake": "Space", + "Up": "Up", + "Left": "Left", + "Down": "Down", + "Right": "Right" + }, + { + "isFolded": true, + "name": "PhysicsCar3DMultitouchMapper", + "type": "SpriteMultitouchJoystick::PhysicsCar3DMultitouchMapper", + "PhysicsCar3D": "PhysicsCar3D", + "ControllerIdentifier": 1, + "SteerJoystickIdentifier": "Primary", + "SpeedJoystickIdentifier": "Secondary", + "HandBrakeButton": "B" + }, + { + "isFolded": true, + "name": "ThirdPersonCamera", + "type": "ThirdPersonCamera::ThirdPersonCamera", + "Object3D": "Object3D", + "RotationHalfwayDuration": 0.125, + "TranslationZHalfwayDuration": 0.25, + "Distance": 450, + "LateralOffset": 0, + "AheadOffset": 200, + "OffsetZ": 0, + "RotationAngleOffset": 0, + "ElevationAngleOffset": 10, + "FollowFreeAreaZMax": 0, + "FollowFreeAreaZMin": 0, + "RotationLogSpeed": 2.0247e-320, + "TranslationZLogSpeed": 2.0247e-320, + "IsCalledManually": false, + "CameraZ": 0, + "LocalOffsetY": 200, + "LocalOffsetX": 0, + "OffsetY": 200, + "ElevationHalfwayDuration": 1, + "IsElevatingWithObject": false, + "IsRotatingWithObject": false + } + ], + "content": { + "CannonAngle": 0 + } + }, + { + "assetStoreId": "e71bd69f896d6c7531b48c65ceb5da25071d4fbdeb518aeceecba8d21f34ed8d", + "name": "Steer_Joystick", + "persistentUuid": "bf4f4377-e66f-47ac-a554-7c6b3210d5f5", + "type": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "variant": "", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "JoystickIdentifier": "Secondary", + "DeadZoneRadius": 0.3 + }, + "childrenContent": { + "Border": { + "adaptCollisionMaskAutomatically": false, + "updateIfNotVisible": false, + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": false, + "image": "assets\\Flat dark joystick border.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 0, + "y": 0 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [] + } + ] + } + ] + } + ] + }, + "Thumb": { + "adaptCollisionMaskAutomatically": false, + "updateIfNotVisible": false, + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": false, + "image": "assets\\Flat dark joystick thumb.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 0, + "y": 0 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [] + } + ] + } + ] + } + ] + } + } + }, + { + "assetStoreId": "", + "name": "Target", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Dynamic", + "bullet": false, + "fixedRotation": false, + "shape": "Capsule", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": -35, + "massOverride": 0, + "density": 1, + "friction": 0.75, + "restitution": 0.5, + "linearDamping": 0.5, + "angularDamping": 0.1, + "gravityScale": 2, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "ObjectCenter", + "crossfadeDuration": 0.10000000149011612, + "depth": 117.33, + "height": 54.15, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets\\starting_unit_red.glb", + "originLocation": "BottomCenterZ", + "rotationX": 0, + "rotationY": 0, + "rotationZ": 0, + "width": 54.15, + "animations": [] + } + }, + { + "assetStoreId": "e71bd69f896d6c7531b48c65ceb5da25071d4fbdeb518aeceecba8d21f34ed8d", + "name": "Cannon_Joystick", + "persistentUuid": "afc29e44-b269-4af9-86d2-80190493dedb", + "type": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "variant": "", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "JoystickIdentifier": "Secondary", + "DeadZoneRadius": 0.3, + "ControllerIdentifier": 3 + }, + "childrenContent": { + "Border": { + "adaptCollisionMaskAutomatically": false, + "updateIfNotVisible": false, + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": false, + "image": "assets\\Flat dark joystick border.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 0, + "y": 0 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [] + } + ] + } + ] + } + ] + }, + "Thumb": { + "adaptCollisionMaskAutomatically": false, + "updateIfNotVisible": false, + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": false, + "image": "assets\\Flat dark joystick thumb.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 0, + "y": 0 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [] + } + ] + } + ] + } + ] + } + } + }, + { + "assetStoreId": "8bfb3b8d354233214690205fb41cba03284ca34141297a0d98e09a2f080c029c", + "name": "ExplosionParticle", + "type": "ParticleEmitter3D::ParticleEmitter3D", + "variant": "", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "EndColor": "207;184;184", + "StartColor": "255;178;18", + "StartOpacity": 255, + "SpayConeAngle": 360, + "StartSizeMin": 32, + "StartSizeMax": 128, + "EndScale": 1, + "Flow": 2000, + "LifespanMin": 0.05, + "LifespanMax": 1, + "Duration": 0.05, + "StartSpeedMin": 50, + "StartSpeedMax": 600, + "ShouldAutodestruct": true, + "Gravity": 200, + "GravityTop": "Z+", + "Blending": "Additive" + }, + "childrenContent": { + "Particle": { + "adaptCollisionMaskAutomatically": true, + "updateIfNotVisible": false, + "animations": [ + { + "name": "Image", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": false, + "image": "LightGlow.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 0, + "y": 0 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [] + } + ] + } + ] + } + ] + } + } + }, + { + "assetStoreId": "", + "name": "Bullet", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Dynamic", + "bullet": true, + "fixedRotation": false, + "shape": "Capsule", + "shapeOrientation": "X", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 2, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "ObjectCenter", + "crossfadeDuration": 0.10000000149011612, + "depth": 12, + "height": 12, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": false, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets\\missile_orange.glb", + "originLocation": "ModelOrigin", + "rotationX": 0, + "rotationY": 0, + "rotationZ": 0, + "width": 24, + "animations": [] + } + }, + { + "assetStoreId": "8bfb3b8d354233214690205fb41cba03284ca34141297a0d98e09a2f080c029c", + "name": "BulletParticle", + "type": "ParticleEmitter3D::ParticleEmitter3D", + "variant": "", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "EndColor": "187;171;171", + "StartColor": "245;166;35", + "StartOpacity": 255, + "SpayConeAngle": 360, + "StartSizeMin": 24, + "StartSizeMax": 48, + "EndScale": 1, + "Flow": 200, + "LifespanMin": 0.075, + "LifespanMax": 0.125, + "Duration": 0.05, + "StartSpeedMin": 20, + "StartSpeedMax": 50, + "ShouldAutodestruct": true + }, + "childrenContent": { + "Particle": { + "adaptCollisionMaskAutomatically": true, + "updateIfNotVisible": false, + "animations": [ + { + "name": "Image", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": false, + "image": "LightGlow.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 0, + "y": 0 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [] + } + ] + } + ] + } + ] + } + } + }, + { + "adaptCollisionMaskAutomatically": true, + "assetStoreId": "523c449a51200d76f5572c755155325f5583aa5a0229949273647f2ac9db8c34", + "name": "ShootButton", + "persistentUuid": "b6dedead-3f75-47ca-94c6-a4e717e16a85", + "type": "Sprite", + "updateIfNotVisible": false, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "ButtonFSM", + "type": "ButtonStates::ButtonFSM", + "ShouldCheckHovering": true, + "State": "Idle", + "TouchId": 0, + "TouchIsInside": false, + "MouseIsInside": false, + "Index": 2.0247e-320 + }, + { + "name": "ButtonScaleTween", + "type": "ButtonStates::ButtonScaleTween", + "Scale": "Scale", + "ButtonFSM": "ButtonFSM", + "Tween": "Tween", + "IdleScale": 1, + "FocusedScale": 1.1, + "FadeInDuration": 0.02, + "FadeOutDuration": 0.05, + "PressedScale": 0.95, + "FadeInEasing": "easeOutQuad", + "FadeOutEasing": "easeInQuad", + "PreviousState": "Idle" + }, + { + "name": "Tween", + "type": "Tween::TweenBehavior" + } + ], + "animations": [ + { + "name": "", + "useMultipleDirections": false, + "directions": [ + { + "looping": true, + "timeBetweenFrames": 0.025, + "sprites": [ + { + "hasCustomCollisionMask": true, + "image": "assets\\Target round button.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 40, + "y": 40 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [ + [ + { + "x": 0, + "y": 0 + }, + { + "x": 80, + "y": 0 + }, + { + "x": 80, + "y": 80 + }, + { + "x": 0, + "y": 80 + } + ] + ] + } + ] + } + ] + } + ] + }, + { + "assetStoreId": "", + "name": "Terrain", + "type": "Terrain3D::Terrain3D", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 7, + "restitution": 0, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "Relief": "Flat", + "Seed": 1, + "Resolution": "256", + "TextureSize": 320, + "CliffLayer": "3", + "CliffAngle": 38, + "NormalMapStrength": 1, + "HeightBlending": 0.8, + "TextureVariation": 0.5, + "Edits": "[{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.1071},{\"tool\":\"raise\",\"x\":0,\"y\":0,\"toX\":1,\"toY\":0,\"radius\":0.1139,\"height\":0.3714},{\"tool\":\"raise\",\"x\":0,\"y\":1,\"toX\":1,\"toY\":1,\"radius\":0.1139,\"height\":0.3714},{\"tool\":\"raise\",\"x\":0,\"y\":0,\"toX\":0,\"toY\":1,\"radius\":0.1139,\"height\":0.3714},{\"tool\":\"raise\",\"x\":1,\"y\":0,\"toX\":1,\"toY\":1,\"radius\":0.1139,\"height\":0.3714},{\"tool\":\"raise\",\"x\":0.2559,\"y\":0,\"radius\":0.0977,\"height\":0.2714},{\"tool\":\"raise\",\"x\":0.6628,\"y\":0,\"radius\":0.1139,\"height\":0.3214},{\"tool\":\"raise\",\"x\":1,\"y\":0.3861,\"radius\":0.0977,\"height\":0.2714},{\"tool\":\"raise\",\"x\":1,\"y\":0.7441,\"radius\":0.0814,\"height\":0.2143},{\"tool\":\"raise\",\"x\":0.4512,\"y\":1,\"radius\":0.1058,\"height\":0.2571},{\"tool\":\"raise\",\"x\":0,\"y\":0.5651,\"radius\":0.0977,\"height\":0.2857},{\"tool\":\"raise\",\"x\":0,\"y\":0.1745,\"radius\":0.0814,\"height\":0.2143},{\"tool\":\"raise\",\"x\":0.2396,\"y\":0.2559,\"radius\":0.1139,\"height\":0.1429},{\"tool\":\"raise\",\"x\":0.7441,\"y\":0.2396,\"radius\":0.1302,\"height\":0.1714},{\"tool\":\"raise\",\"x\":0.7767,\"y\":0.7116,\"radius\":0.1058,\"height\":0.1143},{\"tool\":\"raise\",\"x\":0.2559,\"y\":0.7279,\"radius\":0.0977,\"height\":0.1},{\"tool\":\"raise\",\"x\":0.5326,\"y\":0.4512,\"radius\":0.0814,\"height\":0.0643},{\"tool\":\"raise\",\"x\":0.5977,\"y\":0.1257,\"radius\":0.0732,\"height\":0.05},{\"tool\":\"smooth\",\"x\":0.5,\"y\":0.5,\"radius\":0.4232,\"strength\":0.25},{\"tool\":\"flatten\",\"x\":0.3535,\"y\":0.8418,\"toX\":0.5651,\"toY\":0.8418,\"radius\":0.0846,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3535,\"y\":0.8418,\"toX\":0.5651,\"toY\":0.8418,\"radius\":0.0846,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3535,\"y\":0.8418,\"toX\":0.5651,\"toY\":0.8418,\"radius\":0.0846,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.0931,\"y\":0.8418,\"toX\":0.1257,\"toY\":0.8418,\"radius\":0.0381,\"z\":0.1146},{\"tool\":\"flatten\",\"x\":0.1257,\"y\":0.8418,\"toX\":0.1582,\"toY\":0.8418,\"radius\":0.0381,\"z\":0.1134},{\"tool\":\"flatten\",\"x\":0.1582,\"y\":0.8418,\"toX\":0.1908,\"toY\":0.8418,\"radius\":0.0381,\"z\":0.1126},{\"tool\":\"flatten\",\"x\":0.1908,\"y\":0.8418,\"toX\":0.2233,\"toY\":0.8418,\"radius\":0.0381,\"z\":0.1119},{\"tool\":\"flatten\",\"x\":0.2233,\"y\":0.8418,\"toX\":0.2559,\"toY\":0.8418,\"radius\":0.0381,\"z\":0.1094},{\"tool\":\"flatten\",\"x\":0.2559,\"y\":0.8418,\"toX\":0.2884,\"toY\":0.8418,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.2884,\"y\":0.8418,\"toX\":0.321,\"toY\":0.8418,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.321,\"y\":0.8418,\"toX\":0.3535,\"toY\":0.8418,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3535,\"y\":0.8418,\"toX\":0.3861,\"toY\":0.842,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3861,\"y\":0.842,\"toX\":0.4186,\"toY\":0.8428,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.4186,\"y\":0.8428,\"toX\":0.4512,\"toY\":0.8439,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.4512,\"y\":0.8439,\"toX\":0.4837,\"toY\":0.8451,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.4837,\"y\":0.8451,\"toX\":0.5163,\"toY\":0.8454,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.5163,\"y\":0.8454,\"toX\":0.5487,\"toY\":0.8438,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.5487,\"y\":0.8438,\"toX\":0.5811,\"toY\":0.8395,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.5811,\"y\":0.8395,\"toX\":0.6133,\"toY\":0.8348,\"radius\":0.0381,\"z\":0.1081},{\"tool\":\"flatten\",\"x\":0.6133,\"y\":0.8348,\"toX\":0.645,\"toY\":0.8278,\"radius\":0.0381,\"z\":0.1128},{\"tool\":\"flatten\",\"x\":0.645,\"y\":0.8278,\"toX\":0.6755,\"toY\":0.8164,\"radius\":0.0381,\"z\":0.1224},{\"tool\":\"flatten\",\"x\":0.6755,\"y\":0.8164,\"toX\":0.7031,\"toY\":0.7995,\"radius\":0.0381,\"z\":0.1344},{\"tool\":\"flatten\",\"x\":0.7031,\"y\":0.7995,\"toX\":0.728,\"toY\":0.7785,\"radius\":0.0381,\"z\":0.1445},{\"tool\":\"flatten\",\"x\":0.728,\"y\":0.7785,\"toX\":0.7508,\"toY\":0.7554,\"radius\":0.0381,\"z\":0.1497},{\"tool\":\"flatten\",\"x\":0.7508,\"y\":0.7554,\"toX\":0.7721,\"toY\":0.7306,\"radius\":0.0381,\"z\":0.1509},{\"tool\":\"flatten\",\"x\":0.7721,\"y\":0.7306,\"toX\":0.7918,\"toY\":0.7048,\"radius\":0.0381,\"z\":0.1509},{\"tool\":\"flatten\",\"x\":0.7918,\"y\":0.7048,\"toX\":0.8097,\"toY\":0.6776,\"radius\":0.0381,\"z\":0.1509},{\"tool\":\"flatten\",\"x\":0.8097,\"y\":0.6776,\"toX\":0.8258,\"toY\":0.6493,\"radius\":0.0381,\"z\":0.15},{\"tool\":\"flatten\",\"x\":0.8258,\"y\":0.6493,\"toX\":0.8395,\"toY\":0.6198,\"radius\":0.0381,\"z\":0.1453},{\"tool\":\"flatten\",\"x\":0.8395,\"y\":0.6198,\"toX\":0.8504,\"toY\":0.589,\"radius\":0.0381,\"z\":0.1356},{\"tool\":\"flatten\",\"x\":0.8504,\"y\":0.589,\"toX\":0.859,\"toY\":0.5578,\"radius\":0.0381,\"z\":0.1237},{\"tool\":\"flatten\",\"x\":0.859,\"y\":0.5578,\"toX\":0.8657,\"toY\":0.5259,\"radius\":0.0381,\"z\":0.1136},{\"tool\":\"flatten\",\"x\":0.8657,\"y\":0.5259,\"toX\":0.8706,\"toY\":0.4937,\"radius\":0.0381,\"z\":0.1083},{\"tool\":\"flatten\",\"x\":0.8706,\"y\":0.4937,\"toX\":0.8739,\"toY\":0.4613,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.8739,\"y\":0.4613,\"toX\":0.8755,\"toY\":0.4287,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.8755,\"y\":0.4287,\"toX\":0.8755,\"toY\":0.3962,\"radius\":0.0381,\"z\":0.1076},{\"tool\":\"flatten\",\"x\":0.8755,\"y\":0.3962,\"toX\":0.8739,\"toY\":0.3638,\"radius\":0.0381,\"z\":0.1096},{\"tool\":\"flatten\",\"x\":0.8739,\"y\":0.3638,\"toX\":0.8693,\"toY\":0.3315,\"radius\":0.0381,\"z\":0.113},{\"tool\":\"flatten\",\"x\":0.8693,\"y\":0.3315,\"toX\":0.8626,\"toY\":0.2996,\"radius\":0.0381,\"z\":0.1162},{\"tool\":\"flatten\",\"x\":0.8626,\"y\":0.2996,\"toX\":0.8543,\"toY\":0.2682,\"radius\":0.0381,\"z\":0.1177},{\"tool\":\"flatten\",\"x\":0.8543,\"y\":0.2682,\"toX\":0.8452,\"toY\":0.237,\"radius\":0.0381,\"z\":0.118},{\"tool\":\"flatten\",\"x\":0.8452,\"y\":0.237,\"toX\":0.8353,\"toY\":0.2059,\"radius\":0.0381,\"z\":0.1187},{\"tool\":\"flatten\",\"x\":0.8353,\"y\":0.2059,\"toX\":0.825,\"toY\":0.175,\"radius\":0.0381,\"z\":0.1202},{\"tool\":\"flatten\",\"x\":0.825,\"y\":0.175,\"toX\":0.8148,\"toY\":0.1442,\"radius\":0.0381,\"z\":0.1223},{\"tool\":\"flatten\",\"x\":0.8148,\"y\":0.1442,\"toX\":0.8092,\"toY\":0.1257,\"radius\":0.0381,\"z\":0.1241},{\"tool\":\"flatten\",\"x\":0.0931,\"y\":0.8418,\"toX\":0.1257,\"toY\":0.8418,\"radius\":0.0381,\"z\":0.1146},{\"tool\":\"flatten\",\"x\":0.1257,\"y\":0.8418,\"toX\":0.1582,\"toY\":0.8418,\"radius\":0.0381,\"z\":0.1134},{\"tool\":\"flatten\",\"x\":0.1582,\"y\":0.8418,\"toX\":0.1908,\"toY\":0.8418,\"radius\":0.0381,\"z\":0.1126},{\"tool\":\"flatten\",\"x\":0.1908,\"y\":0.8418,\"toX\":0.2233,\"toY\":0.8418,\"radius\":0.0381,\"z\":0.1119},{\"tool\":\"flatten\",\"x\":0.2233,\"y\":0.8418,\"toX\":0.2559,\"toY\":0.8418,\"radius\":0.0381,\"z\":0.1094},{\"tool\":\"flatten\",\"x\":0.2559,\"y\":0.8418,\"toX\":0.2884,\"toY\":0.8418,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.2884,\"y\":0.8418,\"toX\":0.321,\"toY\":0.8418,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.321,\"y\":0.8418,\"toX\":0.3535,\"toY\":0.8418,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3535,\"y\":0.8418,\"toX\":0.3861,\"toY\":0.842,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3861,\"y\":0.842,\"toX\":0.4186,\"toY\":0.8428,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.4186,\"y\":0.8428,\"toX\":0.4512,\"toY\":0.8439,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.4512,\"y\":0.8439,\"toX\":0.4837,\"toY\":0.8451,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.4837,\"y\":0.8451,\"toX\":0.5163,\"toY\":0.8454,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.5163,\"y\":0.8454,\"toX\":0.5487,\"toY\":0.8438,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.5487,\"y\":0.8438,\"toX\":0.5811,\"toY\":0.8395,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.5811,\"y\":0.8395,\"toX\":0.6133,\"toY\":0.8348,\"radius\":0.0381,\"z\":0.1081},{\"tool\":\"flatten\",\"x\":0.6133,\"y\":0.8348,\"toX\":0.645,\"toY\":0.8278,\"radius\":0.0381,\"z\":0.1128},{\"tool\":\"flatten\",\"x\":0.645,\"y\":0.8278,\"toX\":0.6755,\"toY\":0.8164,\"radius\":0.0381,\"z\":0.1224},{\"tool\":\"flatten\",\"x\":0.6755,\"y\":0.8164,\"toX\":0.7031,\"toY\":0.7995,\"radius\":0.0381,\"z\":0.1344},{\"tool\":\"flatten\",\"x\":0.7031,\"y\":0.7995,\"toX\":0.728,\"toY\":0.7785,\"radius\":0.0381,\"z\":0.1445},{\"tool\":\"flatten\",\"x\":0.728,\"y\":0.7785,\"toX\":0.7508,\"toY\":0.7554,\"radius\":0.0381,\"z\":0.1497},{\"tool\":\"flatten\",\"x\":0.7508,\"y\":0.7554,\"toX\":0.7721,\"toY\":0.7306,\"radius\":0.0381,\"z\":0.1509},{\"tool\":\"flatten\",\"x\":0.7721,\"y\":0.7306,\"toX\":0.7918,\"toY\":0.7048,\"radius\":0.0381,\"z\":0.1509},{\"tool\":\"flatten\",\"x\":0.7918,\"y\":0.7048,\"toX\":0.8097,\"toY\":0.6776,\"radius\":0.0381,\"z\":0.1509},{\"tool\":\"flatten\",\"x\":0.8097,\"y\":0.6776,\"toX\":0.8258,\"toY\":0.6493,\"radius\":0.0381,\"z\":0.15},{\"tool\":\"flatten\",\"x\":0.8258,\"y\":0.6493,\"toX\":0.8395,\"toY\":0.6198,\"radius\":0.0381,\"z\":0.1453},{\"tool\":\"flatten\",\"x\":0.8395,\"y\":0.6198,\"toX\":0.8504,\"toY\":0.589,\"radius\":0.0381,\"z\":0.1356},{\"tool\":\"flatten\",\"x\":0.8504,\"y\":0.589,\"toX\":0.859,\"toY\":0.5578,\"radius\":0.0381,\"z\":0.1237},{\"tool\":\"flatten\",\"x\":0.859,\"y\":0.5578,\"toX\":0.8657,\"toY\":0.5259,\"radius\":0.0381,\"z\":0.1136},{\"tool\":\"flatten\",\"x\":0.8657,\"y\":0.5259,\"toX\":0.8706,\"toY\":0.4937,\"radius\":0.0381,\"z\":0.1083},{\"tool\":\"flatten\",\"x\":0.8706,\"y\":0.4937,\"toX\":0.8739,\"toY\":0.4613,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.8739,\"y\":0.4613,\"toX\":0.8755,\"toY\":0.4287,\"radius\":0.0381,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.8755,\"y\":0.4287,\"toX\":0.8755,\"toY\":0.3962,\"radius\":0.0381,\"z\":0.1076},{\"tool\":\"flatten\",\"x\":0.8755,\"y\":0.3962,\"toX\":0.8739,\"toY\":0.3638,\"radius\":0.0381,\"z\":0.1096},{\"tool\":\"flatten\",\"x\":0.8739,\"y\":0.3638,\"toX\":0.8693,\"toY\":0.3315,\"radius\":0.0381,\"z\":0.113},{\"tool\":\"flatten\",\"x\":0.8693,\"y\":0.3315,\"toX\":0.8626,\"toY\":0.2996,\"radius\":0.0381,\"z\":0.1162},{\"tool\":\"flatten\",\"x\":0.8626,\"y\":0.2996,\"toX\":0.8543,\"toY\":0.2682,\"radius\":0.0381,\"z\":0.1177},{\"tool\":\"flatten\",\"x\":0.8543,\"y\":0.2682,\"toX\":0.8452,\"toY\":0.237,\"radius\":0.0381,\"z\":0.118},{\"tool\":\"flatten\",\"x\":0.8452,\"y\":0.237,\"toX\":0.8353,\"toY\":0.2059,\"radius\":0.0381,\"z\":0.1187},{\"tool\":\"flatten\",\"x\":0.8353,\"y\":0.2059,\"toX\":0.825,\"toY\":0.175,\"radius\":0.0381,\"z\":0.1202},{\"tool\":\"flatten\",\"x\":0.825,\"y\":0.175,\"toX\":0.8148,\"toY\":0.1442,\"radius\":0.0381,\"z\":0.1223},{\"tool\":\"flatten\",\"x\":0.8148,\"y\":0.1442,\"toX\":0.8092,\"toY\":0.1257,\"radius\":0.0381,\"z\":0.1241},{\"tool\":\"flatten\",\"x\":0.4349,\"y\":0.8418,\"toX\":0.418,\"toY\":0.814,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.418,\"y\":0.814,\"toX\":0.4004,\"toY\":0.7866,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.4004,\"y\":0.7866,\"toX\":0.3836,\"toY\":0.7586,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3836,\"y\":0.7586,\"toX\":0.3688,\"toY\":0.7297,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3688,\"y\":0.7297,\"toX\":0.3574,\"toY\":0.6992,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3574,\"y\":0.6992,\"toX\":0.3534,\"toY\":0.6672,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3534,\"y\":0.6672,\"toX\":0.3597,\"toY\":0.6353,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3597,\"y\":0.6353,\"toX\":0.3714,\"toY\":0.605,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3714,\"y\":0.605,\"toX\":0.3848,\"toY\":0.5752,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3848,\"y\":0.5752,\"toX\":0.3963,\"toY\":0.5449,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3963,\"y\":0.5449,\"toX\":0.4025,\"toY\":0.513,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.4025,\"y\":0.513,\"toX\":0.3983,\"toY\":0.481,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3983,\"y\":0.481,\"toX\":0.3846,\"toY\":0.4515,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3846,\"y\":0.4515,\"toX\":0.367,\"toY\":0.424,\"radius\":0.0352,\"z\":0.1077},{\"tool\":\"flatten\",\"x\":0.367,\"y\":0.424,\"toX\":0.349,\"toY\":0.397,\"radius\":0.0352,\"z\":0.1091},{\"tool\":\"flatten\",\"x\":0.349,\"y\":0.397,\"toX\":0.3327,\"toY\":0.3688,\"radius\":0.0352,\"z\":0.11},{\"tool\":\"flatten\",\"x\":0.3327,\"y\":0.3688,\"toX\":0.3221,\"toY\":0.3381,\"radius\":0.0352,\"z\":0.1101},{\"tool\":\"flatten\",\"x\":0.3221,\"y\":0.3381,\"toX\":0.3229,\"toY\":0.3058,\"radius\":0.0352,\"z\":0.1101},{\"tool\":\"flatten\",\"x\":0.3229,\"y\":0.3058,\"toX\":0.3325,\"toY\":0.2747,\"radius\":0.0352,\"z\":0.1101},{\"tool\":\"flatten\",\"x\":0.3325,\"y\":0.2747,\"toX\":0.3462,\"toY\":0.2451,\"radius\":0.0352,\"z\":0.1101},{\"tool\":\"flatten\",\"x\":0.3462,\"y\":0.2451,\"toX\":0.362,\"toY\":0.2166,\"radius\":0.0352,\"z\":0.1103},{\"tool\":\"flatten\",\"x\":0.362,\"y\":0.2166,\"toX\":0.3789,\"toY\":0.189,\"radius\":0.0352,\"z\":0.1107},{\"tool\":\"flatten\",\"x\":0.3789,\"y\":0.189,\"toX\":0.3966,\"toY\":0.1616,\"radius\":0.0352,\"z\":0.1103},{\"tool\":\"flatten\",\"x\":0.3966,\"y\":0.1616,\"toX\":0.4186,\"toY\":0.1257,\"radius\":0.0352,\"z\":0.1087},{\"tool\":\"flatten\",\"x\":0.4349,\"y\":0.8418,\"toX\":0.418,\"toY\":0.814,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.418,\"y\":0.814,\"toX\":0.4004,\"toY\":0.7866,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.4004,\"y\":0.7866,\"toX\":0.3836,\"toY\":0.7586,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3836,\"y\":0.7586,\"toX\":0.3688,\"toY\":0.7297,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3688,\"y\":0.7297,\"toX\":0.3574,\"toY\":0.6992,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3574,\"y\":0.6992,\"toX\":0.3534,\"toY\":0.6672,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3534,\"y\":0.6672,\"toX\":0.3597,\"toY\":0.6353,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3597,\"y\":0.6353,\"toX\":0.3714,\"toY\":0.605,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3714,\"y\":0.605,\"toX\":0.3848,\"toY\":0.5752,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3848,\"y\":0.5752,\"toX\":0.3963,\"toY\":0.5449,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3963,\"y\":0.5449,\"toX\":0.4025,\"toY\":0.513,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.4025,\"y\":0.513,\"toX\":0.3983,\"toY\":0.481,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3983,\"y\":0.481,\"toX\":0.3846,\"toY\":0.4515,\"radius\":0.0352,\"z\":0.1071},{\"tool\":\"flatten\",\"x\":0.3846,\"y\":0.4515,\"toX\":0.367,\"toY\":0.424,\"radius\":0.0352,\"z\":0.1077},{\"tool\":\"flatten\",\"x\":0.367,\"y\":0.424,\"toX\":0.349,\"toY\":0.397,\"radius\":0.0352,\"z\":0.1091},{\"tool\":\"flatten\",\"x\":0.349,\"y\":0.397,\"toX\":0.3327,\"toY\":0.3688,\"radius\":0.0352,\"z\":0.11},{\"tool\":\"flatten\",\"x\":0.3327,\"y\":0.3688,\"toX\":0.3221,\"toY\":0.3381,\"radius\":0.0352,\"z\":0.1101},{\"tool\":\"flatten\",\"x\":0.3221,\"y\":0.3381,\"toX\":0.3229,\"toY\":0.3058,\"radius\":0.0352,\"z\":0.1101},{\"tool\":\"flatten\",\"x\":0.3229,\"y\":0.3058,\"toX\":0.3325,\"toY\":0.2747,\"radius\":0.0352,\"z\":0.1101},{\"tool\":\"flatten\",\"x\":0.3325,\"y\":0.2747,\"toX\":0.3462,\"toY\":0.2451,\"radius\":0.0352,\"z\":0.1101},{\"tool\":\"flatten\",\"x\":0.3462,\"y\":0.2451,\"toX\":0.362,\"toY\":0.2166,\"radius\":0.0352,\"z\":0.1103},{\"tool\":\"flatten\",\"x\":0.362,\"y\":0.2166,\"toX\":0.3789,\"toY\":0.189,\"radius\":0.0352,\"z\":0.1107},{\"tool\":\"flatten\",\"x\":0.3789,\"y\":0.189,\"toX\":0.3966,\"toY\":0.1616,\"radius\":0.0352,\"z\":0.1103},{\"tool\":\"flatten\",\"x\":0.3966,\"y\":0.1616,\"toX\":0.4186,\"toY\":0.1257,\"radius\":0.0352,\"z\":0.1087},{\"tool\":\"smooth\",\"x\":0.0931,\"y\":0.8418,\"toX\":0.1908,\"toY\":0.8418,\"radius\":0.0317,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.1908,\"y\":0.8418,\"toX\":0.2884,\"toY\":0.8418,\"radius\":0.0317,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.2884,\"y\":0.8418,\"toX\":0.3861,\"toY\":0.842,\"radius\":0.0317,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.3861,\"y\":0.842,\"toX\":0.4837,\"toY\":0.8451,\"radius\":0.0317,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.4837,\"y\":0.8451,\"toX\":0.5811,\"toY\":0.8395,\"radius\":0.0317,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.5811,\"y\":0.8395,\"toX\":0.6755,\"toY\":0.8164,\"radius\":0.0317,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.6755,\"y\":0.8164,\"toX\":0.7508,\"toY\":0.7554,\"radius\":0.0317,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.7508,\"y\":0.7554,\"toX\":0.8097,\"toY\":0.6776,\"radius\":0.0317,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.8097,\"y\":0.6776,\"toX\":0.8504,\"toY\":0.589,\"radius\":0.0317,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.8504,\"y\":0.589,\"toX\":0.8706,\"toY\":0.4937,\"radius\":0.0317,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.8706,\"y\":0.4937,\"toX\":0.8755,\"toY\":0.3962,\"radius\":0.0317,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.8755,\"y\":0.3962,\"toX\":0.8626,\"toY\":0.2996,\"radius\":0.0317,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.8626,\"y\":0.2996,\"toX\":0.8353,\"toY\":0.2059,\"radius\":0.0317,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.8353,\"y\":0.2059,\"toX\":0.8092,\"toY\":0.1257,\"radius\":0.0317,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.4349,\"y\":0.8418,\"toX\":0.3836,\"toY\":0.7586,\"radius\":0.0293,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.3836,\"y\":0.7586,\"toX\":0.3534,\"toY\":0.6672,\"radius\":0.0293,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.3534,\"y\":0.6672,\"toX\":0.3848,\"toY\":0.5752,\"radius\":0.0293,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.3848,\"y\":0.5752,\"toX\":0.3983,\"toY\":0.481,\"radius\":0.0293,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.3983,\"y\":0.481,\"toX\":0.349,\"toY\":0.397,\"radius\":0.0293,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.349,\"y\":0.397,\"toX\":0.3229,\"toY\":0.3058,\"radius\":0.0293,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.3229,\"y\":0.3058,\"toX\":0.362,\"toY\":0.2166,\"radius\":0.0293,\"strength\":0.35},{\"tool\":\"smooth\",\"x\":0.362,\"y\":0.2166,\"toX\":0.4186,\"toY\":0.1257,\"radius\":0.0293,\"strength\":0.35},{\"tool\":\"paint\",\"x\":0.0931,\"y\":0.8418,\"toX\":0.1908,\"toY\":0.8418,\"radius\":0.0262,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.1908,\"y\":0.8418,\"toX\":0.2884,\"toY\":0.8418,\"radius\":0.0262,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.2884,\"y\":0.8418,\"toX\":0.3861,\"toY\":0.842,\"radius\":0.0262,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.3861,\"y\":0.842,\"toX\":0.4837,\"toY\":0.8451,\"radius\":0.0262,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.4837,\"y\":0.8451,\"toX\":0.5811,\"toY\":0.8395,\"radius\":0.0262,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.5811,\"y\":0.8395,\"toX\":0.6755,\"toY\":0.8164,\"radius\":0.0262,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.6755,\"y\":0.8164,\"toX\":0.7508,\"toY\":0.7554,\"radius\":0.0262,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.7508,\"y\":0.7554,\"toX\":0.8097,\"toY\":0.6776,\"radius\":0.0262,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.8097,\"y\":0.6776,\"toX\":0.8504,\"toY\":0.589,\"radius\":0.0262,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.8504,\"y\":0.589,\"toX\":0.8706,\"toY\":0.4937,\"radius\":0.0262,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.8706,\"y\":0.4937,\"toX\":0.8755,\"toY\":0.3962,\"radius\":0.0262,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.8755,\"y\":0.3962,\"toX\":0.8626,\"toY\":0.2996,\"radius\":0.0262,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.8626,\"y\":0.2996,\"toX\":0.8353,\"toY\":0.2059,\"radius\":0.0262,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.8353,\"y\":0.2059,\"toX\":0.8092,\"toY\":0.1257,\"radius\":0.0262,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.4349,\"y\":0.8418,\"toX\":0.3836,\"toY\":0.7586,\"radius\":0.0242,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.3836,\"y\":0.7586,\"toX\":0.3534,\"toY\":0.6672,\"radius\":0.0242,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.3534,\"y\":0.6672,\"toX\":0.3848,\"toY\":0.5752,\"radius\":0.0242,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.3848,\"y\":0.5752,\"toX\":0.3983,\"toY\":0.481,\"radius\":0.0242,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.3983,\"y\":0.481,\"toX\":0.349,\"toY\":0.397,\"radius\":0.0242,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.349,\"y\":0.397,\"toX\":0.3229,\"toY\":0.3058,\"radius\":0.0242,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.3229,\"y\":0.3058,\"toX\":0.362,\"toY\":0.2166,\"radius\":0.0242,\"layer\":2,\"strength\":0.85},{\"tool\":\"paint\",\"x\":0.362,\"y\":0.2166,\"toX\":0.4186,\"toY\":0.1257,\"radius\":0.0242,\"layer\":2,\"strength\":0.85},{\"tool\":\"raise\",\"x\":0.1094,\"y\":0.5977,\"toX\":0.1663,\"toY\":0.5488,\"radius\":0.0179,\"height\":-0.0393},{\"tool\":\"paint\",\"x\":0.1094,\"y\":0.5977,\"toX\":0.1663,\"toY\":0.5488,\"radius\":0.0244,\"layer\":4,\"strength\":0.9},{\"tool\":\"raise\",\"x\":0.1663,\"y\":0.5488,\"toX\":0.2233,\"toY\":0.6058,\"radius\":0.0179,\"height\":-0.0393},{\"tool\":\"paint\",\"x\":0.1663,\"y\":0.5488,\"toX\":0.2233,\"toY\":0.6058,\"radius\":0.0244,\"layer\":4,\"strength\":0.9},{\"tool\":\"raise\",\"x\":0.2233,\"y\":0.6058,\"toX\":0.2803,\"toY\":0.5488,\"radius\":0.0179,\"height\":-0.0393},{\"tool\":\"paint\",\"x\":0.2233,\"y\":0.6058,\"toX\":0.2803,\"toY\":0.5488,\"radius\":0.0244,\"layer\":4,\"strength\":0.9},{\"tool\":\"raise\",\"x\":0.5651,\"y\":0.5814,\"toX\":0.6302,\"toY\":0.5326,\"radius\":0.0179,\"height\":-0.0393},{\"tool\":\"paint\",\"x\":0.5651,\"y\":0.5814,\"toX\":0.6302,\"toY\":0.5326,\"radius\":0.0244,\"layer\":4,\"strength\":0.9},{\"tool\":\"raise\",\"x\":0.6302,\"y\":0.5326,\"toX\":0.6953,\"toY\":0.5895,\"radius\":0.0179,\"height\":-0.0393},{\"tool\":\"paint\",\"x\":0.6302,\"y\":0.5326,\"toX\":0.6953,\"toY\":0.5895,\"radius\":0.0244,\"layer\":4,\"strength\":0.9},{\"tool\":\"raise\",\"x\":0.6953,\"y\":0.5895,\"toX\":0.7441,\"toY\":0.5326,\"radius\":0.0179,\"height\":-0.0393},{\"tool\":\"paint\",\"x\":0.6953,\"y\":0.5895,\"toX\":0.7441,\"toY\":0.5326,\"radius\":0.0244,\"layer\":4,\"strength\":0.9},{\"tool\":\"raise\",\"x\":0.4512,\"y\":0.2233,\"toX\":0.5163,\"toY\":0.2721,\"radius\":0.0179,\"height\":-0.0393},{\"tool\":\"paint\",\"x\":0.4512,\"y\":0.2233,\"toX\":0.5163,\"toY\":0.2721,\"radius\":0.0244,\"layer\":4,\"strength\":0.9},{\"tool\":\"raise\",\"x\":0.5163,\"y\":0.2721,\"toX\":0.5814,\"toY\":0.2152,\"radius\":0.0179,\"height\":-0.0393},{\"tool\":\"paint\",\"x\":0.5163,\"y\":0.2721,\"toX\":0.5814,\"toY\":0.2152,\"radius\":0.0244,\"layer\":4,\"strength\":0.9},{\"tool\":\"raise\",\"x\":0.5814,\"y\":0.2152,\"toX\":0.6465,\"toY\":0.264,\"radius\":0.0179,\"height\":-0.0393},{\"tool\":\"paint\",\"x\":0.5814,\"y\":0.2152,\"toX\":0.6465,\"toY\":0.264,\"radius\":0.0244,\"layer\":4,\"strength\":0.9},{\"tool\":\"raise\",\"x\":0.6649,\"y\":0.1527,\"radius\":0.0206,\"height\":-0.0316},{\"tool\":\"paint\",\"x\":0.6649,\"y\":0.1527,\"radius\":0.0288,\"layer\":4,\"strength\":0.8},{\"tool\":\"raise\",\"x\":0.1817,\"y\":0.6597,\"radius\":0.0209,\"height\":-0.0321},{\"tool\":\"paint\",\"x\":0.1817,\"y\":0.6597,\"radius\":0.0293,\"layer\":4,\"strength\":0.8},{\"tool\":\"raise\",\"x\":0.4901,\"y\":0.2389,\"radius\":0.0181,\"height\":-0.0278},{\"tool\":\"paint\",\"x\":0.4901,\"y\":0.2389,\"radius\":0.0253,\"layer\":4,\"strength\":0.8},{\"tool\":\"raise\",\"x\":0.4826,\"y\":0.6812,\"radius\":0.0261,\"height\":-0.0401},{\"tool\":\"paint\",\"x\":0.4826,\"y\":0.6812,\"radius\":0.0365,\"layer\":4,\"strength\":0.8},{\"tool\":\"raise\",\"x\":0.5299,\"y\":0.1338,\"radius\":0.0261,\"height\":-0.04},{\"tool\":\"paint\",\"x\":0.5299,\"y\":0.1338,\"radius\":0.0365,\"layer\":4,\"strength\":0.8},{\"tool\":\"raise\",\"x\":0.7017,\"y\":0.42,\"radius\":0.024,\"height\":-0.0368},{\"tool\":\"paint\",\"x\":0.7017,\"y\":0.42,\"radius\":0.0335,\"layer\":4,\"strength\":0.8},{\"tool\":\"raise\",\"x\":0.2412,\"y\":0.3831,\"radius\":0.0241,\"height\":-0.037},{\"tool\":\"paint\",\"x\":0.2412,\"y\":0.3831,\"radius\":0.0337,\"layer\":4,\"strength\":0.8},{\"tool\":\"raise\",\"x\":0.1267,\"y\":0.1315,\"radius\":0.0209,\"height\":-0.0321},{\"tool\":\"paint\",\"x\":0.1267,\"y\":0.1315,\"radius\":0.0292,\"layer\":4,\"strength\":0.8},{\"tool\":\"raise\",\"x\":0.7718,\"y\":0.5477,\"radius\":0.0235,\"height\":-0.0361},{\"tool\":\"paint\",\"x\":0.7718,\"y\":0.5477,\"radius\":0.0329,\"layer\":4,\"strength\":0.8},{\"tool\":\"raise\",\"x\":0.473,\"y\":0.4733,\"radius\":0.0221,\"height\":-0.0339},{\"tool\":\"paint\",\"x\":0.473,\"y\":0.4733,\"radius\":0.0309,\"layer\":4,\"strength\":0.8},{\"tool\":\"raise\",\"x\":0.6232,\"y\":0.3862,\"radius\":0.0236,\"height\":-0.0363},{\"tool\":\"paint\",\"x\":0.6232,\"y\":0.3862,\"radius\":0.0331,\"layer\":4,\"strength\":0.8},{\"tool\":\"raise\",\"x\":0.8585,\"y\":0.7421,\"radius\":0.0239,\"height\":-0.0368},{\"tool\":\"paint\",\"x\":0.8585,\"y\":0.7421,\"radius\":0.0335,\"layer\":4,\"strength\":0.8},{\"tool\":\"raise\",\"x\":0.1752,\"y\":0.5019,\"radius\":0.0259,\"height\":-0.0398},{\"tool\":\"paint\",\"x\":0.1752,\"y\":0.5019,\"radius\":0.0363,\"layer\":4,\"strength\":0.8},{\"tool\":\"raise\",\"x\":0.5389,\"y\":0.5301,\"radius\":0.0197,\"height\":-0.0303},{\"tool\":\"paint\",\"x\":0.5389,\"y\":0.5301,\"radius\":0.0276,\"layer\":4,\"strength\":0.8}]", + "Layer1Color": "255;255;255", + "Layer1Texture": "assets/TerrainGrass.jpg", + "Layer1NormalMap": "assets/TerrainGrassNormal.jpg", + "Layer2Color": "255;255;255", + "Layer2Texture": "assets/TerrainDirt.jpg", + "Layer2NormalMap": "assets/TerrainDirtNormal.jpg", + "Layer3Color": "255;255;255", + "Layer3Texture": "assets/TerrainRock.jpg", + "Layer3NormalMap": "assets/TerrainRockNormal.jpg", + "Layer4Color": "255;255;255", + "Layer4Texture": "assets/TerrainMud.jpg", + "Layer4NormalMap": "assets/TerrainMudNormal.jpg" + } + }, + { + "assetStoreId": "", + "name": "MapLimit", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "width": 100, + "height": 100, + "depth": 100, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "frontFaceResourceName": "", + "backFaceResourceName": "", + "leftFaceResourceName": "", + "rightFaceResourceName": "", + "topFaceResourceName": "", + "bottomFaceResourceName": "", + "backFaceUpThroughWhichAxisRotation": "X", + "frontFaceVisible": false, + "backFaceVisible": false, + "leftFaceVisible": false, + "rightFaceVisible": false, + "topFaceVisible": false, + "bottomFaceVisible": false, + "frontFaceResourceRepeat": true, + "backFaceResourceRepeat": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceRepeat": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceRepeat": false, + "materialType": "StandardWithoutMetalness", + "isCastingShadow": false, + "isReceivingShadow": true + } + }, + { + "assetStoreId": "", + "name": "Grass", + "type": "Terrain3D::TerrainGrass", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Density": 20, + "BendingObject": "PlayerTank", + "BladeHeight": 14, + "BladeWidth": 2, + "BaseColor": "60;70;35", + "TipColor": "130;135;70", + "FadeDistance": 2500 + } + }, + { + "assetStoreId": "03bab29f0a842559922c6c7292a7d2fc2e8068d07194947e52d8761293854375", + "name": "BrickWall", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 33.4, + "height": 220, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Brick Wall 1.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 90, + "width": 20.3, + "animations": [] + } + }, + { + "assetStoreId": "bfe7a5bddc37bc6cb7932f9d6473a243538d684293cbe4ac0272d578db7f43c5", + "name": "BrickWallBroken", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 69.2, + "height": 220, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Brick Wall 2.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 90, + "width": 20.3, + "animations": [] + } + }, + { + "assetStoreId": "5f2bef6fa133e52f0cf621253d2806b9f6c86039cd6fbdfeaa7d6a1ef9e87604", + "name": "SackTrench", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 57.3, + "height": 150, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Sack Trench.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 90, + "width": 41.5, + "animations": [] + } + }, + { + "assetStoreId": "7a4b7ba415852f82a1a1a317ae465320508c0d4c56f2f3c876ceb0d673db0e7a", + "name": "Container", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 146.6, + "height": 300, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Container Long.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 90, + "width": 143.8, + "animations": [] + } + }, + { + "assetStoreId": "f2a8bbb03be950266a4dc806c3d82ad2e810e2342abb9cb13553d064564aff13", + "name": "Wreck", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 57.7, + "height": 86.5, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Debris Broken Car.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 90, + "width": 180, + "animations": [] + } + }, + { + "assetStoreId": "8f4bcc8262005c572b4e2b5c0624ee7b048fb89104114cf7d804875cc8a037bf", + "name": "WaterTank", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 260, + "height": 119.7, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Water Tank Platform.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 90, + "width": 173.6, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "Trees", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "Model": "assets/Tree 1.glb", + "Size": 230, + "Density": 0.02, + "Seed": 1, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.4512,\"y\":0.8418,\"radius\":0.1465},{\"tool\":\"erase\",\"x\":0.0931,\"y\":0.7279,\"toX\":0.2884,\"toY\":0.7279,\"radius\":0.0407},{\"tool\":\"erase\",\"x\":0.6649,\"y\":0.1527,\"radius\":0.0304},{\"tool\":\"erase\",\"x\":0.1817,\"y\":0.6597,\"radius\":0.0307},{\"tool\":\"erase\",\"x\":0.4901,\"y\":0.2389,\"radius\":0.0278},{\"tool\":\"erase\",\"x\":0.4826,\"y\":0.6812,\"radius\":0.0359},{\"tool\":\"erase\",\"x\":0.5299,\"y\":0.1338,\"radius\":0.0358},{\"tool\":\"erase\",\"x\":0.7017,\"y\":0.42,\"radius\":0.0337},{\"tool\":\"erase\",\"x\":0.2412,\"y\":0.3831,\"radius\":0.0338},{\"tool\":\"erase\",\"x\":0.1267,\"y\":0.1315,\"radius\":0.0306},{\"tool\":\"erase\",\"x\":0.7718,\"y\":0.5477,\"radius\":0.0333},{\"tool\":\"erase\",\"x\":0.473,\"y\":0.4733,\"radius\":0.0318},{\"tool\":\"erase\",\"x\":0.6232,\"y\":0.3862,\"radius\":0.0334},{\"tool\":\"erase\",\"x\":0.8585,\"y\":0.7421,\"radius\":0.0337},{\"tool\":\"erase\",\"x\":0.1752,\"y\":0.5019,\"radius\":0.0357},{\"tool\":\"erase\",\"x\":0.5389,\"y\":0.5301,\"radius\":0.0295},{\"tool\":\"erase\",\"x\":0.4674,\"y\":0.4186,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.4455,\"y\":0.4314,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.5651,\"y\":0.3535,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.5652,\"y\":0.3789,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.2396,\"y\":0.4023,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.2157,\"y\":0.3937,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.679,\"y\":0.4186,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.6596,\"y\":0.435,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.1582,\"y\":0.1582,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.1343,\"y\":0.167,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.5977,\"y\":0.7116,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.589,\"y\":0.7355,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.321,\"y\":0.5488,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.2955,\"y\":0.5489,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.1379,\"y\":0.6009,\"radius\":0.0187},{\"tool\":\"erase\",\"x\":0.1948,\"y\":0.605,\"radius\":0.0187},{\"tool\":\"erase\",\"x\":0.2518,\"y\":0.605,\"radius\":0.0187},{\"tool\":\"erase\",\"x\":0.5977,\"y\":0.5846,\"radius\":0.0187},{\"tool\":\"erase\",\"x\":0.6628,\"y\":0.5887,\"radius\":0.0187},{\"tool\":\"erase\",\"x\":0.7197,\"y\":0.5887,\"radius\":0.0187},{\"tool\":\"erase\",\"x\":0.4837,\"y\":0.2754,\"radius\":0.0187},{\"tool\":\"erase\",\"x\":0.5488,\"y\":0.2713,\"radius\":0.0187},{\"tool\":\"erase\",\"x\":0.6139,\"y\":0.2673,\"radius\":0.0187},{\"tool\":\"erase\",\"x\":0.7441,\"y\":0.5977,\"radius\":0.0309},{\"tool\":\"erase\",\"x\":0.4186,\"y\":0.1094,\"radius\":0.0309},{\"tool\":\"erase\",\"x\":0.8581,\"y\":0.1908,\"radius\":0.0309},{\"tool\":\"erase\",\"x\":0.1582,\"y\":0.8092,\"radius\":0.0212},{\"tool\":\"erase\",\"x\":0.6465,\"y\":0.1257,\"radius\":0.0212},{\"tool\":\"erase\",\"x\":0.207,\"y\":0.6628,\"radius\":0.0212},{\"tool\":\"erase\",\"x\":0.8092,\"y\":0.8906,\"radius\":0.0212},{\"tool\":\"erase\",\"x\":0.1257,\"y\":0.3372,\"radius\":0.0206}]", + "MaxSlope": 27, + "TrunkCollisions": true, + "TrunkDiameter": 20 + } + }, + { + "assetStoreId": "", + "name": "TallTrees", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "meshShapeResourceName": "", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 1, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "Model": "assets/Tree 2.glb", + "Size": 260, + "Density": 0.015, + "Seed": 2, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.4512,\"y\":0.8418,\"radius\":0.1465},{\"tool\":\"erase\",\"x\":0.0931,\"y\":0.7279,\"toX\":0.2884,\"toY\":0.7279,\"radius\":0.0407},{\"tool\":\"erase\",\"x\":0.6649,\"y\":0.1527,\"radius\":0.0304},{\"tool\":\"erase\",\"x\":0.1817,\"y\":0.6597,\"radius\":0.0307},{\"tool\":\"erase\",\"x\":0.4901,\"y\":0.2389,\"radius\":0.0278},{\"tool\":\"erase\",\"x\":0.4826,\"y\":0.6812,\"radius\":0.0359},{\"tool\":\"erase\",\"x\":0.5299,\"y\":0.1338,\"radius\":0.0358},{\"tool\":\"erase\",\"x\":0.7017,\"y\":0.42,\"radius\":0.0337},{\"tool\":\"erase\",\"x\":0.2412,\"y\":0.3831,\"radius\":0.0338},{\"tool\":\"erase\",\"x\":0.1267,\"y\":0.1315,\"radius\":0.0306},{\"tool\":\"erase\",\"x\":0.7718,\"y\":0.5477,\"radius\":0.0333},{\"tool\":\"erase\",\"x\":0.473,\"y\":0.4733,\"radius\":0.0318},{\"tool\":\"erase\",\"x\":0.6232,\"y\":0.3862,\"radius\":0.0334},{\"tool\":\"erase\",\"x\":0.8585,\"y\":0.7421,\"radius\":0.0337},{\"tool\":\"erase\",\"x\":0.1752,\"y\":0.5019,\"radius\":0.0357},{\"tool\":\"erase\",\"x\":0.5389,\"y\":0.5301,\"radius\":0.0295},{\"tool\":\"erase\",\"x\":0.4674,\"y\":0.4186,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.4455,\"y\":0.4314,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.5651,\"y\":0.3535,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.5652,\"y\":0.3789,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.2396,\"y\":0.4023,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.2157,\"y\":0.3937,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.679,\"y\":0.4186,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.6596,\"y\":0.435,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.1582,\"y\":0.1582,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.1343,\"y\":0.167,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.5977,\"y\":0.7116,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.589,\"y\":0.7355,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.321,\"y\":0.5488,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.2955,\"y\":0.5489,\"radius\":0.0244},{\"tool\":\"erase\",\"x\":0.1379,\"y\":0.6009,\"radius\":0.0187},{\"tool\":\"erase\",\"x\":0.1948,\"y\":0.605,\"radius\":0.0187},{\"tool\":\"erase\",\"x\":0.2518,\"y\":0.605,\"radius\":0.0187},{\"tool\":\"erase\",\"x\":0.5977,\"y\":0.5846,\"radius\":0.0187},{\"tool\":\"erase\",\"x\":0.6628,\"y\":0.5887,\"radius\":0.0187},{\"tool\":\"erase\",\"x\":0.7197,\"y\":0.5887,\"radius\":0.0187},{\"tool\":\"erase\",\"x\":0.4837,\"y\":0.2754,\"radius\":0.0187},{\"tool\":\"erase\",\"x\":0.5488,\"y\":0.2713,\"radius\":0.0187},{\"tool\":\"erase\",\"x\":0.6139,\"y\":0.2673,\"radius\":0.0187},{\"tool\":\"erase\",\"x\":0.7441,\"y\":0.5977,\"radius\":0.0309},{\"tool\":\"erase\",\"x\":0.4186,\"y\":0.1094,\"radius\":0.0309},{\"tool\":\"erase\",\"x\":0.8581,\"y\":0.1908,\"radius\":0.0309},{\"tool\":\"erase\",\"x\":0.1582,\"y\":0.8092,\"radius\":0.0212},{\"tool\":\"erase\",\"x\":0.6465,\"y\":0.1257,\"radius\":0.0212},{\"tool\":\"erase\",\"x\":0.207,\"y\":0.6628,\"radius\":0.0212},{\"tool\":\"erase\",\"x\":0.8092,\"y\":0.8906,\"radius\":0.0212},{\"tool\":\"erase\",\"x\":0.1257,\"y\":0.3372,\"radius\":0.0206}]", + "MaxSlope": 27, + "TrunkCollisions": true, + "TrunkDiameter": 16 + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "PlayerTank" + }, + { + "objectName": "Bullet" + }, + { + "objectName": "Target" + }, + { + "objectName": "BulletParticle" + }, + { + "objectName": "ExplosionParticle" + }, + { + "folderName": "Mobile Controls", + "children": [ + { + "objectName": "Steer_Joystick" + }, + { + "objectName": "Cannon_Joystick" + }, + { + "objectName": "ShootButton" + } + ] + }, + { + "folderName": "World", + "children": [ + { + "objectName": "Terrain" + }, + { + "objectName": "MapLimit" + }, + { + "objectName": "Grass" + } + ] + }, + { + "folderName": "Battlefield", + "children": [ + { + "objectName": "BrickWall" + }, + { + "objectName": "BrickWallBroken" + }, + { + "objectName": "SackTrench" + }, + { + "objectName": "Container" + }, + { + "objectName": "Wreck" + }, + { + "objectName": "WaterTank" + } + ] + }, + { + "folderName": "Nature", + "children": [ + { + "objectName": "Trees" + }, + { + "objectName": "TallTrees" + } + ] + } + ] + }, + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetTargetedRotationAngle" + }, + "parameters": [ + "PlayerTank", + "ThirdPersonCamera", + "=", + "PlayerTank.TopRotation() + PlayerTank.Angle()", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Mobile controls" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::IsPressed" + }, + "parameters": [ + "Cannon_Joystick", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TankConfiguration::CombinedTank::SetTopRotation" + }, + "parameters": [ + "PlayerTank", + "+", + "Cannon_Joystick.StickForceX()", + "" + ] + }, + { + "type": { + "value": "TankConfiguration::CombinedTank::SetCannonAngle" + }, + "parameters": [ + "PlayerTank", + "+", + "Cannon_Joystick.StickForceY()/2", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::StickForceX" + }, + "parameters": [ + "Steer_Joystick", + "<", + "-0.3", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::StickForceX" + }, + "parameters": [ + "Steer_Joystick", + ">", + "0.3", + "" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateSteeringStick" + }, + "parameters": [ + "PlayerTank", + "PhysicsCar3D", + "Steer_Joystick.StickForceX()/3" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::IsPressed" + }, + "parameters": [ + "Steer_Joystick", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateAcceleratorStick" + }, + "parameters": [ + "PlayerTank", + "PhysicsCar3D", + "-1 * sign(Steer_Joystick.StickForceY())" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Keyboard controls" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"d\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TankConfiguration::CombinedTank::SetTopRotation" + }, + "parameters": [ + "PlayerTank", + "+", + "1", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"a\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TankConfiguration::CombinedTank::SetTopRotation" + }, + "parameters": [ + "PlayerTank", + "-", + "1", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"w\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TankConfiguration::CombinedTank::SetCannonAngle" + }, + "parameters": [ + "PlayerTank", + "+", + "0.75", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"s\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TankConfiguration::CombinedTank::SetCannonAngle" + }, + "parameters": [ + "PlayerTank", + "-", + "0.75", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Bullet logic" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"f\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "ShootButton", + "ButtonFSM", + "" + ] + } + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [ + { + "type": { + "value": "Create" + }, + "parameters": [ + "", + "Bullet", + "PlayerTank.CenterX() + XFromAngleAndDistance(PlayerTank.Angle() + PlayerTank.TopRotation(), PlayerTank.CannonWidth())", + "PlayerTank.CenterY() + YFromAngleAndDistance(PlayerTank.Angle() + PlayerTank.TopRotation(), PlayerTank.CannonWidth())", + "" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetZ" + }, + "parameters": [ + "Bullet", + "Object3D", + "=", + "(PlayerTank.Object3D::CenterZ()) + sin(ToRad(-1 * PlayerTank.CannonAngle() - PlayerTank.Object3D::RotationY() * cos(ToRad(PlayerTank.TopRotation())) - PlayerTank.Object3D::RotationX() * sin(ToRad(PlayerTank.TopRotation())))) * PlayerTank.CannonWidth()" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationY" + }, + "parameters": [ + "Bullet", + "Object3D", + "=", + "(PlayerTank.CannonAngle() + PlayerTank.Object3D::RotationY() * cos(ToRad(PlayerTank.TopRotation())) - PlayerTank.Object3D::RotationX() * sin(ToRad(PlayerTank.TopRotation())))" + ] + }, + { + "type": { + "value": "SetAngle" + }, + "parameters": [ + "Bullet", + "=", + "PlayerTank.TopRotation() + PlayerTank.Angle()" + ] + }, + { + "type": { + "value": "Create" + }, + "parameters": [ + "", + "BulletParticle", + "Bullet.X()", + "Bullet.Y()", + "" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetZ" + }, + "parameters": [ + "BulletParticle", + "Object3D", + "=", + "Bullet.Object3D::Z()" + ] + }, + { + "type": { + "value": "Physics3D::Physics3DBehavior::ApplyImpulseTowardPosition" + }, + "parameters": [ + "Bullet", + "Physics3D", + "15", + "Bullet.X() + XFromAngleAndDistance(PlayerTank.Angle() + PlayerTank.TopRotation(), 20)", + "Bullet.Y() + YFromAngleAndDistance(PlayerTank.Angle() + PlayerTank.TopRotation(), 20)", + "Bullet.Object3D::Z() + sin(ToRad(-1 * Bullet.Object3D::RotationY())) * 10" + ] + }, + { + "type": { + "value": "CameraShake::SetLayerZoomAmplitude" + }, + "parameters": [ + "", + "1.1", + "", + "" + ] + }, + { + "type": { + "value": "CameraShake::ShakeCamera" + }, + "parameters": [ + "", + "0.25", + "0", + "0.2", + "" + ] + }, + { + "type": { + "value": "PlaySound" + }, + "parameters": [ + "", + "assets\\Tankfire.wav", + "", + "70", + "RandomFloatInRange(0.8,1)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Physics3D::CollisionStarted" + }, + "parameters": [ + "Bullet", + "Physics3D", + "BulletObstacles", + "Physics3D", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Create" + }, + "parameters": [ + "", + "ExplosionParticle", + "Bullet.X()", + "Bullet.Y()", + "" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetZ" + }, + "parameters": [ + "ExplosionParticle", + "Object3D", + "=", + "Bullet.Object3D::Z()" + ] + }, + { + "type": { + "value": "PickAllInstances" + }, + "parameters": [ + "", + "Target" + ] + }, + { + "type": { + "value": "CameraShake::SetLayerZoomAmplitude" + }, + "parameters": [ + "", + "0.75", + "", + "" + ] + }, + { + "type": { + "value": "CameraShake::ShakeCamera" + }, + "parameters": [ + "", + "0.35", + "0", + "0.2", + "" + ] + }, + { + "type": { + "value": "PlaySound" + }, + "parameters": [ + "", + "assets\\Explosion.wav", + "", + "70", + "RandomFloatInRange(0.8,1)" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::ForEach", + "object": "Target", + "conditions": [ + { + "type": { + "value": "Distance" + }, + "parameters": [ + "Target", + "Bullet", + "300", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::Physics3DBehavior::ApplyImpulseTowardPosition" + }, + "parameters": [ + "Target", + "Physics3D", + "-5 * (300 - (Target.Distance(Bullet)))", + "Bullet.X() + XFromAngleAndDistance(Target.AngleToObject(Bullet), 500)", + "Bullet.Y() + YFromAngleAndDistance(Target.AngleToObject(Bullet), 500)", + "0" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Delete" + }, + "parameters": [ + "Bullet", + "" + ] + } + ] + } + ] + } + ], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [ + { + "effectType": "Scene3D::HemisphereLight", + "name": "HemisphereLight", + "doubleParameters": { + "elevation": 80, + "intensity": 0.33, + "rotation": 120 + }, + "stringParameters": { + "groundColor": "128;128;128", + "skyColor": "255;255;255", + "top": "Z+" + }, + "booleanParameters": {} + }, + { + "effectType": "Scene3D::DirectionalLight", + "name": "DirectionalLight", + "doubleParameters": { + "distanceFromCamera": 1500, + "elevation": 40, + "frustumSize": 4000, + "intensity": 0.75, + "minimumShadowBias": 0, + "rotation": 300, + "shadowDistance": 2000 + }, + "stringParameters": { + "color": "255;255;255", + "shadowQuality": "medium", + "shadowRenderingMode": "fittedToCameraView", + "top": "Z+" + }, + "booleanParameters": { + "isCastingShadow": true + } + }, + { + "effectType": "Scene3D::Skybox", + "name": "SkyBox", + "doubleParameters": {}, + "stringParameters": { + "backFaceResourceName": "assets/Skybox_Back.png", + "bottomFaceResourceName": "assets/Skybox_Bottom.png", + "frontFaceResourceName": "assets/Skybox_Front.png", + "leftFaceResourceName": "assets/Skybox_Left.png", + "rightFaceResourceName": "assets/Skybox_Right.png", + "top": "Z+", + "topFaceResourceName": "assets/Skybox_Top.png" + }, + "booleanParameters": {} + } + ] + }, + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "MobileControls", + "renderingType": "", + "visibility": true, + "cameras": [], + "effects": [] + } + ], + "behaviorsSharedData": [ + { + "name": "Animation", + "type": "AnimatableCapability::AnimatableBehavior" + }, + { + "name": "ButtonFSM", + "type": "ButtonStates::ButtonFSM" + }, + { + "name": "ButtonScaleTween", + "type": "ButtonStates::ButtonScaleTween" + }, + { + "name": "Effect", + "type": "EffectCapability::EffectBehavior" + }, + { + "name": "Flippable", + "type": "FlippableCapability::FlippableBehavior" + }, + { + "name": "Object3D", + "type": "Scene3D::Base3DBehavior" + }, + { + "name": "Opacity", + "type": "OpacityCapability::OpacityBehavior" + }, + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "gravityX": 0, + "gravityY": 0, + "gravityZ": -9.8, + "worldScale": 20 + }, + { + "name": "PhysicsCar3D", + "type": "Physics3D::PhysicsCar3D" + }, + { + "name": "PhysicsCar3DKeyboardMapper", + "type": "PhysicsCar3DKeyMapper::PhysicsCar3DKeyboardMapper" + }, + { + "name": "PhysicsCar3DMultitouchMapper", + "type": "SpriteMultitouchJoystick::PhysicsCar3DMultitouchMapper" + }, + { + "name": "Resizable", + "type": "ResizableCapability::ResizableBehavior" + }, + { + "name": "Scale", + "type": "ScalableCapability::ScalableBehavior" + }, + { + "name": "ThirdPersonCamera", + "type": "ThirdPersonCamera::ThirdPersonCamera" + }, + { + "name": "Tween", + "type": "Tween::TweenBehavior" + } + ] + } + ], + "externalEvents": [], + "tests": [ + { + "description": "Pressing F fires a single shell out of the cannon, flying in the direction the turret points at.", + "name": "Firing a shell with F", + "type": "gameplay", + "source": [ + "// Pressing \"f\" must fire one shell out of the cannon, in the direction the", + "// turret is pointing at (tank angle + turret rotation) - and only one, as", + "// the firing event is a \"trigger once\".", + "await harness.goToScene('Game Scene');", + "// Faster in the software renderer used to run tests (grass is thousands of blades, trees are drawn in batches it is slow with).", + "harness.setGameResolutionSize(160, 90);", + "harness.getCurrentRuntimeScene().getLayer('').setEffectBooleanParameter('DirectionalLight', 'isCastingShadow', false);", + "for (const grass of harness.getCurrentRuntimeScene().getObjects('Grass')) grass.hide(true);", + "for (const object of harness.getCurrentRuntimeScene().getAdhocListOfAllInstances()) if (object.type === 'Terrain3D::ScatteredModels') object.hide(true);", + "harness.setTimeoutMs(60000);", + "harness.watch('Bullet');", + "", + "const getTank = () => harness.getObjects('PlayerTank')[0];", + "await harness.stepFrames(8);", + "", + "harness.assert(", + " harness.getObjects('Bullet').length === 0,", + " 'No shell is in the air before firing.'", + ");", + "", + "const tank = getTank();", + "const turretAngle = tank.angle + tank.state.TopRotation;", + "const turretRadians = (turretAngle * Math.PI) / 180;", + "", + "harness.setKeyPressed('f', true);", + "await harness.stepFrames(2);", + "", + "const shells = harness.getObjects('Bullet');", + "harness.assert(", + " shells.length === 1,", + " 'Pressing \"f\" fires exactly one shell (found ' + shells.length + ').'", + ");", + "const shell = shells[0];", + "", + "// The shell leaves from the end of the cannon, not from inside the tank.", + "const muzzleDistance = Math.hypot(", + " shell.centerX - tank.centerX,", + " shell.centerY - tank.centerY", + ");", + "harness.assert(", + " muzzleDistance > 50,", + " 'The shell leaves from the end of the cannon (' +", + " Math.round(muzzleDistance) + 'px away from the tank center).'", + ");", + "", + "// It flies away along the cannon direction. \"f\" is kept pressed all along:", + "// the \"trigger once\" must not turn it into a machine gun.", + "let maxShellsWhileHeld = shells.length;", + "const countShells = () => {", + " maxShellsWhileHeld = Math.max(", + " maxShellsWhileHeld,", + " harness.getObjects('Bullet').length", + " );", + "};", + "await harness.stepFrames(8, { onFrame: countShells });", + "const flying = harness.getObjects('Bullet').find(one => one.id === shell.id);", + "harness.assert(!!flying, 'The shell is still flying eight frames after the shot.');", + "const travelX = flying.centerX - shell.centerX;", + "const travelY = flying.centerY - shell.centerY;", + "const travelled = Math.hypot(travelX, travelY);", + "const alongCannon =", + " travelX * Math.cos(turretRadians) + travelY * Math.sin(turretRadians);", + "console.log(", + " 'turretAngle=' + Math.round(turretAngle) +", + " ' travelled=' + Math.round(travelled) +", + " ' alongCannon=' + Math.round(alongCannon)", + ");", + "harness.assert(", + " travelled > 40,", + " 'The shell travels away from the tank (' + Math.round(travelled) +", + " 'px in eight frames).'", + ");", + "harness.assert(", + " alongCannon > 0.9 * travelled,", + " 'The shell flies in the direction the cannon points at.'", + ");", + "", + "await harness.stepFrames(14, { onFrame: countShells });", + "harness.releaseAllInputs();", + "console.log('maxShellsWhileHeld=' + maxShellsWhileHeld);", + "harness.assert(", + " maxShellsWhileHeld === 1,", + " 'Keeping \"f\" pressed does not fire a stream of shells (saw ' +", + " maxShellsWhileHeld + ' at once).'", + ");" + ] + }, + { + "description": "The turret is turned onto a target and fired: the explosion knocks the target away.", + "name": "Blowing a target away with a shell", + "type": "gameplay", + "source": [ + "// The core loop: rotate the turret onto a target, fire, and the explosion", + "// must blow the target away.", + "await harness.goToScene('Game Scene');", + "// Faster in the software renderer used to run tests (grass is thousands of blades, trees are drawn in batches it is slow with).", + "harness.setGameResolutionSize(160, 90);", + "harness.getCurrentRuntimeScene().getLayer('').setEffectBooleanParameter('DirectionalLight', 'isCastingShadow', false);", + "for (const grass of harness.getCurrentRuntimeScene().getObjects('Grass')) grass.hide(true);", + "for (const object of harness.getCurrentRuntimeScene().getAdhocListOfAllInstances()) if (object.type === 'Terrain3D::ScatteredModels') object.hide(true);", + "harness.setTimeoutMs(60000);", + "harness.watch('Target');", + "const getTank = () => harness.getObjects('PlayerTank')[0];", + "const normalize = angle => (((angle % 360) + 540) % 360) - 180;", + "const distanceBetween = (a, b) =>", + " Math.hypot(a.centerX - b.centerX, a.centerY - b.centerY, (a.centerZ || 0) - (b.centerZ || 0));", + "await harness.stepFrames(8);", + "", + "const targets = harness.getNearby('Target', 'PlayerTank', 6000);", + "harness.assert(targets.length > 0, 'There is a target to shoot at.');", + "const targetId = targets[0].id;", + "const getTarget = () => harness.getObjects('Target').find(one => one.id === targetId);", + "", + "// Arrange: park the tank a short distance from that target, with the target", + "// off to one side so the turret really has to be turned onto it. Hitting it", + "// is still up to the game.", + "const target = getTarget();", + "const tank = getTank();", + "const bearing = ((tank.angle + 15) * Math.PI) / 180;", + "const distance = 340;", + "harness.setObjectPosition(", + " tank.id,", + " tank.x + (target.centerX - Math.cos(bearing) * distance - tank.centerX),", + " tank.y + (target.centerY - Math.sin(bearing) * distance - tank.centerY),", + " tank.z", + ");", + "await harness.stepFrames(8);", + "harness.assert(", + " getTank().behaviors.PhysicsCar3D.state.IsOnFloor === true,", + " 'The tank is parked on solid ground, in front of the target.'", + ");", + "", + "// Aim the turret: \"a\" and \"d\" rotate it by one degree per frame.", + "const aimError = () => {", + " const from = getTank();", + " const to = getTarget();", + " const wanted =", + " (Math.atan2(to.centerY - from.centerY, to.centerX - from.centerX) * 180) / Math.PI;", + " return normalize(wanted - (from.angle + from.state.TopRotation));", + "};", + "const errorBefore = aimError();", + "harness.assert(", + " Math.abs(errorBefore) > 5,", + " 'The turret does not already point at the target (' + Math.round(errorBefore) + ' degrees off).'", + ");", + "const aimed = await harness.stepUntil(() => Math.abs(aimError()) < 2, {", + " maxFrames: 30,", + " onFrame: () => {", + " const error = aimError();", + " harness.setKeyPressed('d', error > 0);", + " harness.setKeyPressed('a', error < 0);", + " },", + "});", + "harness.releaseAllInputs();", + "console.log('aimError before=' + Math.round(errorBefore) + ' after=' + aimError().toFixed(1));", + "harness.assert(", + " aimed,", + " 'The turret can be turned onto the target with \"a\"/\"d\" (' +", + " aimError().toFixed(1) + ' degrees off after aiming).'", + ");", + "", + "const before = getTarget();", + "", + "// Fire (\"trigger once\": press, step, release).", + "harness.setKeyPressed('f', true);", + "await harness.stepFrames(2);", + "harness.setKeyPressed('f', false);", + "harness.assert(harness.getObjects('Bullet').length === 1, 'A shell is on its way.');", + "", + "// The shell is deleted when it explodes on whatever it hits.", + "const exploded = await harness.stepUntil(", + " () => harness.getObjects('Bullet').length === 0,", + " { maxFrames: 40 }", + ");", + "harness.assert(exploded, 'The shell reaches the target area and explodes.');", + "", + "// Stop as soon as the target has been blown away: this scene is one of the", + "// slowest to render, so every frame that is not needed is worth dropping.", + "const blownAway = await harness.stepUntil(", + " () => {", + " const target = getTarget();", + " return !!target && distanceBetween(target, before) > 30;", + " },", + " { maxFrames: 15 }", + ");", + "", + "const after = getTarget();", + "harness.assert(!!after, 'The target is still in the scene after the shot.');", + "const knockback = distanceBetween(after, before);", + "console.log('knockback=' + Math.round(knockback));", + "harness.assert(", + " blownAway,", + " 'The explosion blows the target away (it moved ' + Math.round(knockback) + 'px).'", + ");", + "", + "// A picture of what the game looks like once this has happened, kept with", + "// the test results.", + "await harness.takeScreenshot('the target after the shell exploded on it');" + ] + }, + { + "name": "A shell explodes on the ground", + "description": "Aims the cannon at the ground and fires: the shell explodes on the terrain.", + "type": "gameplay", + "source": [ + "await harness.goToScene('Game Scene');", + "// Faster in the software renderer used to run tests (grass is thousands of blades, trees are drawn in batches it is slow with).", + "harness.setGameResolutionSize(160, 90);", + "harness.getCurrentRuntimeScene().getLayer('').setEffectBooleanParameter('DirectionalLight', 'isCastingShadow', false);", + "for (const grass of harness.getCurrentRuntimeScene().getObjects('Grass')) grass.hide(true);", + "for (const object of harness.getCurrentRuntimeScene().getAdhocListOfAllInstances()) if (object.type === 'Terrain3D::ScatteredModels') object.hide(true);", + "harness.setTimeoutMs(60000);", + "await harness.stepFrames(8);", + "// \"w\" lowers the cannon (5 degrees at most).", + "harness.setKeyPressed('w', true);", + "await harness.stepFrames(10);", + "harness.setKeyPressed('w', false);", + "harness.setKeyPressed('f', true);", + "await harness.stepFrames(2);", + "harness.setKeyPressed('f', false);", + "harness.assert(harness.getObjects('Bullet').length === 1, 'A shell is fired.');", + "const exploded = await harness.stepUntil(() => harness.getObjects('Bullet').length === 0, { maxFrames: 40, dtMs: 50 });", + "harness.assert(exploded, 'The shell explodes when it hits the ground.');" + ] + } + ], + "eventsFunctionsExtensions": [ + { + "author": "", + "category": "", + "dimension": "", + "extensionNamespace": "", + "fullName": "", + "gdevelopVersion": "", + "helpPath": "", + "iconUrl": "", + "name": "TankConfiguration", + "previewIconUrl": "", + "shortDescription": "", + "version": "", + "description": "", + "tags": [], + "authorIds": [], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [ + { + "name": "CannonWidth", + "type": "number", + "value": 0 + } + ], + "eventsFunctions": [], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT" + }, + "eventsBasedBehaviors": [], + "eventsBasedObjects": [ + { + "areaMaxX": 95, + "areaMaxY": 65, + "areaMaxZ": 69, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "defaultName": "Combined_Tank", + "description": "", + "fullName": "Combined Tank", + "helpPath": "", + "iconUrl": "", + "is3D": true, + "isUsingLegacyInstancesRenderer": false, + "name": "CombinedTank", + "previewIconUrl": "", + "objects": [ + { + "assetStoreId": "", + "name": "TankBase", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "centerLocation": "ModelOrigin", + "crossfadeDuration": 0.10000000149011612, + "depth": 100, + "height": 100, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets\\Tank_Orange_Base.glb", + "originLocation": "ModelOrigin", + "rotationX": 0, + "rotationY": 0, + "rotationZ": 0, + "width": 100, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "TankTop_Combined", + "persistentUuid": "5f2a95c1-f9cd-4f43-b143-9e8cb93d9bec", + "type": "TankConfiguration::TankTop", + "variant": "", + "variables": [], + "effects": [], + "behaviors": [], + "content": {} + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "TankBase" + }, + { + "objectName": "TankTop_Combined" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [ + { + "angle": 0, + "customSize": true, + "depth": 25, + "height": 65, + "keepRatio": true, + "layer": "", + "name": "TankBase", + "persistentUuid": "6eedf890-c262-4f2e-a139-dbb8ab052b83", + "width": 95, + "x": 41.97606798483116, + "y": 32.5, + "zOrder": 12, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 76, + "height": 56, + "keepRatio": true, + "layer": "", + "name": "TankTop_Combined", + "persistentUuid": "c80610c1-0ffd-4631-b076-37aee90b376e", + "width": 57, + "x": 14, + "y": 4.5, + "z": 28, + "zOrder": 13, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": { + "grid": false, + "gridType": "rectangular", + "gridWidth": 32, + "gridHeight": 32, + "gridOffsetX": 0, + "gridOffsetY": 0, + "gridColor": 10401023, + "gridAlpha": 0.8, + "snap": false, + "zoomFactor": 3.3943255487207074, + "windowMask": false, + "selectedLayer": "" + }, + "eventsFunctions": [ + { + "description": "the rotation of the tank top of the object.", + "fullName": "The rotation of the tank top", + "functionType": "ExpressionAndCondition", + "group": "CustomTank_Combined_Orange configuration", + "name": "TopRotation", + "sentence": "the rotation of the tank top", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "TopRotation" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::CombinedTank", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "TopRotation", + "group": "CustomTank_Combined_Orange configuration", + "name": "SetTopRotation", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TopRotation", + "=", + "Value" + ] + }, + { + "type": { + "value": "RotateTowardAngle" + }, + "parameters": [ + "TankTop_Combined", + "TopRotation", + "0", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::CombinedTank", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the elevation of the tank cannon of the object.", + "fullName": "The elevation of the tank cannon", + "functionType": "ExpressionAndCondition", + "group": "CustomTank_Combined_Orange configuration", + "name": "CannonAngle", + "sentence": "the elevation of the tank cannon", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "CannonAngle" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::CombinedTank", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "CannonAngle", + "group": "CustomTank_Combined_Orange configuration", + "name": "SetCannonAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CannonAngle", + "=", + "clamp(Value,-20,5)" + ] + }, + { + "type": { + "value": "TankConfiguration::TankTop::Elevation" + }, + "parameters": [ + "TankTop_Combined", + "CannonAngle", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::CombinedTank", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the distance between the end of the cannon and the center of the TankTop object", + "fullName": "Return CannonWidth", + "functionType": "Expression", + "name": "CannonWidth", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "TankConfiguration::TankTop::CannonInfo" + }, + "parameters": [ + "TankTop_Combined", + "" + ] + }, + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "CannonWidth" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::CombinedTank", + "type": "object" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "folderName": "CustomTank_Combined_Orange configuration", + "children": [ + { + "functionName": "TopRotation" + }, + { + "functionName": "SetTopRotation" + }, + { + "functionName": "CannonAngle" + }, + { + "functionName": "SetCannonAngle" + } + ] + }, + { + "functionName": "CannonWidth" + } + ] + }, + "propertyDescriptors": [ + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "The elevation of the tank cannon", + "name": "CannonAngle" + }, + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "The rotation of the tank top", + "name": "TopRotation" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "CannonAngle" + }, + { + "propertyName": "TopRotation" + } + ] + }, + "variants": [] + }, + { + "areaMaxX": 48, + "areaMaxY": 51, + "areaMaxZ": 57, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "defaultName": "Tank_Top", + "description": "", + "fullName": "Tank Top", + "helpPath": "", + "iconUrl": "", + "is3D": true, + "isUsingLegacyInstancesRenderer": false, + "name": "TankTop", + "previewIconUrl": "", + "objects": [ + { + "assetStoreId": "", + "name": "TankTop", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "centerLocation": "ModelOrigin", + "crossfadeDuration": 0.10000000149011612, + "depth": 100, + "height": 100, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets\\Tank_Orange_upperbase.glb", + "originLocation": "ModelOrigin", + "rotationX": 0, + "rotationY": 0, + "rotationZ": 0, + "width": 100, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "TankCanon", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "centerLocation": "ModelOrigin", + "crossfadeDuration": 0.10000000149011612, + "depth": 100, + "height": 100, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets\\Tank_Orange_Cannon.glb", + "originLocation": "ModelOrigin", + "rotationX": 0, + "rotationY": 0, + "rotationZ": 0, + "width": 100, + "animations": [] + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "TankCanon" + }, + { + "objectName": "TankTop" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [ + { + "angle": 0, + "customSize": true, + "depth": 10, + "height": 10, + "layer": "", + "name": "TankCanon", + "persistentUuid": "b3a4c66d-b3da-42ea-ba69-670751fbd29d", + "width": 65, + "x": 46, + "y": 25.5, + "zOrder": 14, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 57, + "height": 51, + "layer": "", + "name": "TankTop", + "persistentUuid": "aa82e4bd-a61a-4a48-a0c6-e56afe78de7f", + "width": 52, + "x": 24, + "y": 25.5, + "z": -35, + "zOrder": 13, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": { + "grid": false, + "gridType": "rectangular", + "gridWidth": 32, + "gridHeight": 32, + "gridOffsetX": 0, + "gridOffsetY": 0, + "gridColor": 10401023, + "gridAlpha": 0.8, + "snap": false, + "zoomFactor": 6.166071119581128, + "windowMask": false, + "selectedLayer": "" + }, + "eventsFunctions": [ + { + "description": "Change the elevation of the tank cannon inside of the Tank_Top.", + "fullName": "Cannon Elevation", + "functionType": "Action", + "name": "Elevation", + "sentence": "Change the _PARAM0_ elevation to _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationY" + }, + "parameters": [ + "TankCanon", + "Object3D", + "=", + "Angle" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::TankTop", + "type": "object" + }, + { + "description": "Elevation angle", + "name": "Angle", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Record variables related to the TankCannon object", + "fullName": "Record TankCannon information", + "functionType": "Action", + "name": "CannonInfo", + "sentence": "Record information for the TankCannon of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CannonWidth", + "=", + "TankCanon.Width() + TankTop.Width()/2" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TankConfiguration::TankTop", + "type": "object" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "Elevation" + }, + { + "functionName": "CannonInfo" + } + ] + }, + "propertyDescriptors": [], + "propertiesFolderStructure": { + "folderName": "__ROOT" + }, + "variants": [] + } + ] + }, + { + "author": "", + "category": "Camera", + "dimension": "3D", + "extensionNamespace": "", + "fullName": "Third person camera", + "gdevelopVersion": ">=5.5.269", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSI3LDEwIDEsMTMgNywxNiAxMywxMyAiLz4NCjxwb2x5bGluZSBjbGFzcz0ic3QwIiBwb2ludHM9IjEsMTMgMSwyMCA3LDIzIDEzLDIwIDEzLDEzICIvPg0KPGxpbmUgY2xhc3M9InN0MCIgeDE9IjciIHkxPSIxNiIgeDI9IjciIHkyPSIyMyIvPg0KPGxpbmUgY2xhc3M9InN0MCIgeDE9IjMxIiB5MT0iMTYiIHgyPSIyMSIgeTI9IjgiLz4NCjxsaW5lIGNsYXNzPSJzdDAiIHgxPSIyMSIgeTE9IjI0IiB4Mj0iMzEiIHkyPSIxNiIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTIyLjcsMjIuNkMyMCwyMS43LDE4LDE5LjEsMTgsMTZjMC0zLjEsMi01LjcsNC43LTYuNiIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTE5LjgsMTEuM2MxLjQsMS4xLDIuMiwyLjgsMi4yLDQuN2MwLDEuOS0wLjksMy42LTIuMiw0LjciLz4NCjwvc3ZnPg0K", + "name": "ThirdPersonCamera", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/Line Hero Pack/Master/SVG/Virtual Reality/94e95d2c318e1f3dc7151a351024e13c574e1e44669c6696aa107d60230073f6_Virtual Reality_3d_vision_eye_vr.svg", + "shortDescription": "Third-person camera orbiting an object at configurable distance, elevation, and rotation.", + "version": "1.9.0", + "description": [ + "Move the camera to look at an object from a given distance with a rotation and an elevation angles.", + "", + "It can be useful for:", + "- 3D platformer games ([open the project online](https://editor.gdevelop.io/?project=example://3d-platformer))", + "- 3D racing games ([open the project online](https://editor.gdevelop.io/?project=example://3d-car-coin-hunt))", + "- Orbit camera controls ([open the project online](https://editor.gdevelop.io/?project=example://3d-tile-based-city-builder))", + "- Isometric-like point of view ([open the project online](https://editor.gdevelop.io/?project=example://3d-road-crosser))" + ], + "origin": { + "identifier": "ThirdPersonCamera", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d", + "camera" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "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": [ + "//@ts-ignore", + "if (gdjs.__thirdPersonCameraExtension) {", + " //@ts-ignore", + " return;", + "}", + "", + "/**", + " * It can be built the follow:", + " * ", + " * `objectDefaultRotation.setFromAxisAngle(new THREE.Vector3(0, 0, 1), -Math.PI/2);`", + " * ", + " * `objectDefaultRotation.multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), Math.PI/2));`", + " * ", + " */", + "const objectDefaultRotation = new THREE.Quaternion(0.5, -0.5, -0.5, 0.5);", + "const targetQuaternion = new THREE.Quaternion();", + "const yAxis = new THREE.Vector3(0, 1, 0);", + "const zAxis = new THREE.Vector3(0, 0, 1);", + "const quaternion = new THREE.Quaternion();", + "const objectRotation = new THREE.Euler();", + "", + "/**", + " * @param object {gdjs.RuntimeObject}", + " * @param ratio {number}", + " * @param elevationAngleOffset {number}", + " */", + "function slerpCamera(object, ratio, rotationAngleOffset, elevationAngleOffset) {", + " const layer = object.getInstanceContainer().getLayer(object.getLayer());", + " const threeCamera = layer.getRenderer().getThreeCamera();", + " const threeObject = object.get3DRendererObject();", + "", + " objectRotation.copy(threeObject.rotation);", + " objectRotation.z = -objectRotation.z;", + " objectRotation.x = -objectRotation.x;", + " targetQuaternion.setFromEuler(objectRotation);", + " targetQuaternion.multiply(quaternion.setFromAxisAngle(zAxis, -gdjs.toRad(rotationAngleOffset)));", + " targetQuaternion.multiply(quaternion.setFromAxisAngle(yAxis, gdjs.toRad(elevationAngleOffset)));", + " targetQuaternion.multiply(objectDefaultRotation);", + "", + " threeCamera.quaternion.slerp(targetQuaternion, ratio);", + "", + " // The layer angle takes over the 3D camera Z rotation.", + " layer.setCameraRotation(gdjs.toDegrees(-threeCamera.rotation.z));", + "}", + "", + "//@ts-ignore", + "gdjs.__thirdPersonCameraExtension = {", + " slerpCamera,", + "};", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Move the camera to look at a position from a distance.", + "fullName": "Look at a position from a distance (deprecated)", + "functionType": "Action", + "name": "LookFromDistanceAtPosition", + "private": true, + "sentence": "Move the camera of _PARAM6_ to look at _PARAM1_; _PARAM2_ from _PARAM3_ pixels with a rotation of _PARAM4_° and an elevation of _PARAM5_°", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "=", + "PositionX + Distance * cos(ToRad(RotationAngle + 90)) * cos(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "=", + "PositionY + Distance * sin(ToRad(RotationAngle + 90)) * cos(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "Distance * sin(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::TurnCameraTowardPosition" + }, + "parameters": [ + "", + "PositionX", + "PositionY", + "0", + "Layer", + "", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Position on X axis", + "name": "PositionX", + "type": "expression" + }, + { + "description": "Position on Y axis", + "name": "PositionY", + "type": "expression" + }, + { + "description": "Distance", + "name": "Distance", + "type": "expression" + }, + { + "description": "Rotation angle (around Z axis)", + "name": "RotationAngle", + "type": "expression" + }, + { + "description": "Elevation angle (around Y axis)", + "name": "ElevationAngle", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera to look at an object from a distance.", + "fullName": "Look at an object from a distance (deprecated)", + "functionType": "Action", + "name": "LookFromDistanceAtObject", + "private": true, + "sentence": "Move the camera of _PARAM5_ to look at _PARAM1_ from _PARAM2_ pixels with a rotation of _PARAM3_° and an elevation of _PARAM4_°", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition" + }, + "parameters": [ + "", + "Object.CenterX()", + "Object.CenterY()", + "Distance", + "RotationAngle", + "ElevationAngle", + "Layer", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "Distance", + "name": "Distance", + "type": "expression" + }, + { + "description": "Rotation angle (around Z axis)", + "name": "RotationAngle", + "type": "expression" + }, + { + "description": "Elevation angle (around Y axis)", + "name": "ElevationAngle", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera to look at a position from a distance.", + "fullName": "Look at a position from a distance", + "functionType": "Action", + "name": "LookFromDistanceAtPosition3D", + "sentence": "Move the camera of _PARAM7_ to look at _PARAM1_; _PARAM2_; _PARAM3_ from _PARAM4_ pixels with a rotation of _PARAM5_° and an elevation of _PARAM6_°", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "=", + "PositionX + Distance * cos(ToRad(RotationAngle + 90)) * cos(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "=", + "PositionY + Distance * sin(ToRad(RotationAngle + 90)) * cos(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "PositionZ + Distance * sin(ToRad(ElevationAngle))", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationX" + }, + "parameters": [ + "", + "=", + "90 - ElevationAngle", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationY" + }, + "parameters": [ + "", + "=", + "0", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraAngle" + }, + "parameters": [ + "", + "=", + "RotationAngle", + "Layer", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Position on X axis", + "name": "PositionX", + "type": "expression" + }, + { + "description": "Position on Y axis", + "name": "PositionY", + "type": "expression" + }, + { + "description": "Position on Z axis", + "name": "PositionZ", + "type": "expression" + }, + { + "description": "Distance", + "name": "Distance", + "type": "expression" + }, + { + "description": "Rotation angle (around Z axis)", + "name": "RotationAngle", + "type": "expression" + }, + { + "description": "Elevation angle (around Y axis)", + "name": "ElevationAngle", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera to look at an object from a distance.", + "fullName": "Look at an object from a distance", + "functionType": "Action", + "group": "Layers and cameras", + "name": "LookFromDistanceAtObject3D", + "sentence": "Move the camera of _PARAM6_ to look at _PARAM1_ from _PARAM3_ pixels with a rotation of _PARAM4_° and an elevation of _PARAM5_°", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition3D" + }, + "parameters": [ + "", + "Object.CenterX()", + "Object.CenterY()", + "Object.Object3D::CenterZ()", + "Distance", + "RotationAngle", + "ElevationAngle", + "Layer", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Distance", + "name": "Distance", + "type": "expression" + }, + { + "description": "Rotation angle (around Z axis)", + "name": "RotationAngle", + "type": "expression" + }, + { + "description": "Elevation angle (around Y axis)", + "name": "ElevationAngle", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Expression", + "name": "RotatedX", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Cos", + "=", + "cos(ToRad(RotationAngle))" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Sin", + "=", + "sin(ToRad(RotationAngle))" + ] + }, + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Cos * X - Sin * Y" + ] + } + ], + "variables": [ + { + "folded": true, + "name": "Cos", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "Sin", + "type": "number", + "value": 0 + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Rotation angle", + "name": "RotationAngle", + "supplementaryInformation": "Tank::Tank", + "type": "expression" + }, + { + "description": "", + "name": "X", + "type": "expression" + }, + { + "description": "", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Expression", + "name": "RotatedY", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Cos", + "=", + "cos(ToRad(RotationAngle))" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Sin", + "=", + "sin(ToRad(RotationAngle))" + ] + }, + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Sin * X + Cos * Y" + ] + } + ], + "variables": [ + { + "folded": true, + "name": "Cos", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "Sin", + "type": "number", + "value": 0 + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Rotation angle", + "name": "RotationAngle", + "supplementaryInformation": "Tank::Tank", + "type": "expression" + }, + { + "description": "", + "name": "X", + "type": "expression" + }, + { + "description": "", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "DefineHelperClasses" + }, + { + "functionName": "LookFromDistanceAtPosition" + }, + { + "functionName": "LookFromDistanceAtObject" + }, + { + "functionName": "LookFromDistanceAtPosition3D" + }, + { + "folderName": "Layers and cameras", + "children": [ + { + "functionName": "LookFromDistanceAtObject3D" + } + ] + }, + { + "functionName": "RotatedX" + }, + { + "functionName": "RotatedY" + } + ] + }, + "eventsBasedBehaviors": [ + { + "description": "Smoothly follow an object at a distance.", + "fullName": "Third person camera", + "helpPath": "", + "iconUrl": "", + "name": "ThirdPersonCamera", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Update private properties through setters to check their values and initialize state." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetRotationHalfwayDuration" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "RotationHalfwayDuration", + "" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetElevationHalfwayDuration" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "ElevationHalfwayDuration", + "" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetTranslationZHalfwayDuration" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "TranslationZHalfwayDuration", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "HasJustBeenCreated", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "HasJustBeenCreated", + "False", + "" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraZ", + "=", + "Object.Object3D::CenterZ()" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::JumpToTargetedRotation" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "IsCalledManually", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::DoMoveCameraCloser" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera closer to the object. This action must be called after the object has moved for the frame.", + "fullName": "Move the camera closer", + "functionType": "Action", + "name": "MoveCameraCloser", + "sentence": "Move the camera closer to _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "The camera following is called with an action, the call from doStepPreEvents must be disabled to avoid to do it twice." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BehaviorActivated" + }, + "parameters": [ + "Object", + "Behavior" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsCalledManually", + "False", + "" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::DoMoveCameraCloser" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera closer to the object.", + "fullName": "Do move the camera closer", + "functionType": "Action", + "name": "DoMoveCameraCloser", + "private": true, + "sentence": "Do move the camera closer _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "At each frame, the camera must catchup the target by a given ratio (speed)\ncameraX(t) - targetX = (cameraX(t - 1) - targetX) * (1 - speed)\n\nThe frame rate must not impact on the catch-up speed, we don't want a speed in ratio per frame but a speed ratio per second, like this:\ncameraX(t) - targetX = (cameraX(t - 1s) - targetX) * (1 - speed)\n\nOk, but we still need to process each frame, we can use a exponent for this:\ncameraX(t) - targetX = (cameraX(t - timeDelta) - targetX) * (1 - speed)^timeDelta\ncameraX(t) = targetX + (cameraX(t - timeDelta) - targetX) * exp(timeDelta * ln((1 - speed)))\n\npow is probably more efficient than precalculated log if the speed is changed continuously but this might be rare enough." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "RotationMode", + "=", + "\"ZYX\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::Slerp" + }, + "parameters": [ + "Object", + "Behavior", + "1 - exp(TimeDelta() * RotationLogSpeed)", + "RotationAngleOffset", + "ElevationAngleOffset", + "" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::MoveToObject" + }, + "parameters": [ + "Object", + "Behavior", + "OffsetY", + "OffsetX", + "OffsetZ", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "-", + "Distance * Scene3D::CameraForwardX(Object.Layer())", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "-", + "Distance * Scene3D::CameraForwardY(Object.Layer())", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "-", + "Distance * Scene3D::CameraForwardZ(Object.Layer())", + "Object.Layer()", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "RotationMode", + "!=", + "\"ZYX\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "False", + "" + ] + } + ] + } + ], + "actions": [], + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Z translation", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CameraZ", + "<", + "Object.Behavior::FreeAreaZMin()" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraZ", + "=", + "Object.Behavior::FreeAreaZMin() + (CameraZ - Object.Behavior::FreeAreaZMin()) * exp(TimeDelta() * TranslationZLogSpeed)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CameraZ", + ">", + "Object.Behavior::FreeAreaZMax()" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraZ", + "=", + "Object.Behavior::FreeAreaZMax() + (CameraZ - Object.Behavior::FreeAreaZMax()) * exp(TimeDelta() * TranslationZLogSpeed)" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Z rotation", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedRotationAngle", + "=", + "Object.Angle()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "RotationMode", + "!=", + "\"Z\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsElevatingWithObject", + "True", + "" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedElevationAngle", + "=", + "Object.Object3D::RotationY()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraRotationAngle", + "=", + "TargetedRotationAngle + 90 + RotationAngleOffset + AngleDifference(CameraAngle(Object.Layer()), TargetedRotationAngle + 90 + RotationAngleOffset) * exp(TimeDelta() * RotationLogSpeed)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraElevationAngle", + "=", + "TargetedElevationAngle + ElevationAngleOffset + AngleDifference(90 - Scene3D::CameraRotationX(Object.Layer()), TargetedElevationAngle + ElevationAngleOffset) * exp(TimeDelta() * ElevationLogSpeed)" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition3D" + }, + "parameters": [ + "", + "Object.CenterX() + ThirdPersonCamera::RotatedX(CameraRotationAngle, OffsetX, -OffsetY)", + "Object.CenterY() + ThirdPersonCamera::RotatedY(CameraRotationAngle, OffsetX, -OffsetY)", + "CameraZ + OffsetZ", + "Distance", + "CameraRotationAngle", + "CameraElevationAngle", + "Object.Layer()", + "" + ] + } + ], + "variables": [ + { + "folded": true, + "name": "CameraRotationAngle", + "type": "number", + "value": 0 + }, + { + "name": "CameraElevationAngle", + "type": "number", + "value": 0 + } + ] + } + ], + "parameters": [] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Lerp camera", + "functionType": "Action", + "name": "Slerp", + "private": true, + "sentence": "Lerp the camera rotation to _PARAM0_ with a ratio of _PARAM2_ with rotation offset _PARAM3_° and elevation offset _PARAM4_°", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.RuntimeObject3D} */", + "const object = objects[0];", + "const ratio = eventsFunctionContext.getArgument(\"Ratio\");", + "const rotationAngleOffset = eventsFunctionContext.getArgument(\"NewRotationAngleOffset\");", + "const elevationAngleOffset = eventsFunctionContext.getArgument(\"NewElevationAngleOffset\");", + "", + "gdjs.__thirdPersonCameraExtension.slerpCamera(object, ratio, rotationAngleOffset, elevationAngleOffset);", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + }, + { + "description": "Ratio", + "name": "Ratio", + "type": "expression" + }, + { + "description": "Rotation angle offset", + "name": "NewRotationAngleOffset", + "type": "expression" + }, + { + "description": "Elevation angle offset", + "name": "NewElevationAngleOffset", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "fullName": "Move to object", + "functionType": "Action", + "name": "MoveToObject", + "private": true, + "sentence": "Change the camera position to _PARAM0_ with offset _PARAM2_, _PARAM3_, _PARAM4_ ", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "=", + "Object.CenterX() + LocalOffsetX * Object.Object3D::ForwardX() + LocalOffsetY * Object.Object3D::RightX() + LocalOffsetZ * Object.Object3D::UpX()", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "=", + "Object.CenterY() + LocalOffsetX * Object.Object3D::ForwardY() + LocalOffsetY * Object.Object3D::RightY() + LocalOffsetZ * Object.Object3D::UpY()", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "Object.Object3D::CenterZ() + LocalOffsetX * Object.Object3D::ForwardZ() + LocalOffsetY * Object.Object3D::RightZ() + LocalOffsetZ * Object.Object3D::UpZ()", + "Object.Layer()", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + }, + { + "description": "X offset", + "name": "LocalOffsetX", + "type": "expression" + }, + { + "description": "Y offset", + "name": "LocalOffsetY", + "type": "expression" + }, + { + "description": "Z offset", + "name": "LocalOffsetZ", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Rotate the camera all the way to the targeted angle.", + "fullName": "Rotate the camera all the way", + "functionType": "Action", + "name": "JumpToTargetedRotation", + "sentence": "Rotate the camera all the way to the targeted angle of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedRotationAngle", + "=", + "Object.Angle()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsElevatingWithObject", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedElevationAngle", + "=", + "Object.Object3D::RotationY()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraAngle", + "=", + "TargetedRotationAngle + 90 + RotationAngleOffset" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition3D" + }, + "parameters": [ + "", + "Object.CenterX() + ThirdPersonCamera::RotatedX(CameraAngle, OffsetX, -OffsetY)", + "Object.CenterY() + ThirdPersonCamera::RotatedY(CameraAngle, OffsetX, -OffsetY)", + "CameraZ + OffsetZ", + "Distance", + "CameraAngle", + "Object.Object3D::RotationY() + ElevationAngleOffset", + "", + "" + ] + } + ], + "variables": [ + { + "folded": true, + "name": "CameraAngle", + "type": "number", + "value": 0 + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the camera rotation.", + "fullName": "Camera rotation", + "functionType": "ExpressionAndCondition", + "name": "RotationAngle", + "private": true, + "sentence": "the camera rotation", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "CameraAngle(Object.Layer())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Rotate the camera all the way to the targeted angle.", + "fullName": "Rotate the camera all the way", + "functionType": "ActionWithOperator", + "getterName": "RotationAngle", + "name": "SetCameraRotation", + "sentence": "Rotate the camera all the way to the targeted angle of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ThirdPersonCamera::LookFromDistanceAtPosition3D" + }, + "parameters": [ + "", + "Object.CenterX() + ThirdPersonCamera::RotatedX(Value, OffsetX, -OffsetY)", + "Object.CenterY() + ThirdPersonCamera::RotatedY(Value, OffsetX, -OffsetY)", + "CameraZ + OffsetZ", + "Distance", + "Value", + "Object.Object3D::RotationY() + ElevationAngleOffset", + "", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the halfway time for rotation of the object.", + "fullName": "Halfway time for rotation", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "RotationHalfwayDuration", + "sentence": "the halfway time for rotation", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "RotationHalfwayDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "RotationHalfwayDuration", + "group": "Third person camera configuration", + "name": "SetRotationHalfwayDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "ln((1 - speed)) = ln(1 / 2) / halfwatTime" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "RotationHalfwayDuration", + "=", + "Value" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "RotationLogSpeed", + "=", + "log(0.5) / Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the halfway time for elevation rotation of the object.", + "fullName": "Halfway time for elevation rotation", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "ElevationHalfwayDuration", + "sentence": "the halfway time for elevation rotation", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ElevationHalfwayDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ElevationHalfwayDuration", + "group": "Third person camera configuration", + "name": "SetElevationHalfwayDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "ln((1 - speed)) = ln(1 / 2) / halfwatTime" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ElevationHalfwayDuration", + "=", + "Value" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ElevationLogSpeed", + "=", + "log(0.5) / Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the halfway time on Z axis of the object.", + "fullName": "Halfway time on Z axis", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "TranslationZHalfwayDuration", + "sentence": "the halfway time on Z axis", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "TranslationZHalfwayDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "TranslationZHalfwayDuration", + "group": "Third person camera configuration", + "name": "SetTranslationZHalfwayDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "ln((1 - speed)) = ln(1 / 2) / halfwatTime" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TranslationZHalfwayDuration", + "=", + "Value" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TranslationZLogSpeed", + "=", + "log(0.5) / Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Return follow free area bottom border Z.", + "fullName": "Free area Z min", + "functionType": "Expression", + "group": "Private", + "name": "FreeAreaZMin", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Object3D::CenterZ() + OffsetZ - FollowFreeAreaZMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Return follow free area top border Z.", + "fullName": "Free area Z max", + "functionType": "Expression", + "group": "Private", + "name": "FreeAreaZMax", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Object3D::CenterZ() + OffsetZ + FollowFreeAreaZMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the follow free area top border on Z axis of the object.", + "fullName": "Follow free area top border on Z axis", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "FollowFreeAreaZMax", + "sentence": "the follow free area top border on Z axis", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FollowFreeAreaZMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FollowFreeAreaZMax", + "group": "Third person camera configuration", + "name": "SetFollowFreeAreaZMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FollowFreeAreaZMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the follow free area bottom border on Z axis of the object.", + "fullName": "Follow free area bottom border on Z axis", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "FollowFreeAreaZMin", + "sentence": "the follow free area bottom border on Z axis", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FollowFreeAreaZMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FollowFreeAreaZMin", + "group": "Third person camera configuration", + "name": "SetFollowFreeAreaZMin", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FollowFreeAreaZMin", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the camera distance of the object.", + "fullName": "Camera distance", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "Distance", + "sentence": "the camera distance", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Distance" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Distance", + "group": "Third person camera configuration", + "name": "SetDistance", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Distance", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the lateral distance offset of the object.", + "fullName": "Lateral distance offset", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "OffsetX", + "sentence": "the lateral distance offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "OffsetX" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "OffsetX", + "group": "Third person camera configuration", + "name": "SetOffsetX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OffsetX", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the ahead distance offset of the object.", + "fullName": "Ahead distance offset", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "OffsetY", + "sentence": "the ahead distance offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "OffsetY" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "OffsetY", + "group": "Third person camera configuration", + "name": "SetOffsetY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OffsetY", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the z offset of the object.", + "fullName": "Z offset", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "OffsetZ", + "sentence": "the z offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "OffsetZ" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "OffsetZ", + "group": "Third person camera configuration", + "name": "SetOffsetZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OffsetZ", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the rotation angle offset of the object.", + "fullName": "Rotation angle offset", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "RotationAngleOffset", + "sentence": "the rotation angle offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "RotationAngleOffset" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "RotationAngleOffset", + "group": "Third person camera configuration", + "name": "SetRotationAngleOffset", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "RotationAngleOffset", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the elevation angle offset of the object.", + "fullName": "Elevation angle offset", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "ElevationAngleOffset", + "sentence": "the elevation angle offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ElevationAngleOffset" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ElevationAngleOffset", + "group": "Third person camera configuration", + "name": "SetElevationAngleOffset", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ElevationAngleOffset", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the targeted camera rotation angle of the object. When this angle is set, the camera follow this value instead of the object angle.", + "fullName": "Targeted rotation angle", + "functionType": "ExpressionAndCondition", + "group": "Third person camera configuration", + "name": "TargetedRotationAngle", + "sentence": "the targeted camera rotation angle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "TargetedRotationAngle" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "TargetedRotationAngle", + "group": "Third person camera configuration", + "name": "SetTargetedRotationAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TargetedRotationAngle", + "=", + "Value" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsRotatingWithObject", + "False", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ThirdPersonCamera::ThirdPersonCamera", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "MoveCameraCloser" + }, + { + "functionName": "DoMoveCameraCloser" + }, + { + "functionName": "Slerp" + }, + { + "functionName": "MoveToObject" + }, + { + "functionName": "JumpToTargetedRotation" + }, + { + "functionName": "RotationAngle" + }, + { + "functionName": "SetCameraRotation" + }, + { + "folderName": "Third person camera configuration", + "children": [ + { + "functionName": "RotationHalfwayDuration" + }, + { + "functionName": "SetRotationHalfwayDuration" + }, + { + "functionName": "ElevationHalfwayDuration" + }, + { + "functionName": "SetElevationHalfwayDuration" + }, + { + "functionName": "TranslationZHalfwayDuration" + }, + { + "functionName": "SetTranslationZHalfwayDuration" + }, + { + "functionName": "FollowFreeAreaZMax" + }, + { + "functionName": "SetFollowFreeAreaZMax" + }, + { + "functionName": "FollowFreeAreaZMin" + }, + { + "functionName": "SetFollowFreeAreaZMin" + }, + { + "functionName": "Distance" + }, + { + "functionName": "SetDistance" + }, + { + "functionName": "OffsetX" + }, + { + "functionName": "SetOffsetX" + }, + { + "functionName": "OffsetY" + }, + { + "functionName": "SetOffsetY" + }, + { + "functionName": "OffsetZ" + }, + { + "functionName": "SetOffsetZ" + }, + { + "functionName": "RotationAngleOffset" + }, + { + "functionName": "SetRotationAngleOffset" + }, + { + "functionName": "ElevationAngleOffset" + }, + { + "functionName": "SetElevationAngleOffset" + }, + { + "functionName": "TargetedRotationAngle" + }, + { + "functionName": "SetTargetedRotationAngle" + } + ] + }, + { + "folderName": "Private", + "children": [ + { + "functionName": "FreeAreaZMin" + }, + { + "functionName": "FreeAreaZMax" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D capability", + "extraInformation": [ + "Scene3D::Base3DBehavior" + ], + "choices": [], + "name": "Object3D" + }, + { + "value": "0.125", + "type": "Number", + "unit": "Second", + "label": "Halfway time for rotation", + "group": "Catch-up speed", + "quickCustomizationVisibility": "hidden", + "name": "RotationHalfwayDuration" + }, + { + "value": "0", + "type": "Number", + "unit": "Second", + "label": "Halfway time for elevation rotation", + "description": "Only used by Z and ZY rotation modes", + "group": "Catch-up speed", + "quickCustomizationVisibility": "hidden", + "name": "ElevationHalfwayDuration" + }, + { + "value": "0.125", + "type": "Number", + "unit": "Second", + "label": "Halfway time on Z axis", + "description": "Only used by Z and ZY rotation modes", + "group": "Catch-up speed", + "quickCustomizationVisibility": "hidden", + "name": "TranslationZHalfwayDuration" + }, + { + "value": "500", + "type": "Number", + "unit": "Pixel", + "label": "Camera distance", + "group": "Position", + "name": "Distance" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Lateral distance offset", + "group": "Position", + "name": "OffsetX" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Ahead distance offset", + "group": "Position", + "name": "OffsetY" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Z offset", + "group": "Position", + "name": "OffsetZ" + }, + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "Rotation angle offset", + "group": "Position", + "advanced": true, + "name": "RotationAngleOffset" + }, + { + "value": "20", + "type": "Number", + "unit": "DegreeAngle", + "label": "Elevation angle offset", + "group": "Position", + "name": "ElevationAngleOffset" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Follow free area top border on Z axis", + "group": "Position", + "advanced": true, + "quickCustomizationVisibility": "hidden", + "name": "FollowFreeAreaZMax" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Follow free area bottom border on Z axis", + "group": "Position", + "advanced": true, + "quickCustomizationVisibility": "hidden", + "name": "FollowFreeAreaZMin" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "RotationLogSpeed" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "ElevationLogSpeed" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "TranslationZLogSpeed" + }, + { + "value": "", + "type": "Boolean", + "label": "", + "hidden": true, + "name": "IsCalledManually" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "", + "hidden": true, + "name": "CameraZ" + }, + { + "value": "true", + "type": "Boolean", + "label": "", + "hidden": true, + "name": "HasJustBeenCreated" + }, + { + "value": "true", + "type": "Boolean", + "label": "Automatically rotate the camera with the object", + "quickCustomizationVisibility": "hidden", + "name": "IsRotatingWithObject" + }, + { + "value": "false", + "type": "Boolean", + "label": "Automatically rotate the camera with the object (elevation)", + "description": "Best left unchecked, use the rotation mode instead", + "deprecated": true, + "quickCustomizationVisibility": "hidden", + "name": "IsElevatingWithObject" + }, + { + "value": "Z", + "type": "Choice", + "label": "Rotation mode", + "choices": [ + { + "label": "Horizontal rotation (good for characters)", + "value": "Z" + }, + { + "label": "Vertical and horizontal rotations (good for cars)", + "value": "ZY" + }, + { + "label": "All rotations (good for spaceships)", + "value": "ZYX" + } + ], + "name": "RotationMode" + }, + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "Targeted camera rotation angle", + "hidden": true, + "name": "TargetedRotationAngle" + }, + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "Targeted camera rotation angle", + "hidden": true, + "name": "TargetedElevationAngle" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Forward X", + "hidden": true, + "name": "ForwardX" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Forward Y", + "hidden": true, + "name": "ForwardY" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Forward Z", + "hidden": true, + "name": "ForwardZ" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Object3D" + }, + { + "folderName": "Catch-up speed", + "children": [ + { + "propertyName": "RotationHalfwayDuration" + }, + { + "propertyName": "ElevationHalfwayDuration" + }, + { + "propertyName": "TranslationZHalfwayDuration" + } + ] + }, + { + "folderName": "Position", + "children": [ + { + "propertyName": "Distance" + }, + { + "propertyName": "OffsetX" + }, + { + "propertyName": "OffsetY" + }, + { + "propertyName": "OffsetZ" + }, + { + "propertyName": "RotationAngleOffset" + }, + { + "propertyName": "ElevationAngleOffset" + }, + { + "propertyName": "FollowFreeAreaZMax" + }, + { + "propertyName": "FollowFreeAreaZMin" + } + ] + }, + { + "propertyName": "RotationLogSpeed" + }, + { + "propertyName": "ElevationLogSpeed" + }, + { + "propertyName": "TranslationZLogSpeed" + }, + { + "propertyName": "IsCalledManually" + }, + { + "propertyName": "CameraZ" + }, + { + "propertyName": "HasJustBeenCreated" + }, + { + "propertyName": "IsRotatingWithObject" + }, + { + "propertyName": "IsElevatingWithObject" + }, + { + "propertyName": "RotationMode" + }, + { + "propertyName": "TargetedRotationAngle" + }, + { + "propertyName": "TargetedElevationAngle" + }, + { + "propertyName": "ForwardX" + }, + { + "propertyName": "ForwardY" + }, + { + "propertyName": "ForwardZ" + } + ] + } + } + ], + "eventsBasedObjects": [] + }, + { + "author": "", + "category": "Input", + "dimension": "", + "extensionNamespace": "", + "fullName": "Multitouch joystick and buttons (sprite)", + "gdevelopVersion": ">=5.5.222", + "helpPath": "/objects/multitouch-joystick", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPGNpcmNsZSBjbGFzcz0ic3QwIiBjeD0iMTYiIGN5PSIxNiIgcj0iMTMiLz4NCjxwb2x5bGluZSBjbGFzcz0ic3QwIiBwb2ludHM9IjI4LjQsMTIgMjAsMTIgMjAsMy42ICIvPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMjAsMjguNCAyMCwyMCAyOC40LDIwICIvPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMy42LDIwIDEyLDIwIDEyLDI4LjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSIxMiwzLjYgMTIsMTIgMy42LDEyICIvPg0KPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSIxNiw2IDE2LjcsNyAxNS4zLDcgIi8+DQo8cG9seWdvbiBjbGFzcz0ic3QwIiBwb2ludHM9IjE2LDI2IDE1LjMsMjUgMTYuNywyNSAiLz4NCjxwb2x5Z29uIGNsYXNzPSJzdDAiIHBvaW50cz0iNiwxNiA3LDE1LjMgNywxNi43ICIvPg0KPHBvbHlnb24gY2xhc3M9InN0MCIgcG9pbnRzPSIyNiwxNiAyNSwxNi43IDI1LDE1LjMgIi8+DQo8L3N2Zz4NCg==", + "name": "SpriteMultitouchJoystick", + "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Videogames/Videogames_controller_joystick_arrows_direction.svg", + "shortDescription": "On-screen multitouch joystick and buttons.", + "version": "1.9.2", + "description": [ + "Multitouch joysticks are objects showing a joystick on the screen, useful for mobile. They work like a physical gamepad:", + "- 4 or 8 directions", + "- Analogus pads", + "- Player selection", + "- Automatic \"mapper\" behaviors for 2D and 3D movement behaviors (platformer characters, top-down movement, 3D character etc...). Add one of these to the object with the movement behavior (i.e: the player most of the time) and the behavior will then be controlled automatically by the virtual joystick (it works by reading the multitouch joystick state and simulating controls). No need for additional events to make it work once the behaviors are set up.", + "", + "There are ready-to-use joysticks in the asset-store [multitouch joysticks pack](https://editor.gdevelop.io/?initial-dialog=asset-store&asset-pack=multitouch-joysticks-multitouch-joysticks)." + ], + "origin": { + "identifier": "SpriteMultitouchJoystick", + "name": "gdevelop-extension-store" + }, + "tags": [ + "multitouch", + "joystick", + "thumbstick", + "controller", + "touchscreen", + "twin stick", + "shooter", + "virtual", + "platformer", + "platform", + "top-down" + ], + "authorIds": [ + "gqDaZjCfevOOxBYkK6zlhtZnXCg1", + "1OgYzWp5UeVPbiWGJwI6vqfgZLC3", + "v0YRpdAnIucZFgiRCCecqVnGKno2", + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [ + { + "name": "Controllers", + "type": "array", + "children": [ + { + "type": "structure", + "children": [ + { + "name": "Buttons", + "type": "array", + "children": [ + { + "type": "structure", + "children": [ + { + "name": "State", + "type": "string", + "value": "Idle" + } + ] + } + ] + }, + { + "name": "Joystick", + "type": "structure", + "children": [] + } + ] + } + ] + } + ], + "eventsFunctions": [ + { + "description": "Check if the events are running for the editor.", + "fullName": "Editor is running", + "functionType": "Condition", + "name": "IsInGameEdition", + "private": true, + "sentence": "Events are running for the editor", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const game = runtimeScene.getGame();\r", + "eventsFunctionContext.returnValue = game.isInGameEdition && game.isInGameEdition();" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "fullName": "Accelerated speed", + "functionType": "Expression", + "name": "AcceleratedSpeed", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "CurrentSpeed" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TargetedSpeed", + "<", + "0" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Reduce the speed to match the stick force." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + "<", + "TargetedSpeed" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "min(TargetedSpeed, CurrentSpeed + Acceleration * TimeDelta())" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + ">", + "TargetedSpeed" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + "<", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "-", + "Acceleration * TimeDelta()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Turn back at least as fast as it would stop." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + ">=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "max(TargetedSpeed, CurrentSpeed - max(Acceleration , Deceleration) * TimeDelta())" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TargetedSpeed", + ">", + "0" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Reduce the speed to match the stick force." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + ">", + "TargetedSpeed" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "max(TargetedSpeed, CurrentSpeed - Acceleration * TimeDelta())" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + "<", + "TargetedSpeed" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "+", + "Acceleration * TimeDelta()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Turn back at least as fast as it would stop." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + "<=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "min(TargetedSpeed, CurrentSpeed + max(Acceleration , Deceleration) * TimeDelta())" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TargetedSpeed", + "=", + "0" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + "<", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "min(0, CurrentSpeed + Acceleration * TimeDelta())" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CurrentSpeed", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AcceleratedSpeed", + "=", + "max(0, CurrentSpeed - Acceleration * TimeDelta())" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "clamp(AcceleratedSpeed, -SpeedMax, SpeedMax)" + ] + } + ] + } + ], + "variables": [ + { + "name": "AcceleratedSpeed", + "type": "number", + "value": 0 + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Current speed", + "name": "CurrentSpeed", + "type": "expression" + }, + { + "description": "Targeted speed", + "name": "TargetedSpeed", + "type": "expression" + }, + { + "description": "Max speed", + "name": "SpeedMax", + "type": "expression" + }, + { + "description": "Acceleration", + "name": "Acceleration", + "type": "expression" + }, + { + "description": "Deceleration", + "name": "Deceleration", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a button was just pressed on a multitouch controller.", + "fullName": "Multitouch controller button just pressed", + "functionType": "Condition", + "name": "IsButtonJustPressed", + "sentence": "Button _PARAM2_ of multitouch controller _PARAM1_ was just pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Buttons[Button].State", + "=", + "\"JustPressed\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Button name", + "name": "Button", + "supplementaryInformation": "[\"A\",\"CROSS\",\"B\",\"CIRCLE\",\"X\",\"SQUARE\",\"Y\",\"TRIANGLE\",\"LB\",\"L1\",\"RB\",\"R1\",\"LT\",\"L2\",\"RT\",\"R2\",\"UP\",\"DOWN\",\"LEFT\",\"RIGHT\",\"BACK\",\"SHARE\",\"START\",\"OPTIONS\",\"CLICK_STICK_LEFT\",\"CLICK_STICK_RIGHT\",\"PS_BUTTON\",\"CLICK_TOUCHPAD\"]", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a button is pressed on a multitouch controller.", + "fullName": "Multitouch controller button pressed", + "functionType": "Condition", + "name": "IsButtonPressed", + "sentence": "Button _PARAM2_ of multitouch controller _PARAM1_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Buttons[Button].State", + "=", + "\"Pressed\"" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Buttons[Button].State", + "=", + "\"JustPressed\"" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Button name", + "name": "Button", + "supplementaryInformation": "[\"A\",\"CROSS\",\"B\",\"CIRCLE\",\"X\",\"SQUARE\",\"Y\",\"TRIANGLE\",\"LB\",\"L1\",\"RB\",\"R1\",\"LT\",\"L2\",\"RT\",\"R2\",\"UP\",\"DOWN\",\"LEFT\",\"RIGHT\",\"BACK\",\"SHARE\",\"START\",\"OPTIONS\",\"CLICK_STICK_LEFT\",\"CLICK_STICK_RIGHT\",\"PS_BUTTON\",\"CLICK_TOUCHPAD\"]", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a button is released on a multitouch controller.", + "fullName": "Multitouch controller button released", + "functionType": "Condition", + "name": "IsButtonReleased", + "sentence": "Button _PARAM2_ of multitouch controller _PARAM1_ is released", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Buttons[Button].State", + "=", + "\"Released\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Button name", + "name": "Button", + "supplementaryInformation": "[\"A\",\"CROSS\",\"B\",\"CIRCLE\",\"X\",\"SQUARE\",\"Y\",\"TRIANGLE\",\"LB\",\"L1\",\"RB\",\"R1\",\"LT\",\"L2\",\"RT\",\"R2\",\"UP\",\"DOWN\",\"LEFT\",\"RIGHT\",\"BACK\",\"SHARE\",\"START\",\"OPTIONS\",\"CLICK_STICK_LEFT\",\"CLICK_STICK_RIGHT\",\"PS_BUTTON\",\"CLICK_TOUCHPAD\"]", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Change a button state for a multitouch controller.", + "fullName": "Button state", + "functionType": "Action", + "name": "SetButtonState", + "private": true, + "sentence": "Mark _PARAM2_ button as _PARAM3_ for multitouch controller _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Buttons[Button].State", + "=", + "ButtonState" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Button name", + "name": "Button", + "type": "string" + }, + { + "description": "Button state", + "name": "ButtonState", + "supplementaryInformation": "[\"Idle\",\"Pressed\",\"JustPressed\",\"Released\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Change the dead zone radius of a joystick. The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved).", + "fullName": "Dead zone radius", + "functionType": "Action", + "name": "SetDeadZone", + "private": true, + "sentence": "Change the dead zone of multitouch joystick _PARAM2_ of multitouch controller _PARAM1_ to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].DeadZone", + "=", + "DeadZoneRadius" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "type": "string" + }, + { + "description": "Dead zone radius", + "name": "DeadZoneRadius", + "supplementaryInformation": "[]", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the dead zone radius of a joystick. The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved).", + "fullName": "Dead zone radius", + "functionType": "Expression", + "name": "DeadZone", + "private": true, + "sentence": "Change multitouch joystick _PARAM2_ of multitouch controller _PARAM1_ dead zone to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].DeadZone" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "the direction index (right = 0, bottom = 1, left = 2, top = 3) for an angle (in degrees).", + "fullName": "Angle to 4-way index", + "functionType": "ExpressionAndCondition", + "name": "AngleTo4Way", + "private": true, + "sentence": "The angle _PARAM1_ 4-way index", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "mod(round(Angle * 4 / 360), 4)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Angle", + "name": "Angle", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the direction index (right = 0, bottom-right = 1... top-right = 7) for an angle (in degrees).", + "fullName": "Angle to 8-way index", + "functionType": "ExpressionAndCondition", + "name": "AngleTo8Way", + "private": true, + "sentence": "The angle _PARAM1_ 8-way index", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "mod(round(Angle * 8 / 360), 8)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Angle", + "name": "Angle", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Check if angle is in a given direction.", + "fullName": "Angle 4-way direction", + "functionType": "Condition", + "name": "IsAngleIn4WayDirection", + "private": true, + "sentence": "The angle _PARAM1_ is the 4-way direction _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Right\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo4Way" + }, + "parameters": [ + "", + "=", + "0", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Down\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo4Way" + }, + "parameters": [ + "", + "=", + "1", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Left\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo4Way" + }, + "parameters": [ + "", + "=", + "2", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Up\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo4Way" + }, + "parameters": [ + "", + "=", + "3", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Angle", + "name": "Angle", + "type": "expression" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if angle is in a given direction.", + "fullName": "Angle 8-way direction", + "functionType": "Condition", + "name": "IsAngleIn8WayDirection", + "private": true, + "sentence": "The angle _PARAM1_ is the 8-way direction _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Right\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "0", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"DownRight\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "1", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Down\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "2", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"DownLeft\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "3", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Left\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "4", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"UpLeft\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "5", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"Up\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "6", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Direction", + "=", + "\"UpRight\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::AngleTo8Way" + }, + "parameters": [ + "", + "=", + "7", + "Angle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Angle", + "name": "Angle", + "type": "expression" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\",\"UpLeft\",\"UpRight\",\"DownLeft\",\"DownRight\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (4-way)", + "functionType": "Condition", + "name": "IsDirectionPushed4Way", + "sentence": "Joystick _PARAM2_ of multitouch controller _PARAM1_ is pushed in direction _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Make sure the joystick has moved from center" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::JoystickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "JoystickIdentifier", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::IsAngleIn4WayDirection" + }, + "parameters": [ + "", + "SpriteMultitouchJoystick::JoystickAngle(ControllerIdentifier, JoystickIdentifier)", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (8-way)", + "functionType": "Condition", + "name": "IsDirectionPushed8Way", + "sentence": "Joystick _PARAM2_ of multitouch controller _PARAM1_ is pushed in direction _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Make sure the joystick has moved from center" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::JoystickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "JoystickIdentifier", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::IsAngleIn8WayDirection" + }, + "parameters": [ + "", + "SpriteMultitouchJoystick::JoystickAngle(ControllerIdentifier, JoystickIdentifier)", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\",\"UpLeft\",\"UpRight\",\"DownLeft\",\"DownRight\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "the percentage the thumb has been pulled away from the joystick center (Range: 0 to 1).", + "fullName": "Joystick force (deprecated)", + "functionType": "ExpressionAndCondition", + "name": "JoystickForce", + "private": true, + "sentence": "Joystick _PARAM2_ of multitouch controller _PARAM1_ force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + }, + { + "description": "", + "name": "Coucou", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the force of multitouch contoller stick (from 0 to 1).", + "fullName": "Stick force", + "functionType": "ExpressionAndCondition", + "name": "StickForce", + "sentence": "multitouch controller _PARAM1_ _PARAM2_ stick force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "max(0, Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].Force - SpriteMultitouchJoystick::DeadZone(ControllerIdentifier, JoystickIdentifier)) / (1 - SpriteMultitouchJoystick::DeadZone(ControllerIdentifier, JoystickIdentifier))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Stick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Change the percentage the thumb has been pulled away from the joystick center (Range: 0 to 1).", + "fullName": "Joystick force", + "functionType": "Action", + "name": "SetJoystickForce", + "private": true, + "sentence": "Change the force of the joystick _PARAM2_ of multitouch controller _PARAM1_ to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].Force", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "type": "string" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle the joystick is pointing towards (Range: -180 to 180).", + "fullName": "Joystick angle (deprecated)", + "functionType": "Expression", + "name": "JoystickAngle", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle the multitouch controller stick is pointing towards (Range: -180 to 180).", + "fullName": "Stick angle", + "functionType": "Expression", + "name": "StickAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].Angle" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Change the angle the joystick is pointing towards (Range: -180 to 180).", + "fullName": "Joystick angle", + "functionType": "Action", + "name": "SetJoystickAngle", + "private": true, + "sentence": "Change the angle of the joystick _PARAM2_ of multitouch controller _PARAM1_ to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Controllers[ControllerIdentifier].Joystick[JoystickIdentifier].Angle", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "type": "string" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the multitouch contoller stick force on X axis (from -1 at the left to 1 at the right).", + "fullName": "Stick X force", + "functionType": "Expression", + "name": "StickForceX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "XFromAngleAndDistance(SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier), SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Return the multitouch contoller stick force on Y axis (from -1 at the top to 1 at the bottom).", + "fullName": "Stick Y force", + "functionType": "Expression", + "name": "StickForceY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "YFromAngleAndDistance(SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier), SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier", + "type": "expression" + }, + { + "description": "Joystick name", + "name": "JoystickIdentifier", + "supplementaryInformation": "[\"Primary\",\"Secondary\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a new touch has started on the right or left side of the screen.", + "fullName": "New touch on a screen side", + "functionType": "Condition", + "group": "Multitouch Joystick", + "name": "HasTouchStartedOnScreenSide", + "sentence": "A new touch has started on the _PARAM2_ side of the screen on _PARAM1_'s layer", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Side", + "=", + "\"Left\"" + ] + }, + { + "type": { + "value": "TouchX" + }, + "parameters": [ + "", + "StartedTouchOrMouseId(0)", + "<", + "CameraCenterX(Object.Layer())", + "Object.Layer()", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Side", + "=", + "\"Right\"" + ] + }, + { + "type": { + "value": "TouchX" + }, + "parameters": [ + "", + "StartedTouchOrMouseId(0)", + ">=", + "CameraCenterX(Object.Layer())", + "Object.Layer()", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Multitouch joystick", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "objectList" + }, + { + "description": "Screen side", + "name": "Side", + "supplementaryInformation": "[\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "IsInGameEdition" + }, + { + "functionName": "AcceleratedSpeed" + }, + { + "functionName": "IsButtonJustPressed" + }, + { + "functionName": "IsButtonPressed" + }, + { + "functionName": "IsButtonReleased" + }, + { + "functionName": "SetButtonState" + }, + { + "functionName": "SetDeadZone" + }, + { + "functionName": "DeadZone" + }, + { + "functionName": "AngleTo4Way" + }, + { + "functionName": "AngleTo8Way" + }, + { + "functionName": "IsAngleIn4WayDirection" + }, + { + "functionName": "IsAngleIn8WayDirection" + }, + { + "functionName": "IsDirectionPushed4Way" + }, + { + "functionName": "IsDirectionPushed8Way" + }, + { + "functionName": "JoystickForce" + }, + { + "functionName": "StickForce" + }, + { + "functionName": "SetJoystickForce" + }, + { + "functionName": "JoystickAngle" + }, + { + "functionName": "StickAngle" + }, + { + "functionName": "SetJoystickAngle" + }, + { + "functionName": "StickForceX" + }, + { + "functionName": "StickForceY" + }, + { + "folderName": "Multitouch Joystick", + "children": [ + { + "functionName": "HasTouchStartedOnScreenSide" + } + ] + } + ] + }, + "eventsBasedBehaviors": [ + { + "description": "Joystick that can be controlled by interacting with a touchscreen.", + "fullName": "Multitouch Joystick", + "helpPath": "", + "iconUrl": "", + "name": "MultitouchJoystick", + "objectType": "", + "previewIconUrl": "", + "private": true, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::SetDeadZone" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "DeadZoneRadius", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::Reset" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDeActivate", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::Reset" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::IsInGameEdition" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasGameJustResumed" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::Reset" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Manage touches", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchIndex", + "=", + "0" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "StartedTouchOrMouseCount()", + "conditions": [], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "0" + ] + }, + { + "type": { + "value": "CollisionPoint" + }, + "parameters": [ + "Object", + "TouchX(StartedTouchOrMouseId(TouchIndex), Object.Layer(), 0)", + "TouchY(StartedTouchOrMouseId(TouchIndex), Object.Layer(), 0)" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "StartedTouchOrMouseId(TouchIndex)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchIndex", + "+", + "1" + ] + } + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Move thumb back to center when not being pressed (acts like a spring on a real controller)" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasTouchEnded" + }, + "parameters": [ + "", + "TouchId" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::Reset" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Update joystick position", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetJoystickAngle" + }, + "parameters": [ + "Object", + "Behavior", + "AngleBetweenPositions(Object.CenterX(), Object.CenterY(), TouchX(TouchId, Object.Layer(), 0), TouchY(TouchId, Object.Layer(), 0))", + "AngleBetweenPositions(Object.CenterX(), Object.CenterY(), TouchX(TouchId, Object.Layer(), 0), TouchY(TouchId, Object.Layer(), 0))" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetJoystickForce" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "clamp(2 * DistanceBetweenPositions(Object.CenterX(), Object.CenterY(), TouchX(TouchId, Object.Layer(), 0), TouchY(TouchId, Object.Layer(), 0)) / Object.Width(), 0, 1)", + "" + ] + } + ] + } + ], + "parameters": [] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the joystick force (from 0 to 1).", + "fullName": "Joystick force", + "functionType": "ExpressionAndCondition", + "name": "JoystickForce", + "sentence": "the joystick force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "max(0, JoystickForce - DeadZoneRadius) / (1 - DeadZoneRadius)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "JoystickForce", + "name": "SetJoystickForce", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "JoystickForce", + "=", + "Value" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SetJoystickForce" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "JoystickForce", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + }, + { + "description": "", + "name": "Parameter", + "type": "objectList" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle the joystick is pointing towards (Range: -180 to 180).", + "fullName": "Joystick angle", + "functionType": "Expression", + "name": "JoystickAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "JoystickAngle" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Change the angle the joystick is pointing towards (Range: -180 to 180).", + "fullName": "Joystick angle", + "functionType": "Action", + "name": "SetJoystickAngle", + "private": true, + "sentence": "Change the joystick angle of _PARAM0_ to _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "JoystickAngle", + "=", + "Value" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SetJoystickAngle" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "JoystickAngle", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + }, + { + "description": "Angle", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the stick force on X axis (from -1 at the left to 1 at the right).", + "fullName": "Stick X force", + "functionType": "Expression", + "name": "StickForceX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Behavior::JoystickForce() * cos(ToRad(Object.Behavior::JoystickAngle()))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Return the stick force on Y axis (from -1 at the top to 1 at the bottom).", + "fullName": "Stick Y force", + "functionType": "Expression", + "name": "StickForceY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Behavior::JoystickForce() * sin(ToRad(Object.Behavior::JoystickAngle()))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (4-way movement)", + "functionType": "Condition", + "name": "IsDirectionPushed4Way", + "sentence": "_PARAM0_ is pushed in direction _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::JoystickForce" + }, + "parameters": [ + "Object", + "Behavior", + ">", + "0", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::IsAngleIn4WayDirection" + }, + "parameters": [ + "", + "JoystickAngle", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (8-way movement)", + "functionType": "Condition", + "name": "IsDirectionPushed8Way", + "sentence": "_PARAM0_ is pushed in direction _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::JoystickForce" + }, + "parameters": [ + "Object", + "Behavior", + ">", + "0", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::IsAngleIn8WayDirection" + }, + "parameters": [ + "", + "JoystickAngle", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\",\"UpLeft\",\"UpRight\",\"DownLeft\",\"DownRight\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a joystick is pressed.", + "fullName": "Joystick pressed", + "functionType": "Condition", + "name": "IsPressed", + "sentence": "Joystick _PARAM0_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Reset the joystick values (except for angle, which stays the same)", + "fullName": "Reset", + "functionType": "Action", + "name": "Reset", + "private": true, + "sentence": "Reset the joystick of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetJoystickForce" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "0", + "" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "0" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the multitouch controller identifier.", + "fullName": "Multitouch controller identifier", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "ControllerIdentifier", + "sentence": "the multitouch controller identifier", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ControllerIdentifier" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ControllerIdentifier", + "group": "Multitouch Joystick configuration", + "name": "SetControllerIdentifier", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ControllerIdentifier", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the joystick name.", + "fullName": "Joystick name", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "JoystickIdentifier", + "sentence": "the joystick name", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "JoystickIdentifier" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "JoystickIdentifier", + "group": "Multitouch Joystick configuration", + "name": "SetJoystickIdentifier", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "JoystickIdentifier", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the dead zone radius (range: 0 to 1) of the joystick. The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved).", + "fullName": "Dead zone radius", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "DeadZoneRadius", + "sentence": "the dead zone radius", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "DeadZoneRadius" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "DeadZoneRadius", + "group": "Multitouch Joystick configuration", + "name": "SetDeadZoneRadius", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DeadZoneRadius", + "=", + "Value" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SetDeadZone" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "Value", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Force the joystick into the pressing state.", + "fullName": "Force start pressing", + "functionType": "Action", + "name": "ForceStartPressing", + "sentence": "Force start pressing _PARAM0_ with touch identifier: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchJoystick", + "type": "behavior" + }, + { + "description": "Touch identifier", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "onDeActivate" + }, + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "JoystickForce" + }, + { + "functionName": "SetJoystickForce" + }, + { + "functionName": "JoystickAngle" + }, + { + "functionName": "SetJoystickAngle" + }, + { + "functionName": "StickForceX" + }, + { + "functionName": "StickForceY" + }, + { + "functionName": "IsDirectionPushed4Way" + }, + { + "functionName": "IsDirectionPushed8Way" + }, + { + "functionName": "IsPressed" + }, + { + "functionName": "Reset" + }, + { + "folderName": "Multitouch Joystick configuration", + "children": [ + { + "functionName": "ControllerIdentifier" + }, + { + "functionName": "SetControllerIdentifier" + }, + { + "functionName": "JoystickIdentifier" + }, + { + "functionName": "SetJoystickIdentifier" + }, + { + "functionName": "DeadZoneRadius" + }, + { + "functionName": "SetDeadZoneRadius" + } + ] + }, + { + "functionName": "ForceStartPressing" + } + ] + }, + "propertyDescriptors": [ + { + "value": "1", + "type": "Number", + "label": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "String", + "label": "Joystick name", + "name": "JoystickIdentifier" + }, + { + "value": "0.4", + "type": "Number", + "label": "Dead zone radius (range: 0 to 1)", + "description": "The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved)", + "name": "DeadZoneRadius" + }, + { + "value": "0", + "type": "Number", + "label": "Joystick angle (range: -180 to 180)", + "hidden": true, + "name": "JoystickAngle" + }, + { + "value": "0", + "type": "Number", + "label": "Joystick force (range: 0 to 1)", + "hidden": true, + "name": "JoystickForce" + }, + { + "value": "0", + "type": "Number", + "label": "", + "hidden": true, + "name": "TouchId" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "TouchIndex" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ControllerIdentifier" + }, + { + "propertyName": "JoystickIdentifier" + }, + { + "propertyName": "DeadZoneRadius" + }, + { + "propertyName": "JoystickAngle" + }, + { + "propertyName": "JoystickForce" + }, + { + "propertyName": "TouchId" + }, + { + "propertyName": "TouchIndex" + } + ] + } + }, + { + "description": "Detect presses made on a touchscreen on the object so it acts like a button and automatically trigger the button having the same identifier for the mapper behaviors.", + "fullName": "Multitouch button", + "helpPath": "", + "iconUrl": "", + "name": "MultitouchButton", + "objectType": "", + "previewIconUrl": "", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsReleased", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsReleased", + "False", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchButton::SetButtonState" + }, + "parameters": [ + "Object", + "Behavior", + "\"Idle\"", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsJustPressed", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsJustPressed", + "False", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchButton::SetButtonState" + }, + "parameters": [ + "Object", + "Behavior", + "\"Pressed\"", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + }, + { + "type": { + "value": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchIndex", + "=", + "0" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "StartedTouchOrMouseCount()", + "conditions": [], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "CollisionPoint" + }, + "parameters": [ + "Object", + "TouchX(StartedTouchOrMouseId(TouchIndex), Object.Layer())", + "TouchY(StartedTouchOrMouseId(TouchIndex), Object.Layer())" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "Radius", + ">", + "DistanceBetweenPositions(Object.CenterX(), Object.CenterY(), TouchX(StartedTouchOrMouseId(TouchIndex), Object.Layer()), TouchY(StartedTouchOrMouseId(TouchIndex), Object.Layer()))" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "StartedTouchOrMouseId(TouchIndex)" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchButton::SetButtonState" + }, + "parameters": [ + "Object", + "Behavior", + "\"JustPressed\"", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsJustPressed", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchIndex", + "+", + "1" + ] + } + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + }, + { + "type": { + "value": "HasTouchEnded" + }, + "parameters": [ + "", + "TouchId" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchButton::SetButtonState" + }, + "parameters": [ + "Object", + "Behavior", + "\"Released\"", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsReleased", + "True", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsJustPressed", + "False", + "" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "0" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchButton", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button was just pressed.", + "fullName": "Button just pressed", + "functionType": "Condition", + "name": "IsJustPressed", + "sentence": "Button _PARAM0_ was just pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsJustPressed", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchButton", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is pressed.", + "fullName": "Button pressed", + "functionType": "Condition", + "name": "IsPressed", + "sentence": "Button _PARAM0_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchButton", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is released.", + "fullName": "Button released", + "functionType": "Condition", + "name": "IsReleased", + "sentence": "Button _PARAM0_ is released", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsReleased", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchButton", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Button state", + "functionType": "Action", + "name": "SetButtonState", + "private": true, + "sentence": "Mark the button _PARAM0_ as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::SetButtonState" + }, + "parameters": [ + "", + "ControllerIdentifier", + "ButtonIdentifier", + "ButtonState", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::MultitouchButton", + "type": "behavior" + }, + { + "description": "Button state", + "name": "ButtonState", + "supplementaryInformation": "[\"Idle\",\"JustPressed\",\"Pressed\",\"Released\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "IsJustPressed" + }, + { + "functionName": "IsPressed" + }, + { + "functionName": "IsReleased" + }, + { + "functionName": "SetButtonState" + } + ] + }, + "propertyDescriptors": [ + { + "value": "1", + "type": "Number", + "label": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "A", + "type": "String", + "label": "Button identifier", + "name": "ButtonIdentifier" + }, + { + "value": "0", + "type": "Number", + "label": "TouchID", + "hidden": true, + "name": "TouchId" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "TouchIndex" + }, + { + "value": "", + "type": "Boolean", + "label": "Button released", + "hidden": true, + "name": "IsReleased" + }, + { + "value": "", + "type": "Boolean", + "label": "Button just pressed", + "hidden": true, + "name": "IsJustPressed" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Triggering circle radius", + "description": "This circle adds up to the object collision mask.", + "name": "Radius" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ControllerIdentifier" + }, + { + "propertyName": "ButtonIdentifier" + }, + { + "propertyName": "TouchId" + }, + { + "propertyName": "TouchIndex" + }, + { + "propertyName": "IsReleased" + }, + { + "propertyName": "IsJustPressed" + }, + { + "propertyName": "Radius" + } + ] + } + }, + { + "description": "Control a platformer character with a multitouch controller.", + "fullName": "Platformer multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "PlatformerMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Left\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "Property" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Right\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "Property" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Up\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "Property" + ] + }, + { + "type": { + "value": "PlatformBehavior::SimulateLadderKey" + }, + "parameters": [ + "Object", + "Property" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Down\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "Property" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsButtonPressed" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JumpButton", + "\"Down\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateJumpKey" + }, + "parameters": [ + "Object", + "Property" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::PlatformerMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Platform character behavior", + "extraInformation": [ + "PlatformBehavior::PlatformerObjectBehavior" + ], + "choices": [], + "name": "Property" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Joystick name", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "A", + "type": "String", + "label": "Jump button name", + "group": "Controls", + "name": "JumpButton" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Property" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "JoystickIdentifier" + }, + { + "propertyName": "JumpButton" + } + ] + } + ] + } + }, + { + "description": "Control a 3D physics character with a multitouch controller.", + "fullName": "3D platformer multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "Platformer3DMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::StickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "JoystickIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SetForwardAngle" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D", + "=", + "SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier) + CameraAngle(Object.Layer())" + ] + }, + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateStick" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D", + "-90", + "SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsButtonPressed" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JumpButton", + "\"Down\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateJumpKey" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::Platformer3DMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics character", + "extraInformation": [ + "Physics3D::PhysicsCharacter3D" + ], + "choices": [], + "name": "PhysicsCharacter3D" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Walk joystick", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "A", + "type": "String", + "label": "Jump button name", + "group": "Controls", + "name": "JumpButton" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "PhysicsCharacter3D" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "JoystickIdentifier" + }, + { + "propertyName": "JumpButton" + } + ] + } + ] + } + }, + { + "description": "Control a 3D physics character with a multitouch controller.", + "fullName": "3D shooter multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "Shooter3DMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::StickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "JoystickIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateStick" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D", + "SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier)", + "SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsButtonPressed" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JumpButton", + "\"Down\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateJumpKey" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::Shooter3DMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics character", + "extraInformation": [ + "Physics3D::PhysicsCharacter3D" + ], + "choices": [], + "name": "PhysicsCharacter3D" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Walk joystick", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "A", + "type": "String", + "label": "Jump button name", + "group": "Controls", + "name": "JumpButton" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "PhysicsCharacter3D" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "JoystickIdentifier" + }, + { + "propertyName": "JumpButton" + } + ] + } + ] + } + }, + { + "description": "Control camera rotations with a multitouch controller.", + "fullName": "First person camera multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "FirstPersonMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "TODO It's probably a bad idea to rotate the object around Y." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CurrentRotationSpeedZ", + "=", + "SpriteMultitouchJoystick::AcceleratedSpeed(CurrentRotationSpeedZ, SpriteMultitouchJoystick::StickForceX(ControllerIdentifier, CameraStick) * HorizontalRotationSpeedMax, HorizontalRotationSpeedMax, HorizontalRotationAcceleration, HorizontalRotationDeceleration)" + ] + }, + { + "type": { + "value": "SetAngle" + }, + "parameters": [ + "Object", + "+", + "CurrentRotationSpeedZ * TimeDelta()" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CurrentRotationSpeedY", + "=", + "SpriteMultitouchJoystick::AcceleratedSpeed(CurrentRotationSpeedY, SpriteMultitouchJoystick::StickForceY(ControllerIdentifier, CameraStick) * VerticalRotationSpeedMax, VerticalRotationSpeedMax, VerticalRotationAcceleration, VerticalRotationDeceleration)" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationY" + }, + "parameters": [ + "Object", + "Object3D", + "+", + "CurrentRotationSpeedY * TimeDelta()" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationY" + }, + "parameters": [ + "Object", + "Object3D", + "=", + "clamp(Object.Object3D::RotationY(), VerticalAngleMin, VerticalAngleMax)" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper::LookFromObjectEyes" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera to look though _PARAM1_ eyes. The object must look to the right when all its angles are 0 and the top of its head be toward Z+.", + "fullName": "Look through object eyes", + "functionType": "Action", + "group": "Layers and cameras", + "name": "LookFromObjectEyes", + "private": true, + "sentence": "Move the camera to look though _PARAM0_ eyes", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "CenterCameraOnObject" + }, + "parameters": [ + "", + "Object", + "", + "Object.Layer()", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "Object.Object3D::Z() + Object.Object3D::Depth() + OffsetZ", + "", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationX" + }, + "parameters": [ + "", + "=", + "- Object.Object3D::RotationY() + 90", + "GetArgumentAsString(\"Layer\")", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationY" + }, + "parameters": [ + "", + "=", + "Object.Object3D::RotationX()", + "GetArgumentAsString(\"Layer\")", + "" + ] + }, + { + "type": { + "value": "SetCameraAngle" + }, + "parameters": [ + "", + "=", + "Object.Angle() + 90", + "Object.Layer()", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the maximum horizontal rotation speed of the object.", + "fullName": "Maximum horizontal rotation speed", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "HorizontalRotationSpeedMax", + "sentence": "the maximum horizontal rotation speed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "HorizontalRotationSpeedMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "HorizontalRotationSpeedMax", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "SetHorizontalRotationSpeedMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "HorizontalRotationSpeedMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the horizontal rotation acceleration of the object.", + "fullName": "Horizontal rotation acceleration", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "HorizontalRotationAcceleration", + "sentence": "the horizontal rotation acceleration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "HorizontalRotationAcceleration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "HorizontalRotationAcceleration", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "SetHorizontalRotationAcceleration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "HorizontalRotationAcceleration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the horizontal rotation deceleration of the object.", + "fullName": "Horizontal rotation deceleration", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "HorizontalRotationDeceleration", + "sentence": "the horizontal rotation deceleration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "HorizontalRotationDeceleration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "HorizontalRotationDeceleration", + "group": "First person camera multitouch controller mapper horizontal rotation configuration", + "name": "SetHorizontalRotationDeceleration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "HorizontalRotationDeceleration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the maximum vertical rotation speed of the object.", + "fullName": "Maximum vertical rotation speed", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "VerticalRotationSpeedMax", + "sentence": "the maximum vertical rotation speed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalRotationSpeedMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalRotationSpeedMax", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "SetVerticalRotationSpeedMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalRotationSpeedMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the vertical rotation acceleration of the object.", + "fullName": "Vertical rotation acceleration", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "VerticalRotationAcceleration", + "sentence": "the vertical rotation acceleration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalRotationAcceleration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalRotationAcceleration", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "SetVerticalRotationAcceleration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalRotationAcceleration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the vertical rotation deceleration of the object.", + "fullName": "Vertical rotation deceleration", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "VerticalRotationDeceleration", + "sentence": "the vertical rotation deceleration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalRotationDeceleration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalRotationDeceleration", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "SetVerticalRotationDeceleration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalRotationDeceleration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the minimum vertical camera angle of the object.", + "fullName": "Minimum vertical camera angle", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "VerticalAngleMin", + "sentence": "the minimum vertical camera angle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalAngleMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalAngleMin", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "SetVerticalAngleMin", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalAngleMin", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the maximum vertical camera angle of the object.", + "fullName": "Maximum vertical camera angle", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "VerticalAngleMax", + "sentence": "the maximum vertical camera angle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "VerticalAngleMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalAngleMax", + "group": "First person camera multitouch controller mapper vertical rotation configuration", + "name": "SetVerticalAngleMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "VerticalAngleMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the z position offset of the object.", + "fullName": "Z position offset", + "functionType": "ExpressionAndCondition", + "group": "First person camera multitouch controller mapper position configuration", + "name": "OffsetZ", + "sentence": "the z position offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "OffsetZ" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "OffsetZ", + "group": "First person camera multitouch controller mapper position configuration", + "name": "SetOffsetZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OffsetZ", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + }, + { + "folderName": "Layers and cameras", + "children": [ + { + "functionName": "LookFromObjectEyes" + } + ] + }, + { + "folderName": "First person camera multitouch controller mapper horizontal rotation configuration", + "children": [ + { + "functionName": "HorizontalRotationSpeedMax" + }, + { + "functionName": "SetHorizontalRotationSpeedMax" + }, + { + "functionName": "HorizontalRotationAcceleration" + }, + { + "functionName": "SetHorizontalRotationAcceleration" + }, + { + "functionName": "HorizontalRotationDeceleration" + }, + { + "functionName": "SetHorizontalRotationDeceleration" + } + ] + }, + { + "folderName": "First person camera multitouch controller mapper vertical rotation configuration", + "children": [ + { + "functionName": "VerticalRotationSpeedMax" + }, + { + "functionName": "SetVerticalRotationSpeedMax" + }, + { + "functionName": "VerticalRotationAcceleration" + }, + { + "functionName": "SetVerticalRotationAcceleration" + }, + { + "functionName": "VerticalRotationDeceleration" + }, + { + "functionName": "SetVerticalRotationDeceleration" + }, + { + "functionName": "VerticalAngleMin" + }, + { + "functionName": "SetVerticalAngleMin" + }, + { + "functionName": "VerticalAngleMax" + }, + { + "functionName": "SetVerticalAngleMax" + } + ] + }, + { + "folderName": "First person camera multitouch controller mapper position configuration", + "children": [ + { + "functionName": "OffsetZ" + }, + { + "functionName": "SetOffsetZ" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D capability", + "extraInformation": [ + "Scene3D::Base3DBehavior" + ], + "choices": [], + "name": "Object3D" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Secondary", + "type": "Choice", + "label": "Camera joystick", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "CameraStick" + }, + { + "value": "180", + "type": "Number", + "unit": "AngularSpeed", + "label": "Maximum rotation speed", + "group": "Horizontal rotation", + "name": "HorizontalRotationSpeedMax" + }, + { + "value": "360", + "type": "Number", + "label": "Rotation acceleration", + "group": "Horizontal rotation", + "name": "HorizontalRotationAcceleration" + }, + { + "value": "720", + "type": "Number", + "label": "Rotation deceleration", + "group": "Horizontal rotation", + "name": "HorizontalRotationDeceleration" + }, + { + "value": "120", + "type": "Number", + "unit": "AngularSpeed", + "label": "Maximum rotation speed", + "group": "Vertical rotation", + "name": "VerticalRotationSpeedMax" + }, + { + "value": "240", + "type": "Number", + "label": "Rotation acceleration", + "group": "Vertical rotation", + "name": "VerticalRotationAcceleration" + }, + { + "value": "480", + "type": "Number", + "label": "Rotation deceleration", + "group": "Vertical rotation", + "name": "VerticalRotationDeceleration" + }, + { + "value": "-90", + "type": "Number", + "unit": "DegreeAngle", + "label": "Minimum angle", + "group": "Vertical rotation", + "name": "VerticalAngleMin" + }, + { + "value": "90", + "type": "Number", + "unit": "DegreeAngle", + "label": "Maximum angle", + "group": "Vertical rotation", + "name": "VerticalAngleMax" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Z position offset", + "group": "Position", + "name": "OffsetZ" + }, + { + "value": "0", + "type": "Number", + "unit": "AngularSpeed", + "label": "Current rotation speed Z", + "hidden": true, + "name": "CurrentRotationSpeedZ" + }, + { + "value": "0", + "type": "Number", + "unit": "AngularSpeed", + "label": "Current rotation speed Y", + "hidden": true, + "name": "CurrentRotationSpeedY" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Object3D" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "CameraStick" + } + ] + }, + { + "folderName": "Horizontal rotation", + "children": [ + { + "propertyName": "HorizontalRotationSpeedMax" + }, + { + "propertyName": "HorizontalRotationAcceleration" + }, + { + "propertyName": "HorizontalRotationDeceleration" + } + ] + }, + { + "folderName": "Vertical rotation", + "children": [ + { + "propertyName": "VerticalRotationSpeedMax" + }, + { + "propertyName": "VerticalRotationAcceleration" + }, + { + "propertyName": "VerticalRotationDeceleration" + }, + { + "propertyName": "VerticalAngleMin" + }, + { + "propertyName": "VerticalAngleMax" + } + ] + }, + { + "folderName": "Position", + "children": [ + { + "propertyName": "OffsetZ" + } + ] + }, + { + "propertyName": "CurrentRotationSpeedZ" + }, + { + "propertyName": "CurrentRotationSpeedY" + } + ] + } + }, + { + "description": "Control a 3D physics car with a multitouch controller.", + "fullName": "3D car multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "PhysicsCar3DMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::StickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "SteerJoystickIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateSteeringStick" + }, + "parameters": [ + "Object", + "PhysicsCar3D", + "SpriteMultitouchJoystick::StickForceX(ControllerIdentifier, SteerJoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::StickForce" + }, + "parameters": [ + "", + ">", + "0", + "ControllerIdentifier", + "SpeedJoystickIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateAcceleratorStick" + }, + "parameters": [ + "Object", + "PhysicsCar3D", + "-SpriteMultitouchJoystick::StickForceY(ControllerIdentifier, SpeedJoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsButtonPressed" + }, + "parameters": [ + "", + "ControllerIdentifier", + "HandBrakeButton", + "\"Down\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateHandBrakeKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::PhysicsCar3DMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics car", + "extraInformation": [ + "Physics3D::PhysicsCar3D" + ], + "choices": [], + "name": "PhysicsCar3D" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Steer joystick", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "SteerJoystickIdentifier" + }, + { + "value": "Secondary", + "type": "Choice", + "label": "Speed joystick", + "group": "Controls", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "SpeedJoystickIdentifier" + }, + { + "value": "B", + "type": "String", + "label": "Hand brake button name", + "group": "Controls", + "name": "HandBrakeButton" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "PhysicsCar3D" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "SteerJoystickIdentifier" + }, + { + "propertyName": "SpeedJoystickIdentifier" + }, + { + "propertyName": "HandBrakeButton" + } + ] + } + ] + } + }, + { + "description": "Control a top-down character with a multitouch controller.", + "fullName": "Top-down multitouch controller mapper", + "helpPath": "", + "iconUrl": "", + "name": "TopDownMultitouchMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "StickMode", + "=", + "\"Analog\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateStick" + }, + "parameters": [ + "Object", + "TopDownMovement", + "SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier)", + "SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "StickMode", + "=", + "\"360°\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateStick" + }, + "parameters": [ + "Object", + "TopDownMovement", + "SpriteMultitouchJoystick::StickAngle(ControllerIdentifier, JoystickIdentifier)", + "sign(SpriteMultitouchJoystick::StickForce(ControllerIdentifier, JoystickIdentifier))" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "StickMode", + "=", + "\"8 Directions\"" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "TopDownMovementBehavior::DiagonalsAllowed" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Left\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Right\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Up\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Down\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "TopDownMovementBehavior::DiagonalsAllowed" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Left\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Right\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Up\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"Down\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"UpLeft\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + }, + { + "type": { + "value": "TopDownMovementBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"UpRight\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + }, + { + "type": { + "value": "TopDownMovementBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"DownLeft\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + }, + { + "type": { + "value": "TopDownMovementBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "", + "ControllerIdentifier", + "JoystickIdentifier", + "\"DownRight\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + }, + { + "type": { + "value": "TopDownMovementBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SpriteMultitouchJoystick::TopDownMultitouchMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Top-down movement behavior", + "extraInformation": [ + "TopDownMovementBehavior::TopDownMovementBehavior" + ], + "choices": [], + "name": "TopDownMovement" + }, + { + "value": "1", + "type": "Number", + "label": "Controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Joystick name", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "Analog", + "type": "Choice", + "label": "Stick mode", + "group": "Controls", + "choices": [ + { + "label": "Analog", + "value": "Analog" + }, + { + "label": "360°", + "value": "360°" + }, + { + "label": "8 Directions", + "value": "8 Directions" + } + ], + "name": "StickMode" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "TopDownMovement" + }, + { + "propertyName": "ControllerIdentifier" + }, + { + "propertyName": "JoystickIdentifier" + }, + { + "folderName": "Controls", + "children": [ + { + "propertyName": "StickMode" + } + ] + } + ] + } + } + ], + "eventsBasedObjects": [ + { + "areaMaxX": 64, + "areaMaxY": 64, + "areaMaxZ": 64, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "assetStoreTag": "multitouch joysticks", + "defaultName": "Joystick", + "description": "Joystick for touchscreens.", + "fullName": "Multitouch Joystick", + "helpPath": "", + "iconUrl": "", + "isUsingLegacyInstancesRenderer": true, + "name": "SpriteMultitouchJoystick", + "previewIconUrl": "", + "objects": [ + { + "adaptCollisionMaskAutomatically": false, + "assetStoreId": "", + "name": "Thumb", + "type": "Sprite", + "updateIfNotVisible": false, + "variables": [], + "effects": [], + "behaviors": [], + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [] + } + ] + } + ] + }, + { + "adaptCollisionMaskAutomatically": false, + "assetStoreId": "", + "name": "Border", + "type": "Sprite", + "updateIfNotVisible": false, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "MultitouchJoystick", + "type": "SpriteMultitouchJoystick::MultitouchJoystick", + "ControllerIdentifier": 1, + "JoystickIdentifier": "Primary", + "FloatingEnabled": false, + "DeadZoneRadius": 0.4, + "JoystickAngle": 0, + "JoystickForce": 0, + "TouchId": 0, + "TouchIndex": 0 + } + ], + "animations": [ + { + "name": "Idle", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [] + } + ] + } + ] + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Thumb" + }, + { + "objectName": "Border" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [], + "editionSettings": [], + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Create" + }, + "parameters": [ + "", + "Border", + "0", + "0", + "" + ] + }, + { + "type": { + "value": "Create" + }, + "parameters": [ + "", + "Thumb", + "0", + "0", + "" + ] + }, + { + "type": { + "value": "SetZOrder" + }, + "parameters": [ + "Border", + "=", + "1" + ] + }, + { + "type": { + "value": "SetZOrder" + }, + "parameters": [ + "Thumb", + "=", + "2" + ] + }, + { + "type": { + "value": "SetCenter" + }, + "parameters": [ + "Border", + "=", + "0", + "=", + "0" + ] + }, + { + "type": { + "value": "SetCenter" + }, + "parameters": [ + "Thumb", + "=", + "0", + "=", + "0" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::UpdateConfiguration" + }, + "parameters": [ + "Object", + "" + ] + }, + { + "type": { + "value": "SetIncludedInParentCollisionMask" + }, + "parameters": [ + "Thumb", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::IsInGameEdition" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "PutAroundObject" + }, + "parameters": [ + "Thumb", + "Border", + "Border.MultitouchJoystick::JoystickForce() * Border.Width() / 2", + "Border.MultitouchJoystick::JoystickAngle()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldBeHiddenWhenReleased", + "True", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::IsPressed" + }, + "parameters": [ + "Object", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Hide" + }, + "parameters": [ + "Object" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::ActivateControl" + }, + "parameters": [ + "Object", + "no", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "ShouldBeHiddenWhenReleased", + "False", + "" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::UpdateConfiguration" + }, + "parameters": [ + "Object", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Pass the object property values to the behavior.", + "fullName": "Update configuration", + "functionType": "Action", + "name": "UpdateConfiguration", + "private": true, + "sentence": "Update the configuration of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetControllerIdentifier" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "ControllerIdentifier", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetJoystickIdentifier" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "JoystickIdentifier", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetDeadZoneRadius" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "DeadZoneRadius", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Show the joystick until it is released.", + "fullName": "Show and start pressing", + "functionType": "Action", + "name": "TeleportAndPress", + "sentence": "Show _PARAM0_ at the cursor position and start pressing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetXY" + }, + "parameters": [ + "Object", + "=", + "Object.ParentTouchX(StartedTouchOrMouseId(0))", + "=", + "Object.ParentTouchY(StartedTouchOrMouseId(0))" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::ActivateControl" + }, + "parameters": [ + "Object", + "yes", + "" + ] + }, + { + "type": { + "value": "Show" + }, + "parameters": [ + "Object", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "ShouldBeHiddenWhenReleased", + "True", + "" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::ForceStartPressing" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "StartedTouchOrMouseId(0)", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the X position of a specified touch", + "fullName": "Touch X position (on parent)", + "functionType": "Expression", + "name": "ParentTouchX", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];", + "const touchId = eventsFunctionContext.getArgument(\"TouchId\");", + "eventsFunctionContext.returnValue = gdjs.evtTools.input.getTouchX(object.getInstanceContainer(), touchId, object.getLayer());" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + }, + { + "description": "Touch identifier", + "name": "TouchId", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the X position of a specified touch", + "fullName": "Touch X position (on parent)", + "functionType": "Expression", + "name": "ParentTouchY", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];", + "const touchId = eventsFunctionContext.getArgument(\"TouchId\");", + "eventsFunctionContext.returnValue = gdjs.evtTools.input.getTouchY(object.getInstanceContainer(), touchId, object.getLayer());" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + }, + { + "description": "Touch identifier", + "name": "TouchId", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "De/activate control of the joystick.", + "fullName": "De/activate control", + "functionType": "Action", + "name": "ActivateControl", + "sentence": "Activate control of _PARAM0_: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldActivate", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "no" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldActivate", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "yes" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + }, + { + "description": "Activate", + "name": "ShouldActivate", + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a stick is pressed.", + "fullName": "Stick pressed", + "functionType": "Condition", + "name": "IsPressed", + "sentence": "Stick _PARAM0_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::IsPressed" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "!=" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the joystick force (from 0 to 1).", + "fullName": "Joystick force (deprecated)", + "functionType": "ExpressionAndCondition", + "name": "JoystickForce", + "private": true, + "sentence": "the joystick force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::JoystickForce()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the strick force (from 0 to 1).", + "fullName": "Stick force", + "functionType": "ExpressionAndCondition", + "name": "StickForce", + "sentence": "the stick force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::JoystickForce()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the stick force on X axis (from -1 at the left to 1 at the right).", + "fullName": "Stick X force", + "functionType": "ExpressionAndCondition", + "name": "StickForceX", + "sentence": "the stick X force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::StickForceX()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the stick force on Y axis (from -1 at the top to 1 at the bottom).", + "fullName": "Stick Y force", + "functionType": "ExpressionAndCondition", + "name": "StickForceY", + "sentence": "the stick Y force", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::StickForceY()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle the joystick is pointing towards (from -180 to 180).", + "fullName": "Joystick angle (deprecated)", + "functionType": "Expression", + "name": "JoystickAngle", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::JoystickAngle()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle the stick is pointing towards (from -180 to 180).", + "fullName": "Stick angle", + "functionType": "Expression", + "name": "StickAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::JoystickAngle()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (4-way movement)", + "functionType": "Condition", + "name": "IsDirectionPushed4Way", + "sentence": "_PARAM0_ is pushed in direction _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if joystick is pushed in a given direction.", + "fullName": "Joystick pushed in a direction (8-way movement)", + "functionType": "Condition", + "name": "IsDirectionPushed8Way", + "sentence": "_PARAM0_ is pushed in direction _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::IsDirectionPushed8Way" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "Direction", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + }, + { + "description": "Direction", + "name": "Direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\",\"UpLeft\",\"UpRight\",\"DownLeft\",\"DownRight\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "the multitouch controller identifier (1, 2, 3, 4...).", + "fullName": "Multitouch controller identifier", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "ControllerIdentifier", + "sentence": "the multitouch controller identifier", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::PropertyControllerIdentifier()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ControllerIdentifier", + "group": "Multitouch Joystick configuration", + "name": "SetControllerIdentifier", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetControllerIdentifier" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "Value", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the joystick name of the object.", + "fullName": "Joystick name", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "JoystickIdentifier", + "sentence": "the joystick name", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "Border.MultitouchJoystick::PropertyJoystickIdentifier()" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "JoystickIdentifier", + "group": "Multitouch Joystick configuration", + "name": "SetJoystickIdentifier", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetJoystickIdentifier" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "Value", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the dead zone radius of the joystick (range: 0 to 1). The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved).", + "fullName": "Dead zone radius", + "functionType": "ExpressionAndCondition", + "group": "Multitouch Joystick configuration", + "name": "DeadZoneRadius", + "sentence": "the dead zone radius", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Border.MultitouchJoystick::PropertyDeadZoneRadius()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "DeadZoneRadius", + "group": "Multitouch Joystick configuration", + "name": "SetDeadZoneRadius", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::MultitouchJoystick::SetDeadZoneRadius" + }, + "parameters": [ + "Border", + "MultitouchJoystick", + "=", + "Value", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "type": "object" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPostEvents" + }, + { + "functionName": "onHotReloading" + }, + { + "functionName": "UpdateConfiguration" + }, + { + "functionName": "TeleportAndPress" + }, + { + "functionName": "ParentTouchX" + }, + { + "functionName": "ParentTouchY" + }, + { + "functionName": "ActivateControl" + }, + { + "functionName": "IsPressed" + }, + { + "functionName": "JoystickForce" + }, + { + "functionName": "StickForce" + }, + { + "functionName": "StickForceX" + }, + { + "functionName": "StickForceY" + }, + { + "functionName": "JoystickAngle" + }, + { + "functionName": "StickAngle" + }, + { + "functionName": "IsDirectionPushed4Way" + }, + { + "functionName": "IsDirectionPushed8Way" + }, + { + "folderName": "Multitouch Joystick configuration", + "children": [ + { + "functionName": "ControllerIdentifier" + }, + { + "functionName": "SetControllerIdentifier" + }, + { + "functionName": "JoystickIdentifier" + }, + { + "functionName": "SetJoystickIdentifier" + }, + { + "functionName": "DeadZoneRadius" + }, + { + "functionName": "SetDeadZoneRadius" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "1", + "type": "Number", + "label": "Multitouch controller identifier (1, 2, 3, 4...)", + "name": "ControllerIdentifier" + }, + { + "value": "Primary", + "type": "Choice", + "label": "Joystick name", + "choices": [ + { + "label": "Primary", + "value": "Primary" + }, + { + "label": "Secondary", + "value": "Secondary" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "0.4", + "type": "Number", + "label": "Dead zone radius (range: 0 to 1)", + "description": "The deadzone is an area for which movement on sticks won't be taken into account (instead, the stick will be considered as not moved)", + "name": "DeadZoneRadius" + }, + { + "value": "Center-center", + "type": "String", + "label": "", + "description": "Only used by the scene editor.", + "extraInformation": [ + "Thumb" + ], + "choices": [], + "hidden": true, + "name": "ThumbAnchorOrigin" + }, + { + "value": "Center-center", + "type": "Number", + "label": "", + "description": "Only used by the scene editor.", + "hidden": true, + "name": "ThumbAnchorTarget" + }, + { + "value": "true", + "type": "Boolean", + "label": "", + "description": "Only used by the scene editor.", + "extraInformation": [ + "Thumb" + ], + "choices": [], + "hidden": true, + "name": "ThumbIsScaledProportionally" + }, + { + "value": "Center-center", + "type": "String", + "label": "", + "description": "Only used by the scene editor.", + "hidden": true, + "name": "ParentOrigin" + }, + { + "value": "", + "type": "Boolean", + "label": "", + "hidden": true, + "name": "ShouldBeHiddenWhenReleased" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ControllerIdentifier" + }, + { + "propertyName": "JoystickIdentifier" + }, + { + "propertyName": "DeadZoneRadius" + }, + { + "propertyName": "ThumbAnchorOrigin" + }, + { + "propertyName": "ThumbAnchorTarget" + }, + { + "propertyName": "ThumbIsScaledProportionally" + }, + { + "propertyName": "ParentOrigin" + }, + { + "propertyName": "ShouldBeHiddenWhenReleased" + } + ] + } + } + ] + }, + { + "author": "", + "category": "Input", + "dimension": "", + "extensionNamespace": "", + "fullName": "3D car keyboard mapper", + "gdevelopVersion": ">=5.5.222", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBhdGggZD0iTTI5LDE0aC00LjJjMC4xLTAuMywwLjItMC42LDAuMi0xYzAtMS43LTEuMy0zLTMtM0g4Yy0wLjYsMC0xLTAuNC0xLTFzMC40LTEsMS0xaDZjMS43LDAsMy0xLjMsMy0zVjJjMC0wLjYtMC40LTEtMS0xDQoJcy0xLDAuNC0xLDF2M2MwLDAuNi0wLjQsMS0xLDFIOEM2LjMsNiw1LDcuMyw1LDlzMS4zLDMsMywzaDE0YzAuNiwwLDEsMC40LDEsMXMtMC40LDEtMSwxSDNjLTAuNiwwLTEsMC40LTEsMXYxNGMwLDAuNiwwLjQsMSwxLDENCgloMjZjMC42LDAsMS0wLjQsMS0xVjE1QzMwLDE0LjQsMjkuNiwxNCwyOSwxNHogTTcsMThoMmMwLjYsMCwxLDAuNCwxLDFjMC0wLjYsMC40LTEsMS0xaDJjMC42LDAsMSwwLjQsMSwxYzAtMC42LDAuNC0xLDEtMWgyDQoJYzAuNiwwLDEsMC40LDEsMWMwLTAuNiwwLjQtMSwxLTFoMmMwLjYsMCwxLDAuNCwxLDFjMC0wLjYsMC40LTEsMS0xaDJjMC42LDAsMSwwLjQsMSwxcy0wLjQsMS0xLDFoLTJjLTAuNiwwLTEtMC40LTEtMQ0KCWMwLDAuNi0wLjQsMS0xLDFoLTJjLTAuNiwwLTEtMC40LTEtMWMwLDAuNi0wLjQsMS0xLDFoLTJjLTAuNiwwLTEtMC40LTEtMWMwLDAuNi0wLjQsMS0xLDFoLTJjLTAuNiwwLTEtMC40LTEtMWMwLDAuNi0wLjQsMS0xLDFINw0KCWMtMC42LDAtMS0wLjQtMS0xUzYuNCwxOCw3LDE4eiBNMTksMjJjMCwwLjEsMCwwLjMtMC4xLDAuNHMtMC4xLDAuMi0wLjIsMC4zYzAsMC0wLjEsMC4xLTAuMiwwLjFjMCwwLTAuMSwwLjEtMC4yLDAuMQ0KCWMtMC4xLDAtMC4xLDAtMC4yLDAuMWMtMC4xLDAtMC4xLDAtMC4yLDBjLTAuMSwwLTAuMSwwLTAuMiwwYy0wLjEsMC0wLjEsMC0wLjItMC4xYy0wLjEsMC0wLjEtMC4xLTAuMi0wLjFjMCwwLTAuMS0wLjEtMC4xLTAuMQ0KCWMtMC4xLTAuMS0wLjItMC4yLTAuMi0wLjNDMTcsMjIuMywxNywyMi4xLDE3LDIyYzAtMC4xLDAtMC4zLDAuMS0wLjRjMC0wLjEsMC4xLTAuMiwwLjItMC4zYzAsMCwwLjEtMC4xLDAuMS0wLjENCgljMC4xLDAsMC4xLTAuMSwwLjItMC4xYzAuMSwwLDAuMSwwLDAuMi0wLjFjMC4xLDAsMC4zLDAsMC40LDBjMC4xLDAsMC4xLDAsMC4yLDAuMWMwLjEsMCwwLjEsMC4xLDAuMiwwLjFjMC4xLDAsMC4xLDAuMSwwLjIsMC4xDQoJYzAuMSwwLjEsMC4yLDAuMiwwLjIsMC4zQzE5LDIxLjcsMTksMjEuOSwxOSwyMnogTTE1LDIyYzAsMC4xLDAsMC4zLTAuMSwwLjRzLTAuMSwwLjItMC4yLDAuM2MwLDAtMC4xLDAuMS0wLjIsMC4xDQoJYzAsMC0wLjEsMC4xLTAuMiwwLjFjLTAuMSwwLTAuMSwwLTAuMiwwLjFjLTAuMSwwLTAuMSwwLTAuMiwwYy0wLjMsMC0wLjUtMC4xLTAuNy0wLjNjLTAuMS0wLjEtMC4yLTAuMi0wLjItMC4zDQoJQzEzLDIyLjMsMTMsMjIuMSwxMywyMmMwLTAuMSwwLTAuMSwwLTAuMmMwLTAuMSwwLTAuMSwwLjEtMC4yYzAtMC4xLDAtMC4xLDAuMS0wLjJjMC0wLjEsMC4xLTAuMSwwLjEtMC4xYzAuMi0wLjIsMC42LTAuMywwLjktMC4zDQoJYzAuMSwwLDAuMSwwLDAuMiwwLjFjMC4xLDAsMC4xLDAuMSwwLjIsMC4xYzAuMSwwLDAuMSwwLjEsMC4yLDAuMWMwLDAsMC4xLDAuMSwwLjEsMC4xYzAsMC4xLDAuMSwwLjEsMC4xLDAuMmMwLDAuMSwwLDAuMSwwLjEsMC4yDQoJQzE1LDIxLjksMTUsMjEuOSwxNSwyMnogTTUsMjJjMC0wLjEsMC0wLjMsMC4xLTAuNGMwLTAuMSwwLjEtMC4yLDAuMi0wLjNjMC4yLTAuMiwwLjYtMC4zLDAuOS0wLjNjMC4xLDAsMC4xLDAsMC4yLDAuMQ0KCWMwLjEsMCwwLjEsMC4xLDAuMiwwLjFjMCwwLDAuMSwwLjEsMC4yLDAuMWMwLjEsMC4xLDAuMiwwLjIsMC4yLDAuM0M3LDIxLjcsNywyMS45LDcsMjJjMCwwLjMtMC4xLDAuNS0wLjMsMC43YzAsMC0wLjEsMC4xLTAuMiwwLjENCgljLTAuMSwwLTAuMSwwLjEtMC4yLDAuMWMtMC4xLDAtMC4xLDAtMC4yLDAuMWMtMC4xLDAtMC4xLDAtMC4yLDBjLTAuMywwLTAuNS0wLjEtMC43LTAuM0M1LjEsMjIuNSw1LDIyLjMsNSwyMnogTTgsMjZINw0KCWMtMC42LDAtMS0wLjQtMS0xczAuNC0xLDEtMWgxYzAuNiwwLDEsMC40LDEsMVM4LjYsMjYsOCwyNnogTTEwLjksMjIuNGMwLDAuMSwwLDAuMS0wLjEsMC4yYzAsMC0wLjEsMC4xLTAuMSwwLjENCglDMTAuNSwyMi45LDEwLjMsMjMsMTAsMjNjLTAuMSwwLTAuMywwLTAuNC0wLjFjLTAuMS0wLjEtMC4yLTAuMS0wLjMtMC4yYzAtMC4xLTAuMS0wLjEtMC4xLTAuMWMwLTAuMS0wLjEtMC4xLTAuMS0wLjINCgljMC0wLjEsMC0wLjEtMC4xLTAuMmMwLTAuMSwwLTAuMSwwLTAuMmMwLTAuMywwLjEtMC41LDAuMy0wLjdjMC4xLTAuMSwwLjItMC4yLDAuMy0wLjJjMC40LTAuMiwwLjgtMC4xLDEuMSwwLjINCgljMC4yLDAuMiwwLjMsMC40LDAuMywwLjdjMCwwLjEsMCwwLjEsMCwwLjJDMTEsMjIuMywxMSwyMi4zLDEwLjksMjIuNHogTTIwLDI2aC04Yy0wLjYsMC0xLTAuNC0xLTFzMC40LTEsMS0xaDhjMC42LDAsMSwwLjQsMSwxDQoJUzIwLjYsMjYsMjAsMjZ6IE0yMi45LDIyLjRjMCwwLjEsMCwwLjEtMC4xLDAuMmMwLDAtMC4xLDAuMS0wLjEsMC4xYzAsMC0wLjEsMC4xLTAuMiwwLjFjMCwwLTAuMSwwLjEtMC4yLDAuMWMtMC4xLDAtMC4xLDAtMC4yLDAuMQ0KCWMtMC4xLDAtMC4xLDAtMC4yLDBjLTAuMSwwLTAuMSwwLTAuMiwwYy0wLjEsMC0wLjEsMC0wLjItMC4xYy0wLjEsMC0wLjEtMC4xLTAuMi0wLjFjMCwwLTAuMS0wLjEtMC4xLTAuMWMwLTAuMS0wLjEtMC4xLTAuMS0wLjENCgljMC0wLjEtMC4xLTAuMS0wLjEtMC4yYzAtMC4xLDAtMC4xLTAuMS0wLjJjMC0wLjEsMC0wLjEsMC0wLjJjMC0wLjMsMC4xLTAuNSwwLjMtMC43YzAsMCwwLjEtMC4xLDAuMS0wLjFjMC4xLDAsMC4xLTAuMSwwLjItMC4xDQoJYzAuMSwwLDAuMSwwLDAuMi0wLjFjMC4xLDAsMC4zLDAsMC40LDBjMC4xLDAsMC4xLDAsMC4yLDAuMWMwLjEsMCwwLjEsMC4xLDAuMiwwLjFjMC4xLDAsMC4xLDAuMSwwLjIsMC4xYzAuMiwwLjIsMC4zLDAuNCwwLjMsMC43DQoJYzAsMC4xLDAsMC4xLDAsMC4yQzIzLDIyLjMsMjMsMjIuMywyMi45LDIyLjR6IE0yNSwyNmgtMWMtMC42LDAtMS0wLjQtMS0xczAuNC0xLDEtMWgxYzAuNiwwLDEsMC40LDEsMVMyNS42LDI2LDI1LDI2eiBNMjYuNywyMi43DQoJQzI2LjUsMjIuOSwyNi4zLDIzLDI2LDIzYy0wLjEsMC0wLjEsMC0wLjIsMGMtMC4xLDAtMC4xLDAtMC4yLTAuMWMtMC4xLDAtMC4xLTAuMS0wLjItMC4xYy0wLjEsMC0wLjEtMC4xLTAuMi0wLjENCglDMjUuMSwyMi41LDI1LDIyLjMsMjUsMjJjMC0wLjEsMC0wLjMsMC4xLTAuNGMwLTAuMSwwLjEtMC4yLDAuMi0wLjNjMCwwLDAuMS0wLjEsMC4yLTAuMWMwLDAsMC4xLTAuMSwwLjItMC4xYzAuMSwwLDAuMSwwLDAuMi0wLjENCgljMC4zLTAuMSwwLjcsMCwwLjksMC4zYzAuMiwwLjIsMC4zLDAuNCwwLjMsMC43QzI3LDIyLjMsMjYuOSwyMi41LDI2LjcsMjIuN3oiLz4NCjwvc3ZnPg0K", + "name": "PhysicsCar3DKeyMapper", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/Glyphster Pack/Master/SVG/Computers and Hardware/3e5871434a72821bf3ecb44a6857e62a438cf23dc8f95966f56ae97e95315468_Computers and Hardware_keyboard.svg", + "shortDescription": "3D car keyboard controls.", + "version": "1.0.1", + "description": "3D physics car keyboard controls.", + "origin": { + "identifier": "PhysicsCar3DKeyMapper", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT" + }, + "eventsBasedBehaviors": [ + { + "description": "Control a 3D physics car with a keyboard.", + "fullName": "3D car keyboard mapper", + "helpPath": "", + "iconUrl": "", + "name": "PhysicsCar3DKeyboardMapper", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Down" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateBackwardKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Up" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateForwardKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Left" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateLeftKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "Right" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateRightKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "HandBrake" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateHandBrakeKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "PhysicsCar3DKeyMapper::PhysicsCar3DKeyboardMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics car", + "extraInformation": [ + "Physics3D::PhysicsCar3D" + ], + "choices": [], + "name": "PhysicsCar3D" + }, + { + "value": "Space", + "type": "KeyboardKey", + "label": "Hand brake key", + "name": "HandBrake" + }, + { + "value": "Up", + "type": "KeyboardKey", + "label": "Up key", + "name": "Up" + }, + { + "value": "Left", + "type": "KeyboardKey", + "label": "Left key", + "name": "Left" + }, + { + "value": "Down", + "type": "KeyboardKey", + "label": "Down key", + "name": "Down" + }, + { + "value": "Right", + "type": "KeyboardKey", + "label": "Right key", + "name": "Right" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "PhysicsCar3D" + }, + { + "propertyName": "HandBrake" + }, + { + "propertyName": "Up" + }, + { + "propertyName": "Left" + }, + { + "propertyName": "Down" + }, + { + "propertyName": "Right" + } + ] + } + } + ], + "eventsBasedObjects": [] + }, + { + "author": "", + "category": "Visual effect", + "dimension": "3D", + "extensionNamespace": "", + "fullName": "3D particle emitter", + "gdevelopVersion": ">=5.5.222", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLWZpcmUiIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTcuNjYgMTEuMkMxNy40MyAxMC45IDE3LjE1IDEwLjY0IDE2Ljg5IDEwLjM4QzE2LjIyIDkuNzggMTUuNDYgOS4zNSAxNC44MiA4LjcyQzEzLjMzIDcuMjYgMTMgNC44NSAxMy45NSAzQzEzIDMuMjMgMTIuMTcgMy43NSAxMS40NiA0LjMyQzguODcgNi40IDcuODUgMTAuMDcgOS4wNyAxMy4yMkM5LjExIDEzLjMyIDkuMTUgMTMuNDIgOS4xNSAxMy41NUM5LjE1IDEzLjc3IDkgMTMuOTcgOC44IDE0LjA1QzguNTcgMTQuMTUgOC4zMyAxNC4wOSA4LjE0IDEzLjkzQzguMDggMTMuODggOC4wNCAxMy44MyA4IDEzLjc2QzYuODcgMTIuMzMgNi42OSAxMC4yOCA3LjQ1IDguNjRDNS43OCAxMCA0Ljg3IDEyLjMgNSAxNC40N0M1LjA2IDE0Ljk3IDUuMTIgMTUuNDcgNS4yOSAxNS45N0M1LjQzIDE2LjU3IDUuNyAxNy4xNyA2IDE3LjdDNy4wOCAxOS40MyA4Ljk1IDIwLjY3IDEwLjk2IDIwLjkyQzEzLjEgMjEuMTkgMTUuMzkgMjAuOCAxNy4wMyAxOS4zMkMxOC44NiAxNy42NiAxOS41IDE1IDE4LjU2IDEyLjcyTDE4LjQzIDEyLjQ2QzE4LjIyIDEyIDE3LjY2IDExLjIgMTcuNjYgMTEuMk0xNC41IDE3LjVDMTQuMjIgMTcuNzQgMTMuNzYgMTggMTMuNCAxOC4xQzEyLjI4IDE4LjUgMTEuMTYgMTcuOTQgMTAuNSAxNy4yOEMxMS42OSAxNyAxMi40IDE2LjEyIDEyLjYxIDE1LjIzQzEyLjc4IDE0LjQzIDEyLjQ2IDEzLjc3IDEyLjMzIDEzQzEyLjIxIDEyLjI2IDEyLjIzIDExLjYzIDEyLjUgMTAuOTRDMTIuNjkgMTEuMzIgMTIuODkgMTEuNyAxMy4xMyAxMkMxMy45IDEzIDE1LjExIDEzLjQ0IDE1LjM3IDE0LjhDMTUuNDEgMTQuOTQgMTUuNDMgMTUuMDggMTUuNDMgMTUuMjNDMTUuNDYgMTYuMDUgMTUuMSAxNi45NSAxNC41IDE3LjVIMTQuNVoiIC8+PC9zdmc+", + "name": "ParticleEmitter3D", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/f2e5a34bf465f781866677762d385d6c8e9e8d203383f2df9a3b7e0fad6a2cb5_fire.svg", + "shortDescription": "Display a large number of particles in 3D to create visual effects in a 3D game.", + "version": "3.2.1", + "description": [ + "3D particle emitters let you create and display many small particles to simulate visual effects in your game — like fire, explosions, smoke, dust, magic, rain or snow.", + "", + "The parameters of the object can be configured in multiple different ways to create a specific desired effect. GDevelop will give a set of a pre-configured 3D particle emitters that you should try first.", + "", + "Particles are emitted along the local Z axis of the emitter: upward by default. Rotate the instance to aim the emission (for instance a rotation of 180° on the X axis emits downward, 90° on the Y axis emits toward X). The size of the instance has no effect: particles are emitted from a single point, or from a disc when the \"Emission zone radius\" property is set (useful for rain or snow falling over a whole area).", + "", + "Use these 3D particle emitters in 3D games. For 2D games or particles that appear on a game's 2D user interface, use the 2D particle emitter object instead." + ], + "origin": { + "identifier": "ParticleEmitter3D", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d", + "particle", + "explosion", + "fire", + "smoke", + "splash", + "light", + "rain", + "snow", + "weather" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "changelog": [ + { + "version": "3.0.0", + "breaking": "- the object rotates the other way around X axis." + }, + { + "version": "2.0.0", + "breaking": "- Object properties for position and rotation have been removed. They must be set with the instance editor or the action." + }, + { + "version": "1.0.0", + "breaking": "- Particles were 3 times too small." + } + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "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.__particleEmmiter3DExtension) {", + " return;", + "}", + "", + "const game = runtimeScene.getGame();", + "", + "class ParticleEmitter3DRenderer extends gdjs.CustomRuntimeObject3DRenderer {", + " constructor(", + " object,", + " instanceContainer,", + " parent", + " ) {", + " super(object, instanceContainer, parent);", + " }", + "", + " _updateThreeGroup() {", + " const threeObject3D = this.get3DRendererObject();", + "", + " threeObject3D.rotation.set(", + " gdjs.toRad(this._object.getRotationX()),", + " gdjs.toRad(this._object.getRotationY()),", + " gdjs.toRad(this._object.angle)", + " );", + "", + " threeObject3D.position.set(", + " this._object.getX(),", + " this._object.getY(),", + " this._object.getZ()", + " );", + "", + " // Force the scale to 1 because the particle emitter doesn't really has a size.", + " threeObject3D.scale.set(", + " this._object.isFlippedX() ? -1 : 1,", + " this._object.isFlippedY() ? -1 : 1,", + " this._object.isFlippedZ() ? -1 : 1", + " );", + "", + " threeObject3D.visible = !this._object.hidden;", + "", + " this._isContainerDirty = true;", + " }", + "}", + "", + "const coneLength = 64;", + "", + "class ParticleEmitterHelper extends THREE.Object3D {", + " /** @type {gdjs.CustomRuntimeObject3D} */", + " object;", + " /** @type {THREE.LineSegments} */", + " cone;", + " /** @type {THREE.LineSegments} */", + " centerLine;", + " /** @type {THREE.LineSegments} */", + " zoneCircle;", + " /** @type {THREE.Mesh} */", + " originBox;", + "", + "\t/**", + " * @param gdjs.CustomRuntimeObject3D object", + "\t */", + " constructor(object) {", + " super();", + " this.object = object;", + " this.type = 'ParticleEmitterHelper';", + "", + " const coneGeometry = new THREE.BufferGeometry();", + " const conePositions = [", + " 0, 0, 0, 0, 0, 1,", + " 0, 0, 0, 1, 0, 1,", + " 0, 0, 0, - 1, 0, 1,", + " 0, 0, 0, 0, 1, 1,", + " 0, 0, 0, 0, - 1, 1", + " ];", + " for (let i = 0, j = 1, l = 32; i < l; i++ , j++) {", + " const p1 = (i / l) * Math.PI * 2;", + " const p2 = (j / l) * Math.PI * 2;", + " conePositions.push(", + " Math.cos(p1), Math.sin(p1), 1,", + " Math.cos(p2), Math.sin(p2), 1", + " );", + " }", + " coneGeometry.setAttribute('position', new THREE.Float32BufferAttribute(conePositions, 3));", + " this.cone = new THREE.LineSegments(", + " coneGeometry,", + " new THREE.LineBasicMaterial({ fog: false, toneMapped: false })", + " );", + " this.add(this.cone);", + " const centerLineGeometry = new THREE.BufferGeometry();", + " centerLineGeometry.setAttribute('position', new THREE.Float32BufferAttribute([", + " 0, 0, 0, 0, 0, coneLength", + " ], 3));", + " this.centerLine = new THREE.LineSegments(", + " centerLineGeometry,", + " new THREE.LineBasicMaterial({ fog: false, toneMapped: false })", + " );", + " this.add(this.centerLine);", + "", + " const zoneCircleGeometry = new THREE.BufferGeometry();", + " const zoneCirclePositions = [];", + " for (let i = 0, j = 1, l = 32; i < l; i++ , j++) {", + " const p1 = (i / l) * Math.PI * 2;", + " const p2 = (j / l) * Math.PI * 2;", + " zoneCirclePositions.push(", + " Math.cos(p1), Math.sin(p1), 0,", + " Math.cos(p2), Math.sin(p2), 0", + " );", + " }", + " zoneCircleGeometry.setAttribute('position', new THREE.Float32BufferAttribute(zoneCirclePositions, 3));", + " this.zoneCircle = new THREE.LineSegments(", + " zoneCircleGeometry,", + " new THREE.LineBasicMaterial({ fog: false, toneMapped: false })", + " );", + " this.add(this.zoneCircle);", + "", + " this.originBox = new THREE.Mesh(", + " new THREE.BoxGeometry(16, 16, 16),", + " new THREE.MeshBasicMaterial({ fog: false, toneMapped: false })", + " );", + " this.add(this.originBox);", + " this.update();", + "", + " this.originBox.gdjsRuntimeObject = object;", + " }", + "", + " dispose() {", + " this.cone.geometry.dispose();", + " this.cone.material.dispose();", + " this.centerLine.geometry.dispose();", + " this.centerLine.material.dispose();", + " this.zoneCircle.geometry.dispose();", + " this.zoneCircle.material.dispose();", + " this.originBox.geometry.dispose();", + " this.originBox.material.dispose();", + " }", + "", + " update() {", + " const coneWidth = coneLength * Math.sin(gdjs.toRad(this.object._getSpayConeAngle()));", + " const coneHeight = coneLength * Math.cos(gdjs.toRad(this.object._getSpayConeAngle()));", + " this.cone.scale.set(coneWidth, coneWidth, coneHeight);", + " const zoneRadius = this.object._getZoneRadius();", + " this.zoneCircle.visible = zoneRadius > 0;", + " if (zoneRadius > 0) {", + " this.zoneCircle.scale.set(zoneRadius, zoneRadius, 1);", + " }", + " const startColor = gdjs.rgbOrHexStringToNumber(this.object._getStartColor())", + " const endColor = gdjs.rgbOrHexStringToNumber(this.object._getEndColor());", + " this.cone.material.color.set(endColor);", + " this.centerLine.material.color.set(startColor);", + " this.originBox.material.color.set(startColor);", + " }", + "}", + "", + "/**", + " * @param {string} colorString", + " * @param {THREE.Vector4} threeColor", + " */", + "const setThreeColor = (colorString, threeColor = new THREE.Vector4()) => {", + " const integerColor = gdjs.rgbOrHexToRGBColor(colorString);", + " threeColor.x = integerColor[0] / 255;", + " threeColor.y = integerColor[1] / 255;", + " threeColor.z = integerColor[2] / 255;", + "};", + "", + "/**", + " * @param {string} integerOpacity", + " * @param {THREE.Vector4} threeColor", + " */", + "const setThreeOpacity = (integerOpacity, threeColor = new THREE.Vector4()) => {", + " threeColor.w = integerOpacity / 255;", + "};", + "", + "class ParticleEmitterAdapter {", + " /**", + " * @param inverseWorldScale {number}", + " * @param particleSystem {ParticleSystem}", + " * @param colorOverLife {ColorOverLife}", + " * @param sizeOverLife {SizeOverLife}", + " * @param applyForce {ApplyForce}", + " * @param applyForce {ApplyForce}", + " * @param widthOverLength {WidthOverLength}", + " */", + " constructor(inverseWorldScale, particleSystem, colorOverLife, sizeOverLife, applyForce, widthOverLength) {", + " this.inverseWorldScale = inverseWorldScale;", + " this.particleSystem = particleSystem;", + " this.sprayConeAngle = 0;", + " this.zoneRadius = 0;", + " this.startSpeedMin = 0;", + " this.startSpeedMax = 0;", + " this.gravity = 0;", + " this.colorOverLife = colorOverLife;", + " this.sizeOverLife = sizeOverLife;", + " this.applyForce = applyForce;", + " this.widthOverLength = widthOverLength;", + " }", + "", + " /**", + " * @param renderMode {'Billboard' | 'Trail'}", + " */", + " setRenderMode(renderMode) {", + " switch (renderMode) {", + " case \"BillBoard\":", + " this.particleSystem.renderMode = RenderMode.BillBoard;", + " break;", + " case \"Trail\":", + " this.particleSystem.renderMode = RenderMode.Trail;", + " this.particleSystem.rendererEmitterSettings.startLength = new IntervalValue(30, 30);", + " break;", + " }", + " }", + "", + " /**", + " * @param startLengthMin {number}", + " */", + " setTrailStartLengthMin(startLengthMin) {", + " const startLength = this.particleSystem.rendererEmitterSettings.startLength;", + " if (!startLength) {", + " return;", + " }", + " startLength.a = startLengthMin;", + " }", + "", + " /**", + " * @param startLengthMax {number}", + " */", + " setTrailStartLengthMax(startLengthMax) {", + " const startLength = this.particleSystem.rendererEmitterSettings.startLength;", + " if (!startLength) {", + " return;", + " }", + " startLength.b = startLengthMax;", + " }", + "", + " /**", + " * @param followLocalOrigin {boolean}", + " */", + " setTrailFollowingLocalOrigin(followLocalOrigin) {", + " this.particleSystem.rendererEmitterSettings.followLocalOrigin = followLocalOrigin;", + " }", + "", + " /**", + " * @param trailEndWidthRatio {number}", + " */", + " setTrailEndWidthRatio(trailEndWidthRatio) {", + " // Check this comment in the lib code:", + " // We added a multiplication with size because it's more convenient for the extension.", + "", + " /** @type Bezier */", + " const bezier = this.widthOverLength.width.functions[0][0];", + " bezier.p[0] = 1;", + " bezier.p[1] = gdjs.evtTools.common.lerp(1, trailEndWidthRatio, 1 / 3);", + " bezier.p[2] = gdjs.evtTools.common.lerp(1, trailEndWidthRatio, 2 / 3);", + " bezier.p[3] = trailEndWidthRatio;", + " }", + "", + "", + " /**", + " * @param startColor {string}", + " */", + " setStartColor(startColor) {", + " setThreeColor(startColor, this.colorOverLife.color.color.keys[0][0]);", + " }", + "", + " /**", + " * @param endColor {string}", + " */", + " setEndColor(endColor) {", + " setThreeColor(endColor, this.colorOverLife.color.color.keys[1][0]);", + " }", + "", + " /**", + " * @param startOpacity {number}", + " */", + " setStartOpacity(startOpacity) {", + " this.colorOverLife.color.alpha.keys[0][0] = startOpacity / 255;", + " }", + "", + " /**", + " * @param endOpacity {number}", + " */", + " setEndOpacity(endOpacity) {", + " this.colorOverLife.color.alpha.keys[1][0] = endOpacity / 255;", + " }", + "", + " /**", + " * @param flow {number}", + " */", + " setFlow(flow) {", + " this.particleSystem.emissionOverTime.value = flow;", + " }", + "", + " /**", + " * @param startMinSize {number}", + " */", + " setStartMinSize(startMinSize) {", + " this.particleSystem.startSize.a = startMinSize;", + " }", + "", + " /**", + " * @param startMaxSize {number}", + " */", + " setStartMaxSize(startMaxSize) {", + " this.particleSystem.startSize.b = startMaxSize;", + " }", + "", + " /**", + " * @param endScale {number}", + " */", + " setEndScale(endScale) {", + " /** @type Bezier */", + " const bezier = this.sizeOverLife.size.functions[0][0];", + " bezier.p[0] = 1;", + " bezier.p[1] = gdjs.evtTools.common.lerp(1, endScale, 1 / 3);", + " bezier.p[2] = gdjs.evtTools.common.lerp(1, endScale, 2 / 3);", + " bezier.p[3] = endScale;", + " }", + "", + " /**", + " * @param startSpeedMin {number}", + " */", + " setStartSpeedMin(startSpeedMin) {", + " this.startSpeedMin = startSpeedMin;", + " this.updateStartSpeed();", + " }", + "", + " /**", + " * @param startSpeedMax {number}", + " */", + " setStartSpeedMax(startSpeedMax) {", + " this.startSpeedMax = startSpeedMax;", + " this.updateStartSpeed();", + " }", + "", + " /**", + " * Velocities of particles in world space are in the Three.js world units,", + " * so they need the world scale. Velocities of relative particles are in", + " * the emitter units, converted by the emitter transformation.", + " */", + " getVelocityScale() {", + " return this.particleSystem.worldSpace ? this.inverseWorldScale : 1;", + " }", + "", + " updateStartSpeed() {", + " const velocityScale = this.getVelocityScale();", + " this.particleSystem.startSpeed.a = this.startSpeedMin * velocityScale;", + " this.particleSystem.startSpeed.b = this.startSpeedMax * velocityScale;", + " }", + "", + " /**", + " * @param lifespanMin {number}", + " */", + " setLifespanMin(lifespanMin) {", + " this.particleSystem.startLife.a = lifespanMin;", + " }", + "", + " /**", + " * @param lifespanMax {number}", + " */", + " setLifespanMax(lifespanMax) {", + " this.particleSystem.startLife.b = lifespanMax;", + " }", + "", + " /**", + " * @param duration {number}", + " */", + " setDuration(duration) {", + " this.particleSystem.duration = duration || Number.POSITIVE_INFINITY;", + " }", + "", + " /**", + " * @param areParticlesRelative {boolean}", + " */", + " setParticlesRelative(areParticlesRelative) {", + " this.particleSystem.worldSpace = !areParticlesRelative;", + " this.updateStartSpeed();", + " this.updateGravity();", + " }", + "", + " /**", + " * @param sprayConeAngle {number}", + " */", + " setSprayConeAngle(sprayConeAngle) {", + " this.sprayConeAngle = sprayConeAngle;", + " this.updateEmitterShape();", + " }", + "", + " /**", + " * @param zoneRadius {number}", + " */", + " setZoneRadius(zoneRadius) {", + " this.zoneRadius = zoneRadius;", + " this.updateEmitterShape();", + " }", + "", + " updateEmitterShape() {", + " // The emitter transformation converts the shape to the Three.js world units.", + " const zoneRadius = Math.max(0, this.zoneRadius);", + " // Without a zone, a spray angle of 0 emits in every direction from a point.", + " // With a zone, it emits straight from every point of the disc.", + " const isCone = this.sprayConeAngle <= 180 && (0 < this.sprayConeAngle || 0 < zoneRadius);", + " if (isCone) {", + " if (this.particleSystem.emitterShape.type !== 'cone') {", + " this.particleSystem.emitterShape = new ConeEmitter({ radius: 0.1, angle: Math.PI / 8 });", + " }", + " this.particleSystem.emitterShape.angle = Math.max(0, this.sprayConeAngle) * Math.PI / 180;", + " this.particleSystem.emitterShape.radius = zoneRadius || 0.1;", + " }", + " else if (this.particleSystem.emitterShape.type !== 'point') {", + " this.particleSystem.emitterShape = new PointEmitter();", + " }", + " }", + "", + " /**", + " * @param blending {string}", + " */", + " setBlending(blendingString) {", + " const blending =", + " blendingString === \"Additive\" ? THREE.AdditiveBlending :", + " blendingString === \"Normal\" ? THREE.NormalBlending :", + " blendingString === \"Subtractive\" ? THREE.SubtractiveBlending :", + " blendingString === \"Multiply\" ? THREE.MultiplyBlending :", + " blendingString === \"None\" ? THREE.NoBlending :", + " THREE.AdditiveBlending;", + " // TODO This doesn't work.", + " this.particleSystem.blending = blending;", + " }", + "", + " /**", + " * @param gravity {number}", + " */", + " setGravity(gravity) {", + " this.gravity = gravity;", + " this.updateGravity();", + " }", + "", + " updateGravity() {", + " this.applyForce.magnitude.value = this.gravity * this.getVelocityScale();", + " }", + "", + " /**", + " * @param gravityTop {string}", + " */", + " setGravityTop(gravityTop) {", + " // TODO Make gravity absolute even when \"relative\" is enabled. ", + " switch (gravityTop) {", + " case \"Z+\":", + " this.applyForce.direction.set(0, 0, -1);", + " break;", + "", + " case \"Y-\":", + " this.applyForce.direction.set(0, 1, 0);", + " break;", + " }", + " }", + "", + " setImage(resourceName) {", + " const texture = game.getImageManager().getThreeTexture(resourceName);", + " this.particleSystem.texture = texture;", + " }", + "}", + "", + "", + "", + "/**", + " * three.quarks v0.11.2 build Mon Jan 22 2024", + " * https://github.com/Alchemist0823/three.quarks#readme", + " * Copyright 2024 Alchemist0823 , MIT", + " */", + "", + "// The migration to addUpdateRange was backported without updating the library", + "// because GDevelop still uses Three.js 0.160.0.", + "", + "class ParticleEmitter extends THREE.Object3D {", + " constructor(system) {", + " super();", + " this.type = 'ParticleEmitter';", + " this.system = system;", + " }", + " clone() {", + " const system = this.system.clone();", + " system.emitter.copy(this, true);", + " return system.emitter;", + " }", + " dispose() { }", + " extractFromCache(cache) {", + " const values = [];", + " for (const key in cache) {", + " const data = cache[key];", + " delete data.metadata;", + " values.push(data);", + " }", + " return values;", + " }", + " toJSON(meta, options = {}) {", + " const isRootObject = meta === undefined || typeof meta === 'string';", + " const output = {};", + " if (isRootObject) {", + " meta = {", + " geometries: {},", + " materials: {},", + " textures: {},", + " images: {},", + " shapes: {},", + " skeletons: {},", + " animations: {},", + " nodes: {},", + " };", + " output.metadata = {", + " version: 4.5,", + " type: 'Object',", + " generator: 'Object3D.toJSON',", + " };", + " }", + " const object = {};", + " object.uuid = this.uuid;", + " object.type = this.type;", + " if (this.name !== '')", + " object.name = this.name;", + " if (this.castShadow === true)", + " object.castShadow = true;", + " if (this.receiveShadow === true)", + " object.receiveShadow = true;", + " if (this.visible === false)", + " object.visible = false;", + " if (this.frustumCulled === false)", + " object.frustumCulled = false;", + " if (this.renderOrder !== 0)", + " object.renderOrder = this.renderOrder;", + " if (JSON.stringify(this.userData) !== '{}')", + " object.userData = this.userData;", + " object.layers = this.layers.mask;", + " object.matrix = this.matrix.toArray();", + " if (this.matrixAutoUpdate === false)", + " object.matrixAutoUpdate = false;", + " if (this.system !== null)", + " object.ps = this.system.toJSON(meta, options);", + " if (this.children.length > 0) {", + " object.children = [];", + " for (let i = 0; i < this.children.length; i++) {", + " if (this.children[i].type !== 'ParticleSystemPreview') {", + " object.children.push(this.children[i].toJSON(meta).object);", + " }", + " }", + " }", + " if (isRootObject) {", + " const geometries = this.extractFromCache(meta.geometries);", + " const materials = this.extractFromCache(meta.materials);", + " const textures = this.extractFromCache(meta.textures);", + " const images = this.extractFromCache(meta.images);", + " if (geometries.length > 0)", + " output.geometries = geometries;", + " if (materials.length > 0)", + " output.materials = materials;", + " if (textures.length > 0)", + " output.textures = textures;", + " if (images.length > 0)", + " output.images = images;", + " }", + " output.object = object;", + " return output;", + " }", + "}", + "", + "class LinkedListNode {", + " constructor(data) {", + " this.data = data;", + " this.next = null;", + " this.prev = null;", + " }", + " hasPrev() {", + " return this.prev !== null;", + " }", + " hasNext() {", + " return this.next !== null;", + " }", + "}", + "class LinkedList {", + " constructor() {", + " this.length = 0;", + " this.head = this.tail = null;", + " }", + " isEmpty() {", + " return this.head === null;", + " }", + " clear() {", + " this.length = 0;", + " this.head = this.tail = null;", + " }", + " front() {", + " if (this.head === null)", + " return null;", + " return this.head.data;", + " }", + " back() {", + " if (this.tail === null)", + " return null;", + " return this.tail.data;", + " }", + " dequeue() {", + " if (this.head) {", + " const value = this.head.data;", + " this.head = this.head.next;", + " if (!this.head) {", + " this.tail = null;", + " }", + " else {", + " this.head.prev = null;", + " }", + " this.length--;", + " return value;", + " }", + " return undefined;", + " }", + " pop() {", + " if (this.tail) {", + " const value = this.tail.data;", + " this.tail = this.tail.prev;", + " if (!this.tail) {", + " this.head = null;", + " }", + " else {", + " this.tail.next = null;", + " }", + " this.length--;", + " return value;", + " }", + " return undefined;", + " }", + " queue(data) {", + " const node = new LinkedListNode(data);", + " if (!this.tail) {", + " this.tail = node;", + " }", + " if (this.head) {", + " this.head.prev = node;", + " node.next = this.head;", + " }", + " this.head = node;", + " this.length++;", + " }", + " push(data) {", + " const node = new LinkedListNode(data);", + " if (!this.head) {", + " this.head = node;", + " }", + " if (this.tail) {", + " this.tail.next = node;", + " node.prev = this.tail;", + " }", + " this.tail = node;", + " this.length++;", + " }", + " insertBefore(node, data) {", + " const newNode = new LinkedListNode(data);", + " newNode.next = node;", + " newNode.prev = node.prev;", + " if (newNode.prev !== null) {", + " newNode.prev.next = newNode;", + " }", + " newNode.next.prev = newNode;", + " if (node == this.head) {", + " this.head = newNode;", + " }", + " this.length++;", + " }", + " remove(data) {", + " if (this.head === null || this.tail === null) {", + " return;", + " }", + " let tempNode = this.head;", + " if (data === this.head.data) {", + " this.head = this.head.next;", + " }", + " if (data === this.tail.data) {", + " this.tail = this.tail.prev;", + " }", + " while (tempNode.next !== null && tempNode.data !== data) {", + " tempNode = tempNode.next;", + " }", + " if (tempNode.data === data) {", + " if (tempNode.prev !== null)", + " tempNode.prev.next = tempNode.next;", + " if (tempNode.next !== null)", + " tempNode.next.prev = tempNode.prev;", + " this.length--;", + " }", + " }", + " *values() {", + " let current = this.head;", + " while (current !== null) {", + " yield current.data;", + " current = current.next;", + " }", + " }", + "}", + "", + "class NodeParticle {", + " constructor() {", + " this.position = new THREE.Vector3();", + " this.velocity = new THREE.Vector3();", + " this.age = 0;", + " this.life = 1;", + " this.size = 1;", + " this.rotation = 0;", + " this.color = new THREE.Vector4(1, 1, 1, 1);", + " this.uvTile = 0;", + " }", + " get died() {", + " return this.age >= this.life;", + " }", + " reset() {", + " this.position.set(0, 0, 0);", + " this.velocity.set(0, 0, 0);", + " this.age = 0;", + " this.life = 1;", + " this.size = 1;", + " this.rotation = 0;", + " this.color.set(1, 1, 1, 1);", + " this.uvTile = 0;", + " }", + "}", + "class SpriteParticle {", + " constructor() {", + " this.startSpeed = 0;", + " this.startColor = new THREE.Vector4();", + " this.startSize = 1;", + " this.position = new THREE.Vector3();", + " this.velocity = new THREE.Vector3();", + " this.age = 0;", + " this.life = 1;", + " this.size = 1;", + " this.speedModifier = 1;", + " this.rotation = 0;", + " this.color = new THREE.Vector4();", + " this.uvTile = 0;", + " }", + " get died() {", + " return this.age >= this.life;", + " }", + "}", + "class RecordState {", + " constructor(position, size, color) {", + " this.position = position;", + " this.size = size;", + " this.color = color;", + " }", + "}", + "class TrailParticle {", + " constructor() {", + " this.startSpeed = 0;", + " this.startColor = new THREE.Vector4();", + " this.startSize = 1;", + " this.position = new THREE.Vector3();", + " this.velocity = new THREE.Vector3();", + " this.age = 0;", + " this.life = 1;", + " this.size = 1;", + " this.length = 100;", + " this.speedModifier = 1;", + " this.color = new THREE.Vector4();", + " this.previous = new LinkedList();", + " this.uvTile = 0;", + " }", + " update() {", + " if (this.age <= this.life) {", + " this.previous.push(new RecordState(this.position.clone(), this.size, this.color.clone()));", + " }", + " else {", + " if (this.previous.length > 0) {", + " this.previous.dequeue();", + " }", + " }", + " while (this.previous.length > this.length) {", + " this.previous.dequeue();", + " }", + " }", + " get died() {", + " return this.age >= this.life;", + " }", + " reset() {", + " this.previous.clear();", + " }", + "}", + "", + "class Bezier {", + " constructor(p1, p2, p3, p4) {", + " this.p = [p1, p2, p3, p4];", + " }", + " genValue(t) {", + " const t2 = t * t;", + " const t3 = t * t * t;", + " const mt = 1 - t;", + " const mt2 = mt * mt;", + " const mt3 = mt2 * mt;", + " return this.p[0] * mt3 + this.p[1] * mt2 * t * 3 + this.p[2] * mt * t2 * 3 + this.p[3] * t3;", + " }", + " derivativeCoefficients(points) {", + " const dpoints = [];", + " for (let p = points, c = p.length - 1; c > 0; c--) {", + " const list = [];", + " for (let j = 0; j < c; j++) {", + " const dpt = c * (p[j + 1] - p[j]);", + " list.push(dpt);", + " }", + " dpoints.push(list);", + " p = list;", + " }", + " return dpoints;", + " }", + " getSlope(t) {", + " const p = this.derivativeCoefficients(this.p)[0];", + " const mt = 1 - t;", + " const a = mt * mt;", + " const b = mt * t * 2;", + " const c = t * t;", + " return a * p[0] + b * p[1] + c * p[2];", + " }", + " controlCurve(d0, d1) {", + " this.p[1] = d0 / 3 + this.p[0];", + " this.p[2] = this.p[3] - d1 / 3;", + " }", + " hull(t) {", + " let p = this.p;", + " let _p = [], pt, idx = 0, i = 0, l = 0;", + " const q = [];", + " q[idx++] = p[0];", + " q[idx++] = p[1];", + " q[idx++] = p[2];", + " q[idx++] = p[3];", + " while (p.length > 1) {", + " _p = [];", + " for (i = 0, l = p.length - 1; i < l; i++) {", + " pt = t * p[i] + (1 - t) * p[i + 1];", + " q[idx++] = pt;", + " _p.push(pt);", + " }", + " p = _p;", + " }", + " return q;", + " }", + " split(t) {", + " const q = this.hull(t);", + " const result = {", + " left: new Bezier(q[0], q[4], q[7], q[9]),", + " right: new Bezier(q[9], q[8], q[6], q[3]),", + " span: q", + " };", + " return result;", + " }", + " clone() {", + " return new Bezier(this.p[0], this.p[1], this.p[2], this.p[3]);", + " }", + " toJSON() {", + " return {", + " p0: this.p[0],", + " p1: this.p[1],", + " p2: this.p[2],", + " p3: this.p[3],", + " };", + " }", + " static fromJSON(json) {", + " return new Bezier(json.p0, json.p1, json.p2, json.p3);", + " }", + "}", + "", + "const ColorToJSON = (color) => {", + " return { r: color.x, g: color.y, b: color.z, a: color.w };", + "};", + "const JSONToColor = (json) => {", + " return new THREE.Vector4(json.r, json.g, json.b, json.a);", + "};", + "const JSONToValue = (json, type) => {", + " switch (type) {", + " case 'Vector3':", + " return new THREE.Vector3(json.x, json.y, json.z);", + " case 'Vector4':", + " return new THREE.Vector4(json.x, json.y, json.z, json.w);", + " case 'Color':", + " return new THREE.Vector3(json.r, json.g, json.b);", + " case 'Number':", + " return json;", + " default:", + " return json;", + " }", + "};", + "const ValueToJSON = (value, type) => {", + " switch (type) {", + " case 'Vector3':", + " return { x: value.x, y: value.y, z: value.z };", + " case 'Vector4':", + " return { x: value.x, y: value.y, z: value.z, w: value.w };", + " case 'Color':", + " return { r: value.x, g: value.y, b: value.z };", + " case 'Number':", + " return value;", + " default:", + " return value;", + " }", + "};", + "", + "class RandomColor {", + " constructor(a, b) {", + " this.a = a;", + " this.b = b;", + " this.type = \"value\";", + " }", + " genColor(color) {", + " const rand = Math.random();", + " return color.copy(this.a).lerp(this.b, rand);", + " }", + " toJSON() {", + " return {", + " type: \"RandomColor\",", + " a: ColorToJSON(this.a),", + " b: ColorToJSON(this.b),", + " };", + " }", + " static fromJSON(json) {", + " return new RandomColor(JSONToColor(json.a), JSONToColor(json.b));", + " }", + " clone() {", + " return new RandomColor(this.a.clone(), this.b.clone());", + " }", + "}", + "", + "class ColorRange {", + " constructor(a, b) {", + " this.a = a;", + " this.b = b;", + " this.type = 'value';", + " }", + " genColor(color, t) {", + " return color.copy(this.a).lerp(this.b, Math.random());", + " }", + " toJSON() {", + " return {", + " type: 'ColorRange',", + " a: ColorToJSON(this.a),", + " b: ColorToJSON(this.b),", + " };", + " }", + " static fromJSON(json) {", + " return new ColorRange(JSONToColor(json.a), JSONToColor(json.b));", + " }", + " clone() {", + " return new ColorRange(this.a.clone(), this.b.clone());", + " }", + "}", + "", + "class ContinuousLinearFunction {", + " constructor(keys, subType) {", + " this.subType = subType;", + " this.type = 'function';", + " this.keys = keys;", + " }", + " findKey(t) {", + " let mid = 0;", + " let left = 0, right = this.keys.length - 1;", + " while (left + 1 < right) {", + " mid = Math.floor((left + right) / 2);", + " if (t < this.getStartX(mid))", + " right = mid - 1;", + " else if (t > this.getEndX(mid))", + " left = mid + 1;", + " else", + " return mid;", + " }", + " for (let i = left; i <= right; i++) {", + " if (t >= this.getStartX(i) && t <= this.getEndX(i))", + " return i;", + " }", + " return -1;", + " }", + " getStartX(index) {", + " return this.keys[index][1];", + " }", + " getEndX(index) {", + " if (index + 1 < this.keys.length)", + " return this.keys[index + 1][1];", + " return 1;", + " }", + " genValue(value, t) {", + " const index = this.findKey(t);", + " if (this.subType === 'Number') {", + " if (index === -1) {", + " return this.keys[0][0];", + " }", + " else if (index + 1 >= this.keys.length) {", + " return this.keys[this.keys.length - 1][0];", + " }", + " return ((this.keys[index + 1][0] - this.keys[index][0]) *", + " ((t - this.getStartX(index)) / (this.getEndX(index) - this.getStartX(index))) +", + " this.keys[index][0]);", + " }", + " else {", + " if (index === -1) {", + " return value.copy(this.keys[0][0]);", + " }", + " if (index + 1 >= this.keys.length) {", + " return value.copy(this.keys[this.keys.length - 1][0]);", + " }", + " return value", + " .copy(this.keys[index][0])", + " .lerp(this.keys[index + 1][0], (t - this.getStartX(index)) / (this.getEndX(index) - this.getStartX(index)));", + " }", + " }", + " toJSON() {", + " this.keys[0][0].constructor.name;", + " return {", + " type: 'CLinearFunction',", + " subType: this.subType,", + " keys: this.keys.map(([color, pos]) => ({ value: ValueToJSON(color, this.subType), pos: pos })),", + " };", + " }", + " static fromJSON(json) {", + " return new ContinuousLinearFunction(json.keys.map((pair) => [JSONToValue(pair.value, json.subType), pair.pos]), json.subType);", + " }", + " clone() {", + " if (this.subType === 'Number') {", + " return new ContinuousLinearFunction(this.keys.map(([value, pos]) => [value, pos]), this.subType);", + " }", + " else {", + " return new ContinuousLinearFunction(this.keys.map(([value, pos]) => [value.clone(), pos]), this.subType);", + " }", + " }", + "}", + "", + "const tempVec3 = new THREE.Vector3();", + "class Gradient {", + " constructor(color = [", + " [new THREE.Vector3(0, 0, 0), 0],", + " [new THREE.Vector3(1, 1, 1), 0],", + " ], alpha = [", + " [1, 0],", + " [1, 1],", + " ]) {", + " this.type = 'function';", + " this.color = new ContinuousLinearFunction(color, 'Color');", + " this.alpha = new ContinuousLinearFunction(alpha, 'Number');", + " }", + " genColor(color, t) {", + " this.color.genValue(tempVec3, t);", + " return color.set(tempVec3.x, tempVec3.y, tempVec3.z, this.alpha.genValue(1, t));", + " }", + " toJSON() {", + " return {", + " type: 'Gradient',", + " color: this.color.toJSON(),", + " alpha: this.alpha.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " if (json.functions) {", + " const keys = json.functions.map((func) => [ColorRange.fromJSON(func.function).a, func.start]);", + " if (json.functions.length > 0) {", + " keys.push([ColorRange.fromJSON(json.functions[json.functions.length - 1].function).b, 1]);", + " }", + " return new Gradient(keys.map((key) => [new THREE.Vector3(key[0].x, key[0].y, key[0].z), key[1]]), keys.map((key) => [key[0].w, key[1]]));", + " }", + " else {", + " const gradient = new Gradient();", + " gradient.alpha = ContinuousLinearFunction.fromJSON(json.alpha);", + " gradient.color = ContinuousLinearFunction.fromJSON(json.color);", + " return gradient;", + " }", + " }", + " clone() {", + " const gradient = new Gradient();", + " gradient.alpha = this.alpha.clone();", + " gradient.color = this.color.clone();", + " return gradient;", + " }", + "}", + "", + "const tempColor = new THREE.Vector4();", + "class RandomColorBetweenGradient {", + " constructor(gradient1, gradient2) {", + " this.type = 'memorizedFunction';", + " this.gradient1 = gradient1;", + " this.gradient2 = gradient2;", + " }", + " startGen(memory) {", + " memory.rand = Math.random();", + " }", + " genColor(color, t, memory) {", + " this.gradient1.genColor(color, t);", + " this.gradient2.genColor(tempColor, t);", + " if (memory && memory.rand) {", + " color.lerp(tempColor, memory.rand);", + " }", + " else {", + " color.lerp(tempColor, Math.random());", + " }", + " return color;", + " }", + " toJSON() {", + " return {", + " type: 'RandomColorBetweenGradient',", + " gradient1: this.gradient1.toJSON(),", + " gradient2: this.gradient2.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " return new RandomColorBetweenGradient(Gradient.fromJSON(json.gradient1), Gradient.fromJSON(json.gradient2));", + " }", + " clone() {", + " return new RandomColorBetweenGradient(this.gradient1.clone(), this.gradient2.clone());", + " }", + "}", + "", + "class ConstantColor {", + " constructor(color) {", + " this.color = color;", + " this.type = 'value';", + " }", + " genColor(color) {", + " return color.copy(this.color);", + " }", + " toJSON() {", + " return {", + " type: 'ConstantColor',", + " color: ColorToJSON(this.color),", + " };", + " }", + " static fromJSON(json) {", + " return new ConstantColor(JSONToColor(json.color));", + " }", + " clone() {", + " return new ConstantColor(this.color.clone());", + " }", + "}", + "function ColorGeneratorFromJSON(json) {", + " switch (json.type) {", + " case 'ConstantColor':", + " return ConstantColor.fromJSON(json);", + " case 'ColorRange':", + " return ColorRange.fromJSON(json);", + " case 'RandomColor':", + " return RandomColor.fromJSON(json);", + " case 'Gradient':", + " return Gradient.fromJSON(json);", + " case 'RandomColorBetweenGradient':", + " return RandomColorBetweenGradient.fromJSON(json);", + " default:", + " return new ConstantColor(new THREE.Vector4(1, 1, 1, 1));", + " }", + "}", + "", + "class ConstantValue {", + " constructor(value) {", + " this.value = value;", + " this.type = 'value';", + " }", + " genValue() {", + " return this.value;", + " }", + " toJSON() {", + " return {", + " type: \"ConstantValue\",", + " value: this.value", + " };", + " }", + " static fromJSON(json) {", + " return new ConstantValue(json.value);", + " }", + " clone() {", + " return new ConstantValue(this.value);", + " }", + "}", + "", + "class IntervalValue {", + " constructor(a, b) {", + " this.a = a;", + " this.b = b;", + " this.type = 'value';", + " }", + " genValue() {", + " return THREE.MathUtils.lerp(this.a, this.b, Math.random());", + " }", + " toJSON() {", + " return {", + " type: 'IntervalValue',", + " a: this.a,", + " b: this.b,", + " };", + " }", + " static fromJSON(json) {", + " return new IntervalValue(json.a, json.b);", + " }", + " clone() {", + " return new IntervalValue(this.a, this.b);", + " }", + "}", + "", + "class PiecewiseFunction {", + " constructor() {", + " this.functions = new Array();", + " }", + " findFunction(t) {", + " let mid = 0;", + " let left = 0, right = this.functions.length - 1;", + " while (left + 1 < right) {", + " mid = Math.floor((left + right) / 2);", + " if (t < this.getStartX(mid))", + " right = mid - 1;", + " else if (t > this.getEndX(mid))", + " left = mid + 1;", + " else", + " return mid;", + " }", + " for (let i = left; i <= right; i++) {", + " if (t >= this.functions[i][1] && t <= this.getEndX(i))", + " return i;", + " }", + " return -1;", + " }", + " getStartX(index) {", + " return this.functions[index][1];", + " }", + " setStartX(index, x) {", + " if (index > 0)", + " this.functions[index][1] = x;", + " }", + " getEndX(index) {", + " if (index + 1 < this.functions.length)", + " return this.functions[index + 1][1];", + " return 1;", + " }", + " setEndX(index, x) {", + " if (index + 1 < this.functions.length)", + " this.functions[index + 1][1] = x;", + " }", + " insertFunction(t, func) {", + " const index = this.findFunction(t);", + " this.functions.splice(index + 1, 0, [func, t]);", + " }", + " removeFunction(index) {", + " return this.functions.splice(index, 1)[0][0];", + " }", + " getFunction(index) {", + " return this.functions[index][0];", + " }", + " setFunction(index, func) {", + " this.functions[index][0] = func;", + " }", + " get numOfFunctions() {", + " return this.functions.length;", + " }", + "}", + "", + "class PiecewiseBezier extends PiecewiseFunction {", + " constructor(curves = [[new Bezier(0, 1.0 / 3, 1.0 / 3 * 2, 1), 0]]) {", + " super();", + " this.type = \"function\";", + " this.functions = curves;", + " }", + " genValue(t = 0) {", + " const index = this.findFunction(t);", + " if (index === -1) {", + " return 0;", + " }", + " return this.functions[index][0].genValue((t - this.getStartX(index)) / (this.getEndX(index) - this.getStartX(index)));", + " }", + " toSVG(length, segments) {", + " if (segments < 1)", + " return \"\";", + " let result = [\"M\", 0, this.functions[0][0].p[0]].join(\" \");", + " for (let i = 1.0 / segments; i <= 1; i += 1.0 / segments) {", + " result = [result, \"L\", i * length, this.genValue(i)].join(\" \");", + " }", + " return result;", + " }", + " toJSON() {", + " return {", + " type: \"PiecewiseBezier\",", + " functions: this.functions.map(([bezier, start]) => ({ function: bezier.toJSON(), start: start })),", + " };", + " }", + " static fromJSON(json) {", + " return new PiecewiseBezier(json.functions.map((piecewiseFunction) => ([Bezier.fromJSON(piecewiseFunction.function), piecewiseFunction.start])));", + " }", + " clone() {", + " return new PiecewiseBezier(this.functions.map(([bezier, start]) => ([bezier.clone(), start])));", + " }", + "}", + "", + "function ValueGeneratorFromJSON(json) {", + " switch (json.type) {", + " case 'ConstantValue':", + " return ConstantValue.fromJSON(json);", + " case 'IntervalValue':", + " return IntervalValue.fromJSON(json);", + " case 'PiecewiseBezier':", + " return PiecewiseBezier.fromJSON(json);", + " default:", + " return new ConstantValue(0);", + " }", + "}", + "", + "class RandomQuatGenerator {", + " constructor() {", + " this.type = \"rotation\";", + " }", + " genValue(quat, t) {", + " let x, y, z, u, v, w;", + " do {", + " x = Math.random() * 2 - 1;", + " y = Math.random() * 2 - 1;", + " z = x * x + y * y;", + " } while (z > 1);", + " do {", + " u = Math.random() * 2 - 1;", + " v = Math.random() * 2 - 1;", + " w = u * u + v * v;", + " } while (w > 1);", + " const s = Math.sqrt((1 - z) / w);", + " quat.set(x, y, s * u, s * v);", + " return quat;", + " }", + " toJSON() {", + " return {", + " type: \"RandomQuat\"", + " };", + " }", + " static fromJSON(json) {", + " return new RandomQuatGenerator();", + " }", + " clone() {", + " return new RandomQuatGenerator();", + " }", + "}", + "", + "class AxisAngleGenerator {", + " constructor(axis, angle) {", + " this.axis = axis;", + " this.angle = angle;", + " this.type = 'rotation';", + " }", + " genValue(quat, t) {", + " return quat.setFromAxisAngle(this.axis, this.angle.genValue(t));", + " }", + " toJSON() {", + " return {", + " type: 'AxisAngle',", + " axis: { x: this.axis.x, y: this.axis.y, z: this.axis.z },", + " angle: this.angle.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " return new AxisAngleGenerator(new THREE.Vector3(json.axis.x, json.axis.y, json.axis.z), ValueGeneratorFromJSON(json.angle));", + " }", + " clone() {", + " return new AxisAngleGenerator(this.axis.clone(), this.angle.clone());", + " }", + "}", + "", + "class EulerGenerator {", + " constructor(angleX, angleY, angleZ, eulerOrder) {", + " this.angleX = angleX;", + " this.angleY = angleY;", + " this.angleZ = angleZ;", + " this.type = 'rotation';", + " this.eular = new THREE.Euler(0, 0, 0, eulerOrder);", + " }", + " genValue(quat, t) {", + " this.eular.set(this.angleX.genValue(t), this.angleY.genValue(t), this.angleZ.genValue(t));", + " return quat.setFromEuler(this.eular);", + " }", + " toJSON() {", + " return {", + " type: 'Euler',", + " angleX: this.angleX.toJSON(),", + " angleY: this.angleY.toJSON(),", + " angleZ: this.angleZ.toJSON(),", + " eulerOrder: this.eular.order,", + " };", + " }", + " static fromJSON(json) {", + " return new EulerGenerator(ValueGeneratorFromJSON(json.angleX), ValueGeneratorFromJSON(json.angleY), ValueGeneratorFromJSON(json.angleZ), json.eulerOrder);", + " }", + " clone() {", + " return new EulerGenerator(this.angleX, this.angleY, this.angleZ, this.eular.order);", + " }", + "}", + "", + "function RotationGeneratorFromJSON(json) {", + " switch (json.type) {", + " case 'AxisAngle':", + " return AxisAngleGenerator.fromJSON(json);", + " case 'Euler':", + " return EulerGenerator.fromJSON(json);", + " case 'RandomQuat':", + " return RandomQuatGenerator.fromJSON(json);", + " default:", + " return new RandomQuatGenerator();", + " }", + "}", + "", + "function GeneratorFromJSON(json) {", + " switch (json.type) {", + " case 'ConstantValue':", + " case 'IntervalValue':", + " case 'PiecewiseBezier':", + " return ValueGeneratorFromJSON(json);", + " case 'AxisAngle':", + " case 'RandomQuat':", + " case 'Euler':", + " return RotationGeneratorFromJSON(json);", + " default:", + " return new ConstantValue(0);", + " }", + "}", + "", + "class ColorOverLife {", + " constructor(color) {", + " this.color = color;", + " this.type = 'ColorOverLife';", + " }", + " initialize(particle) {", + " if (this.color.type === 'memorizedFunction') {", + " particle.colorOverLifeMemory = {};", + " this.color.startGen(particle.colorOverLifeMemory);", + " }", + " }", + " update(particle, delta) {", + " if (this.color.type === 'memorizedFunction') {", + " this.color.genColor(particle.color, particle.age / particle.life, particle.colorOverLifeMemory);", + " }", + " else {", + " this.color.genColor(particle.color, particle.age / particle.life);", + " }", + " particle.color.x *= particle.startColor.x;", + " particle.color.y *= particle.startColor.y;", + " particle.color.z *= particle.startColor.z;", + " particle.color.w *= particle.startColor.w;", + " }", + " frameUpdate(delta) { }", + " toJSON() {", + " return {", + " type: this.type,", + " color: this.color.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " return new ColorOverLife(ColorGeneratorFromJSON(json.color));", + " }", + " clone() {", + " return new ColorOverLife(this.color.clone());", + " }", + " reset() { }", + "}", + "", + "class RotationOverLife {", + " constructor(angularVelocity) {", + " this.angularVelocity = angularVelocity;", + " this.type = 'RotationOverLife';", + " this.dynamic = !(angularVelocity instanceof ConstantValue || angularVelocity instanceof IntervalValue);", + " }", + " initialize(particle) {", + " this.dynamic = !(this.angularVelocity instanceof ConstantValue || this.angularVelocity instanceof IntervalValue);", + " if (!this.dynamic && typeof particle.rotation === 'number') {", + " particle.angularVelocity = this.angularVelocity.genValue();", + " }", + " }", + " update(particle, delta) {", + " if (!this.dynamic) {", + " if (typeof particle.rotation === 'number') {", + " particle.rotation += delta * particle.angularVelocity;", + " }", + " }", + " else {", + " if (typeof particle.rotation === 'number') {", + " particle.rotation +=", + " delta * this.angularVelocity.genValue(particle.age / particle.life);", + " }", + " }", + " }", + " toJSON() {", + " return {", + " type: this.type,", + " angularVelocity: this.angularVelocity.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " return new RotationOverLife(ValueGeneratorFromJSON(json.angularVelocity));", + " }", + " frameUpdate(delta) { }", + " clone() {", + " return new RotationOverLife(this.angularVelocity.clone());", + " }", + " reset() { }", + "}", + "", + "const IdentityQuaternion = new THREE.Quaternion();", + "class Rotation3DOverLife {", + " constructor(angularVelocity) {", + " this.angularVelocity = angularVelocity;", + " this.type = 'Rotation3DOverLife';", + " this.tempQuat = new THREE.Quaternion();", + " this.dynamic = !(angularVelocity instanceof RandomQuatGenerator);", + " }", + " initialize(particle) {", + " this.dynamic = !(this.angularVelocity instanceof RandomQuatGenerator);", + " if (particle.rotation instanceof THREE.Quaternion) {", + " particle.angularVelocity = new THREE.Quaternion();", + " this.angularVelocity.genValue(particle.angularVelocity);", + " }", + " }", + " update(particle, delta) {", + " if (particle.rotation instanceof THREE.Quaternion) {", + " if (!this.dynamic) {", + " this.tempQuat.slerpQuaternions(IdentityQuaternion, particle.angularVelocity, delta);", + " particle.rotation.multiply(this.tempQuat);", + " }", + " else {", + " this.angularVelocity.genValue(this.tempQuat, particle.age / particle.life);", + " this.tempQuat.slerpQuaternions(IdentityQuaternion, this.tempQuat, delta);", + " particle.rotation.multiply(this.tempQuat);", + " }", + " }", + " }", + " toJSON() {", + " return {", + " type: this.type,", + " angularVelocity: this.angularVelocity.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " return new Rotation3DOverLife(RotationGeneratorFromJSON(json.angularVelocity));", + " }", + " frameUpdate(delta) { }", + " clone() {", + " return new Rotation3DOverLife(this.angularVelocity.clone());", + " }", + " reset() { }", + "}", + "", + "class ForceOverLife {", + " initialize(particle) { }", + " constructor(x, y, z) {", + " this.x = x;", + " this.y = y;", + " this.z = z;", + " this.type = 'ForceOverLife';", + " this._temp = new THREE.Vector3();", + " }", + " update(particle, delta) {", + " this._temp.set(this.x.genValue(particle.age / particle.life), this.y.genValue(particle.age / particle.life), this.z.genValue(particle.age / particle.life));", + " particle.velocity.addScaledVector(this._temp, delta);", + " }", + " toJSON() {", + " return {", + " type: this.type,", + " x: this.x.toJSON(),", + " y: this.y.toJSON(),", + " z: this.z.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " return new ForceOverLife(ValueGeneratorFromJSON(json.x), ValueGeneratorFromJSON(json.y), ValueGeneratorFromJSON(json.z));", + " }", + " frameUpdate(delta) { }", + " clone() {", + " return new ForceOverLife(this.x.clone(), this.y.clone(), this.z.clone());", + " }", + " reset() { }", + "}", + "", + "class SizeOverLife {", + " initialize(particle) {", + " }", + " constructor(size) {", + " this.size = size;", + " this.type = 'SizeOverLife';", + " }", + " update(particle) {", + " particle.size = particle.startSize * this.size.genValue(particle.age / particle.life);", + " }", + " toJSON() {", + " return {", + " type: this.type,", + " size: this.size.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " return new SizeOverLife(ValueGeneratorFromJSON(json.size));", + " }", + " frameUpdate(delta) {", + " }", + " clone() {", + " return new SizeOverLife(this.size.clone());", + " }", + " reset() {", + " }", + "}", + "", + "class SpeedOverLife {", + " initialize(particle) {", + " }", + " constructor(speed) {", + " this.speed = speed;", + " this.type = 'SpeedOverLife';", + " }", + " update(particle) {", + " particle.speedModifier = this.speed.genValue(particle.age / particle.life);", + " }", + " toJSON() {", + " return {", + " type: this.type,", + " speed: this.speed.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " return new SpeedOverLife(ValueGeneratorFromJSON(json.speed));", + " }", + " frameUpdate(delta) {", + " }", + " clone() {", + " return new SpeedOverLife(this.speed.clone());", + " }", + " reset() {", + " }", + "}", + "", + "class FrameOverLife {", + " constructor(frame) {", + " this.frame = frame;", + " this.type = 'FrameOverLife';", + " }", + " initialize(particle) {", + " if (!(this.frame instanceof PiecewiseBezier)) {", + " particle.uvTile = Math.floor(this.frame.genValue(0) + 0.001);", + " }", + " }", + " update(particle, delta) {", + " if (this.frame instanceof PiecewiseBezier) {", + " particle.uvTile = Math.floor(this.frame.genValue(particle.age / particle.life) + 0.001);", + " }", + " }", + " frameUpdate(delta) { }", + " toJSON() {", + " return {", + " type: this.type,", + " frame: this.frame.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " return new FrameOverLife(ValueGeneratorFromJSON(json.frame));", + " }", + " clone() {", + " return new FrameOverLife(this.frame.clone());", + " }", + " reset() { }", + "}", + "", + "new THREE.Vector3(0, 0, 1);", + "class OrbitOverLife {", + " constructor(orbitSpeed, axis = new THREE.Vector3(0, 1, 0)) {", + " this.orbitSpeed = orbitSpeed;", + " this.axis = axis;", + " this.type = 'OrbitOverLife';", + " this.temp = new THREE.Vector3();", + " this.rotation = new THREE.Quaternion();", + " this.line = new THREE.Line3();", + " }", + " initialize(particle) {", + " }", + " update(particle, delta) {", + " this.line.set(new THREE.Vector3(0, 0, 0), this.axis);", + " this.line.closestPointToPoint(particle.position, false, this.temp);", + " this.rotation.setFromAxisAngle(this.axis, this.orbitSpeed.genValue(particle.age / particle.life) * delta);", + " particle.position.sub(this.temp);", + " particle.position.applyQuaternion(this.rotation);", + " particle.position.add(this.temp);", + " }", + " frameUpdate(delta) {", + " }", + " toJSON() {", + " return {", + " type: this.type,", + " orbitSpeed: this.orbitSpeed.toJSON(),", + " axis: [this.axis.x, this.axis.y, this.axis.z],", + " };", + " }", + " static fromJSON(json) {", + " return new OrbitOverLife(ValueGeneratorFromJSON(json.orbitSpeed), json.axis ? new THREE.Vector3(json.axis[0], json.axis[1], json.axis[2]) : undefined);", + " }", + " clone() {", + " return new OrbitOverLife(this.orbitSpeed.clone());", + " }", + " reset() {", + " }", + "}", + "", + "class WidthOverLength {", + " initialize(particle) {", + " }", + " constructor(width) {", + " this.width = width;", + " this.type = 'WidthOverLength';", + " }", + " update(particle) {", + " if (particle instanceof TrailParticle) {", + " const iter = particle.previous.values();", + " for (let i = 0; i < particle.previous.length; i++) {", + " const cur = iter.next();", + " // We added a multiplication with size because it's more convenient for the extension.", + " cur.value.size = this.width.genValue((particle.previous.length - i) / particle.length) * particle.size;", + " }", + " }", + " }", + " frameUpdate(delta) {", + " }", + " toJSON() {", + " return {", + " type: this.type,", + " width: this.width.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " return new WidthOverLength(ValueGeneratorFromJSON(json.width));", + " }", + " clone() {", + " return new WidthOverLength(this.width.clone());", + " }", + " reset() {", + " }", + "}", + "", + "class ApplyForce {", + " constructor(direction, magnitude) {", + " this.direction = direction;", + " this.magnitude = magnitude;", + " this.type = 'ApplyForce';", + " this.magnitudeValue = this.magnitude.genValue();", + " }", + " initialize(particle) {", + " }", + " update(particle, delta) {", + " particle.velocity.addScaledVector(this.direction, this.magnitudeValue * delta);", + " }", + " frameUpdate(delta) {", + " this.magnitudeValue = this.magnitude.genValue();", + " }", + " toJSON() {", + " return {", + " type: this.type,", + " direction: [this.direction.x, this.direction.y, this.direction.z],", + " magnitude: this.magnitude.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " var _a;", + " return new ApplyForce(new THREE.Vector3(json.direction[0], json.direction[1], json.direction[2]), ValueGeneratorFromJSON((_a = json.magnitude) !== null && _a !== void 0 ? _a : json.force));", + " }", + " clone() {", + " return new ApplyForce(this.direction.clone(), this.magnitude.clone());", + " }", + " reset() {", + " }", + "}", + "", + "class GravityForce {", + " constructor(center, magnitude) {", + " this.center = center;", + " this.magnitude = magnitude;", + " this.type = 'GravityForce';", + " this.temp = new THREE.Vector3();", + " }", + " initialize(particle) {", + " }", + " update(particle, delta) {", + " this.temp.copy(this.center).sub(particle.position).normalize();", + " particle.velocity.addScaledVector(this.temp, this.magnitude / particle.position.distanceToSquared(this.center) * delta);", + " }", + " frameUpdate(delta) {", + " }", + " toJSON() {", + " return {", + " type: this.type,", + " center: [this.center.x, this.center.y, this.center.z],", + " magnitude: this.magnitude,", + " };", + " }", + " static fromJSON(json) {", + " return new GravityForce(new THREE.Vector3(json.center[0], json.center[1], json.center[2]), json.magnitude);", + " }", + " clone() {", + " return new GravityForce(this.center.clone(), this.magnitude);", + " }", + " reset() {", + " }", + "}", + "", + "new THREE.Vector3(0, 0, 1);", + "class ChangeEmitDirection {", + " constructor(angle) {", + " this.angle = angle;", + " this.type = 'ChangeEmitDirection';", + " this._temp = new THREE.Vector3();", + " this._q = new THREE.Quaternion();", + " }", + " initialize(particle) {", + " const len = particle.velocity.length();", + " if (len == 0)", + " return;", + " particle.velocity.normalize();", + " if (particle.velocity.x === 0 && particle.velocity.y === 0) {", + " this._temp.set(0, particle.velocity.z, 0);", + " }", + " else {", + " this._temp.set(-particle.velocity.y, particle.velocity.x, 0);", + " }", + " this._q.setFromAxisAngle(this._temp.normalize(), this.angle.genValue());", + " this._temp.copy(particle.velocity);", + " particle.velocity.applyQuaternion(this._q);", + " this._q.setFromAxisAngle(this._temp, Math.random() * Math.PI * 2);", + " particle.velocity.applyQuaternion(this._q);", + " particle.velocity.setLength(len);", + " }", + " update(particle, delta) {", + " }", + " frameUpdate(delta) {", + " }", + " toJSON() {", + " return {", + " type: this.type,", + " angle: this.angle.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " return new ChangeEmitDirection(ValueGeneratorFromJSON(json.angle));", + " }", + " clone() {", + " return new ChangeEmitDirection(this.angle);", + " }", + " reset() {", + " }", + "}", + "", + "const VECTOR_ONE = new THREE.Vector3(1, 1, 1);", + "const VECTOR_Z = new THREE.Vector3(0, 0, 1);", + "let SubParticleEmitMode = void 0;", + "(function (SubParticleEmitMode) {", + " SubParticleEmitMode[SubParticleEmitMode[\"Death\"] = 0] = \"Death\";", + " SubParticleEmitMode[SubParticleEmitMode[\"Birth\"] = 1] = \"Birth\";", + " SubParticleEmitMode[SubParticleEmitMode[\"Frame\"] = 2] = \"Frame\";", + "})(SubParticleEmitMode || (SubParticleEmitMode = {}));", + "class EmitSubParticleSystem {", + " constructor(particleSystem, useVelocityAsBasis, subParticleSystem, mode = SubParticleEmitMode.Frame, emitProbability = 1) {", + " this.particleSystem = particleSystem;", + " this.useVelocityAsBasis = useVelocityAsBasis;", + " this.subParticleSystem = subParticleSystem;", + " this.mode = mode;", + " this.emitProbability = emitProbability;", + " this.type = 'EmitSubParticleSystem';", + " this.q_ = new THREE.Quaternion();", + " this.v_ = new THREE.Vector3();", + " this.v2_ = new THREE.Vector3();", + " this.subEmissions = new Array();", + " if (this.subParticleSystem && this.subParticleSystem.system) {", + " this.subParticleSystem.system.onlyUsedByOther = true;", + " }", + " }", + " initialize(particle) {", + " }", + " update(particle, delta) {", + " if (this.mode === SubParticleEmitMode.Frame) {", + " this.emit(particle, delta);", + " }", + " else if (this.mode === SubParticleEmitMode.Birth && particle.age === 0) {", + " this.emit(particle, delta);", + " }", + " else if (this.mode === SubParticleEmitMode.Death && particle.age + delta >= particle.life) {", + " this.emit(particle, delta);", + " }", + " }", + " emit(particle, delta) {", + " if (!this.subParticleSystem)", + " return;", + " if (Math.random() > this.emitProbability) {", + " return;", + " }", + " const m = new THREE.Matrix4();", + " this.setMatrixFromParticle(m, particle);", + " this.subEmissions.push({", + " burstIndex: 0,", + " burstWaveIndex: 0,", + " time: 0,", + " waitEmiting: 0,", + " matrix: m,", + " travelDistance: 0,", + " particle: particle,", + " });", + " }", + " frameUpdate(delta) {", + " if (!this.subParticleSystem)", + " return;", + " for (let i = 0; i < this.subEmissions.length; i++) {", + " if (this.subEmissions[i].time >= this.subParticleSystem.system.duration) {", + " this.subEmissions[i] = this.subEmissions[this.subEmissions.length - 1];", + " this.subEmissions.length = this.subEmissions.length - 1;", + " i--;", + " }", + " else {", + " let subEmissionState = this.subEmissions[i];", + " if (subEmissionState.particle && subEmissionState.particle.age < subEmissionState.particle.life) {", + " this.setMatrixFromParticle(subEmissionState.matrix, subEmissionState.particle);", + " }", + " else {", + " subEmissionState.particle = undefined;", + " }", + " this.subParticleSystem.system.emit(delta, subEmissionState, subEmissionState.matrix);", + " }", + " }", + " }", + " toJSON() {", + " return {", + " type: this.type,", + " subParticleSystem: this.subParticleSystem ? this.subParticleSystem.uuid : '',", + " useVelocityAsBasis: this.useVelocityAsBasis,", + " mode: this.mode,", + " emitProbability: this.emitProbability,", + " };", + " }", + " static fromJSON(json, particleSystem) {", + " return new EmitSubParticleSystem(particleSystem, json.useVelocityAsBasis, json.subParticleSystem, json.mode, json.emitProbability);", + " }", + " clone() {", + " return new EmitSubParticleSystem(this.particleSystem, this.useVelocityAsBasis, this.subParticleSystem, this.mode, this.emitProbability);", + " }", + " reset() { }", + " setMatrixFromParticle(m, particle) {", + " let rotation;", + " if (particle.rotation === undefined || this.useVelocityAsBasis) {", + " if (particle.velocity.x === 0 &&", + " particle.velocity.y === 0 &&", + " (particle.velocity.z === 1 || particle.velocity.z === 0)) {", + " m.set(1, 0, 0, particle.position.x, 0, 1, 0, particle.position.y, 0, 0, 1, particle.position.z, 0, 0, 0, 1);", + " }", + " else {", + " this.v_.copy(VECTOR_Z).cross(particle.velocity);", + " this.v2_.copy(particle.velocity).cross(this.v_);", + " const len = this.v_.length();", + " const len2 = this.v2_.length();", + " m.set(this.v_.x / len, this.v2_.x / len2, particle.velocity.x, particle.position.x, this.v_.y / len, this.v2_.y / len2, particle.velocity.y, particle.position.y, this.v_.z / len, this.v2_.z / len2, particle.velocity.z, particle.position.z, 0, 0, 0, 1);", + " }", + " }", + " else {", + " if (particle.rotation instanceof THREE.Quaternion) {", + " rotation = particle.rotation;", + " }", + " else {", + " this.q_.setFromAxisAngle(VECTOR_Z, particle.rotation);", + " rotation = this.q_;", + " }", + " m.compose(particle.position, rotation, VECTOR_ONE);", + " }", + " if (!this.particleSystem.worldSpace) {", + " m.multiplyMatrices(this.particleSystem.emitter.matrixWorld, m);", + " }", + " }", + "}", + "", + "const F2 = 0.5 * (Math.sqrt(3.0) - 1.0);", + "const G2 = (3.0 - Math.sqrt(3.0)) / 6.0;", + "const F3 = 1.0 / 3.0;", + "const G3 = 1.0 / 6.0;", + "const F4 = (Math.sqrt(5.0) - 1.0) / 4.0;", + "const G4 = (5.0 - Math.sqrt(5.0)) / 20.0;", + "const grad3 = new Float32Array([1, 1, 0,", + " -1, 1, 0,", + " 1, -1, 0,", + " -1, -1, 0,", + " 1, 0, 1,", + " -1, 0, 1,", + " 1, 0, -1,", + " -1, 0, -1,", + " 0, 1, 1,", + " 0, -1, 1,", + " 0, 1, -1,", + " 0, -1, -1]);", + "const grad4 = new Float32Array([0, 1, 1, 1, 0, 1, 1, -1, 0, 1, -1, 1, 0, 1, -1, -1,", + " 0, -1, 1, 1, 0, -1, 1, -1, 0, -1, -1, 1, 0, -1, -1, -1,", + " 1, 0, 1, 1, 1, 0, 1, -1, 1, 0, -1, 1, 1, 0, -1, -1,", + " -1, 0, 1, 1, -1, 0, 1, -1, -1, 0, -1, 1, -1, 0, -1, -1,", + " 1, 1, 0, 1, 1, 1, 0, -1, 1, -1, 0, 1, 1, -1, 0, -1,", + " -1, 1, 0, 1, -1, 1, 0, -1, -1, -1, 0, 1, -1, -1, 0, -1,", + " 1, 1, 1, 0, 1, 1, -1, 0, 1, -1, 1, 0, 1, -1, -1, 0,", + " -1, 1, 1, 0, -1, 1, -1, 0, -1, -1, 1, 0, -1, -1, -1, 0]);", + "class SimplexNoise {", + " constructor(randomOrSeed = Math.random) {", + " const random = typeof randomOrSeed == 'function' ? randomOrSeed : alea(randomOrSeed);", + " this.p = buildPermutationTable(random);", + " this.perm = new Uint8Array(512);", + " this.permMod12 = new Uint8Array(512);", + " for (let i = 0; i < 512; i++) {", + " this.perm[i] = this.p[i & 255];", + " this.permMod12[i] = this.perm[i] % 12;", + " }", + " }", + " noise2D(x, y) {", + " const permMod12 = this.permMod12;", + " const perm = this.perm;", + " let n0 = 0;", + " let n1 = 0;", + " let n2 = 0;", + " const s = (x + y) * F2;", + " const i = Math.floor(x + s);", + " const j = Math.floor(y + s);", + " const t = (i + j) * G2;", + " const X0 = i - t;", + " const Y0 = j - t;", + " const x0 = x - X0;", + " const y0 = y - Y0;", + " let i1, j1;", + " if (x0 > y0) {", + " i1 = 1;", + " j1 = 0;", + " }", + " else {", + " i1 = 0;", + " j1 = 1;", + " }", + " const x1 = x0 - i1 + G2;", + " const y1 = y0 - j1 + G2;", + " const x2 = x0 - 1.0 + 2.0 * G2;", + " const y2 = y0 - 1.0 + 2.0 * G2;", + " const ii = i & 255;", + " const jj = j & 255;", + " let t0 = 0.5 - x0 * x0 - y0 * y0;", + " if (t0 >= 0) {", + " const gi0 = permMod12[ii + perm[jj]] * 3;", + " t0 *= t0;", + " n0 = t0 * t0 * (grad3[gi0] * x0 + grad3[gi0 + 1] * y0);", + " }", + " let t1 = 0.5 - x1 * x1 - y1 * y1;", + " if (t1 >= 0) {", + " const gi1 = permMod12[ii + i1 + perm[jj + j1]] * 3;", + " t1 *= t1;", + " n1 = t1 * t1 * (grad3[gi1] * x1 + grad3[gi1 + 1] * y1);", + " }", + " let t2 = 0.5 - x2 * x2 - y2 * y2;", + " if (t2 >= 0) {", + " const gi2 = permMod12[ii + 1 + perm[jj + 1]] * 3;", + " t2 *= t2;", + " n2 = t2 * t2 * (grad3[gi2] * x2 + grad3[gi2 + 1] * y2);", + " }", + " return 70.0 * (n0 + n1 + n2);", + " }", + " noise3D(x, y, z) {", + " const permMod12 = this.permMod12;", + " const perm = this.perm;", + " let n0, n1, n2, n3;", + " const s = (x + y + z) * F3;", + " const i = Math.floor(x + s);", + " const j = Math.floor(y + s);", + " const k = Math.floor(z + s);", + " const t = (i + j + k) * G3;", + " const X0 = i - t;", + " const Y0 = j - t;", + " const Z0 = k - t;", + " const x0 = x - X0;", + " const y0 = y - Y0;", + " const z0 = z - Z0;", + " let i1, j1, k1;", + " let i2, j2, k2;", + " if (x0 >= y0) {", + " if (y0 >= z0) {", + " i1 = 1;", + " j1 = 0;", + " k1 = 0;", + " i2 = 1;", + " j2 = 1;", + " k2 = 0;", + " }", + " else if (x0 >= z0) {", + " i1 = 1;", + " j1 = 0;", + " k1 = 0;", + " i2 = 1;", + " j2 = 0;", + " k2 = 1;", + " }", + " else {", + " i1 = 0;", + " j1 = 0;", + " k1 = 1;", + " i2 = 1;", + " j2 = 0;", + " k2 = 1;", + " }", + " }", + " else {", + " if (y0 < z0) {", + " i1 = 0;", + " j1 = 0;", + " k1 = 1;", + " i2 = 0;", + " j2 = 1;", + " k2 = 1;", + " }", + " else if (x0 < z0) {", + " i1 = 0;", + " j1 = 1;", + " k1 = 0;", + " i2 = 0;", + " j2 = 1;", + " k2 = 1;", + " }", + " else {", + " i1 = 0;", + " j1 = 1;", + " k1 = 0;", + " i2 = 1;", + " j2 = 1;", + " k2 = 0;", + " }", + " }", + " const x1 = x0 - i1 + G3;", + " const y1 = y0 - j1 + G3;", + " const z1 = z0 - k1 + G3;", + " const x2 = x0 - i2 + 2.0 * G3;", + " const y2 = y0 - j2 + 2.0 * G3;", + " const z2 = z0 - k2 + 2.0 * G3;", + " const x3 = x0 - 1.0 + 3.0 * G3;", + " const y3 = y0 - 1.0 + 3.0 * G3;", + " const z3 = z0 - 1.0 + 3.0 * G3;", + " const ii = i & 255;", + " const jj = j & 255;", + " const kk = k & 255;", + " let t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0;", + " if (t0 < 0)", + " n0 = 0.0;", + " else {", + " const gi0 = permMod12[ii + perm[jj + perm[kk]]] * 3;", + " t0 *= t0;", + " n0 = t0 * t0 * (grad3[gi0] * x0 + grad3[gi0 + 1] * y0 + grad3[gi0 + 2] * z0);", + " }", + " let t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;", + " if (t1 < 0)", + " n1 = 0.0;", + " else {", + " const gi1 = permMod12[ii + i1 + perm[jj + j1 + perm[kk + k1]]] * 3;", + " t1 *= t1;", + " n1 = t1 * t1 * (grad3[gi1] * x1 + grad3[gi1 + 1] * y1 + grad3[gi1 + 2] * z1);", + " }", + " let t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;", + " if (t2 < 0)", + " n2 = 0.0;", + " else {", + " const gi2 = permMod12[ii + i2 + perm[jj + j2 + perm[kk + k2]]] * 3;", + " t2 *= t2;", + " n2 = t2 * t2 * (grad3[gi2] * x2 + grad3[gi2 + 1] * y2 + grad3[gi2 + 2] * z2);", + " }", + " let t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;", + " if (t3 < 0)", + " n3 = 0.0;", + " else {", + " const gi3 = permMod12[ii + 1 + perm[jj + 1 + perm[kk + 1]]] * 3;", + " t3 *= t3;", + " n3 = t3 * t3 * (grad3[gi3] * x3 + grad3[gi3 + 1] * y3 + grad3[gi3 + 2] * z3);", + " }", + " return 32.0 * (n0 + n1 + n2 + n3);", + " }", + " noise4D(x, y, z, w) {", + " const perm = this.perm;", + " let n0, n1, n2, n3, n4;", + " const s = (x + y + z + w) * F4;", + " const i = Math.floor(x + s);", + " const j = Math.floor(y + s);", + " const k = Math.floor(z + s);", + " const l = Math.floor(w + s);", + " const t = (i + j + k + l) * G4;", + " const X0 = i - t;", + " const Y0 = j - t;", + " const Z0 = k - t;", + " const W0 = l - t;", + " const x0 = x - X0;", + " const y0 = y - Y0;", + " const z0 = z - Z0;", + " const w0 = w - W0;", + " let rankx = 0;", + " let ranky = 0;", + " let rankz = 0;", + " let rankw = 0;", + " if (x0 > y0)", + " rankx++;", + " else", + " ranky++;", + " if (x0 > z0)", + " rankx++;", + " else", + " rankz++;", + " if (x0 > w0)", + " rankx++;", + " else", + " rankw++;", + " if (y0 > z0)", + " ranky++;", + " else", + " rankz++;", + " if (y0 > w0)", + " ranky++;", + " else", + " rankw++;", + " if (z0 > w0)", + " rankz++;", + " else", + " rankw++;", + " const i1 = rankx >= 3 ? 1 : 0;", + " const j1 = ranky >= 3 ? 1 : 0;", + " const k1 = rankz >= 3 ? 1 : 0;", + " const l1 = rankw >= 3 ? 1 : 0;", + " const i2 = rankx >= 2 ? 1 : 0;", + " const j2 = ranky >= 2 ? 1 : 0;", + " const k2 = rankz >= 2 ? 1 : 0;", + " const l2 = rankw >= 2 ? 1 : 0;", + " const i3 = rankx >= 1 ? 1 : 0;", + " const j3 = ranky >= 1 ? 1 : 0;", + " const k3 = rankz >= 1 ? 1 : 0;", + " const l3 = rankw >= 1 ? 1 : 0;", + " const x1 = x0 - i1 + G4;", + " const y1 = y0 - j1 + G4;", + " const z1 = z0 - k1 + G4;", + " const w1 = w0 - l1 + G4;", + " const x2 = x0 - i2 + 2.0 * G4;", + " const y2 = y0 - j2 + 2.0 * G4;", + " const z2 = z0 - k2 + 2.0 * G4;", + " const w2 = w0 - l2 + 2.0 * G4;", + " const x3 = x0 - i3 + 3.0 * G4;", + " const y3 = y0 - j3 + 3.0 * G4;", + " const z3 = z0 - k3 + 3.0 * G4;", + " const w3 = w0 - l3 + 3.0 * G4;", + " const x4 = x0 - 1.0 + 4.0 * G4;", + " const y4 = y0 - 1.0 + 4.0 * G4;", + " const z4 = z0 - 1.0 + 4.0 * G4;", + " const w4 = w0 - 1.0 + 4.0 * G4;", + " const ii = i & 255;", + " const jj = j & 255;", + " const kk = k & 255;", + " const ll = l & 255;", + " let t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0;", + " if (t0 < 0)", + " n0 = 0.0;", + " else {", + " const gi0 = (perm[ii + perm[jj + perm[kk + perm[ll]]]] % 32) * 4;", + " t0 *= t0;", + " n0 = t0 * t0 * (grad4[gi0] * x0 + grad4[gi0 + 1] * y0 + grad4[gi0 + 2] * z0 + grad4[gi0 + 3] * w0);", + " }", + " let t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1;", + " if (t1 < 0)", + " n1 = 0.0;", + " else {", + " const gi1 = (perm[ii + i1 + perm[jj + j1 + perm[kk + k1 + perm[ll + l1]]]] % 32) * 4;", + " t1 *= t1;", + " n1 = t1 * t1 * (grad4[gi1] * x1 + grad4[gi1 + 1] * y1 + grad4[gi1 + 2] * z1 + grad4[gi1 + 3] * w1);", + " }", + " let t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2;", + " if (t2 < 0)", + " n2 = 0.0;", + " else {", + " const gi2 = (perm[ii + i2 + perm[jj + j2 + perm[kk + k2 + perm[ll + l2]]]] % 32) * 4;", + " t2 *= t2;", + " n2 = t2 * t2 * (grad4[gi2] * x2 + grad4[gi2 + 1] * y2 + grad4[gi2 + 2] * z2 + grad4[gi2 + 3] * w2);", + " }", + " let t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3;", + " if (t3 < 0)", + " n3 = 0.0;", + " else {", + " const gi3 = (perm[ii + i3 + perm[jj + j3 + perm[kk + k3 + perm[ll + l3]]]] % 32) * 4;", + " t3 *= t3;", + " n3 = t3 * t3 * (grad4[gi3] * x3 + grad4[gi3 + 1] * y3 + grad4[gi3 + 2] * z3 + grad4[gi3 + 3] * w3);", + " }", + " let t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4;", + " if (t4 < 0)", + " n4 = 0.0;", + " else {", + " const gi4 = (perm[ii + 1 + perm[jj + 1 + perm[kk + 1 + perm[ll + 1]]]] % 32) * 4;", + " t4 *= t4;", + " n4 = t4 * t4 * (grad4[gi4] * x4 + grad4[gi4 + 1] * y4 + grad4[gi4 + 2] * z4 + grad4[gi4 + 3] * w4);", + " }", + " return 27.0 * (n0 + n1 + n2 + n3 + n4);", + " }", + "}", + "function buildPermutationTable(random) {", + " const p = new Uint8Array(256);", + " for (let i = 0; i < 256; i++) {", + " p[i] = i;", + " }", + " for (let i = 0; i < 255; i++) {", + " const r = i + ~~(random() * (256 - i));", + " const aux = p[i];", + " p[i] = p[r];", + " p[r] = aux;", + " }", + " return p;", + "}", + "function alea(seed) {", + " let s0 = 0;", + " let s1 = 0;", + " let s2 = 0;", + " let c = 1;", + " const mash = masher();", + " s0 = mash(' ');", + " s1 = mash(' ');", + " s2 = mash(' ');", + " s0 -= mash(seed);", + " if (s0 < 0) {", + " s0 += 1;", + " }", + " s1 -= mash(seed);", + " if (s1 < 0) {", + " s1 += 1;", + " }", + " s2 -= mash(seed);", + " if (s2 < 0) {", + " s2 += 1;", + " }", + " return function () {", + " const t = 2091639 * s0 + c * 2.3283064365386963e-10;", + " s0 = s1;", + " s1 = s2;", + " return s2 = t - (c = t | 0);", + " };", + "}", + "function masher() {", + " let n = 0xefc8249d;", + " return function (data) {", + " data = data.toString();", + " for (let i = 0; i < data.length; i++) {", + " n += data.charCodeAt(i);", + " let h = 0.02519603282416938 * n;", + " n = h >>> 0;", + " h -= n;", + " h *= n;", + " n = h >>> 0;", + " h -= n;", + " n += h * 0x100000000;", + " }", + " return (n >>> 0) * 2.3283064365386963e-10;", + " };", + "}", + "", + "class TurbulenceField {", + " constructor(scale, octaves, velocityMultiplier, timeScale) {", + " this.scale = scale;", + " this.octaves = octaves;", + " this.velocityMultiplier = velocityMultiplier;", + " this.timeScale = timeScale;", + " this.type = 'TurbulenceField';", + " this.generator = new SimplexNoise();", + " this.timeOffset = new THREE.Vector3();", + " this.temp = new THREE.Vector3();", + " this.temp2 = new THREE.Vector3();", + " this.timeOffset.x = Math.random() / this.scale.x * this.timeScale.x;", + " this.timeOffset.y = Math.random() / this.scale.y * this.timeScale.y;", + " this.timeOffset.z = Math.random() / this.scale.z * this.timeScale.z;", + " }", + " initialize(particle) {", + " }", + " update(particle, delta) {", + " const x = particle.position.x / this.scale.x;", + " const y = particle.position.y / this.scale.y;", + " const z = particle.position.z / this.scale.z;", + " this.temp.set(0, 0, 0);", + " let lvl = 1;", + " for (let i = 0; i < this.octaves; i++) {", + " this.temp2.set(this.generator.noise4D(x * lvl, y * lvl, z * lvl, this.timeOffset.x * lvl) / lvl, this.generator.noise4D(x * lvl, y * lvl, z * lvl, this.timeOffset.y * lvl) / lvl, this.generator.noise4D(x * lvl, y * lvl, z * lvl, this.timeOffset.z * lvl) / lvl);", + " this.temp.add(this.temp2);", + " lvl *= 2;", + " }", + " this.temp.multiply(this.velocityMultiplier);", + " particle.velocity.addScaledVector(this.temp, delta);", + " }", + " toJSON() {", + " return {", + " type: this.type,", + " scale: [this.scale.x, this.scale.y, this.scale.z],", + " octaves: this.octaves,", + " velocityMultiplier: [this.velocityMultiplier.x, this.velocityMultiplier.y, this.velocityMultiplier.z],", + " timeScale: [this.timeScale.x, this.timeScale.y, this.timeScale.z],", + " };", + " }", + " frameUpdate(delta) {", + " this.timeOffset.x += delta * this.timeScale.x;", + " this.timeOffset.y += delta * this.timeScale.y;", + " this.timeOffset.z += delta * this.timeScale.z;", + " }", + " static fromJSON(json) {", + " return new TurbulenceField(new THREE.Vector3(json.scale[0], json.scale[1], json.scale[2]), json.octaves, new THREE.Vector3(json.velocityMultiplier[0], json.velocityMultiplier[1], json.velocityMultiplier[2]), new THREE.Vector3(json.timeScale[0], json.timeScale[1], json.timeScale[2]));", + " }", + " clone() {", + " return new TurbulenceField(this.scale.clone(), this.octaves, this.velocityMultiplier.clone(), this.timeScale.clone());", + " }", + " reset() {", + " }", + "}", + "", + "function randomInt(a, b) {", + " return Math.floor(Math.random() * (b - a)) + a;", + "}", + "", + "const generators = [];", + "const tempV$2 = new THREE.Vector3();", + "const tempQ$2 = new THREE.Quaternion();", + "class Noise {", + " constructor(frequency, power, positionAmount = new ConstantValue(1), rotationAmount = new ConstantValue(0)) {", + " this.frequency = frequency;", + " this.power = power;", + " this.positionAmount = positionAmount;", + " this.rotationAmount = rotationAmount;", + " this.type = 'Noise';", + " this.duration = 0;", + " if (generators.length === 0) {", + " for (let i = 0; i < 100; i++) {", + " generators.push(new SimplexNoise());", + " }", + " }", + " }", + " initialize(particle) {", + " particle.lastPosNoise = new THREE.Vector3();", + " if (typeof particle.rotation === 'number') {", + " particle.lastRotNoise = 0;", + " }", + " else {", + " particle.lastRotNoise = new THREE.Quaternion();", + " }", + " particle.generatorIndex = [randomInt(0, 100), randomInt(0, 100), randomInt(0, 100), randomInt(0, 100)];", + " }", + " update(particle, _) {", + " let frequency = this.frequency.genValue(particle.age / particle.life);", + " let power = this.power.genValue(particle.age / particle.life);", + " let positionAmount = this.positionAmount.genValue(particle.age / particle.life);", + " let rotationAmount = this.rotationAmount.genValue(particle.age / particle.life);", + " if (positionAmount > 0 && particle.lastPosNoise !== undefined) {", + " particle.position.sub(particle.lastPosNoise);", + " tempV$2.set(generators[particle.generatorIndex[0]].noise2D(0, particle.age * frequency) *", + " power *", + " positionAmount, generators[particle.generatorIndex[1]].noise2D(0, particle.age * frequency) *", + " power *", + " positionAmount, generators[particle.generatorIndex[2]].noise2D(0, particle.age * frequency) *", + " power *", + " positionAmount);", + " particle.position.add(tempV$2);", + " particle.lastPosNoise.copy(tempV$2);", + " }", + " if (rotationAmount > 0 && particle.lastRotNoise !== undefined) {", + " if (typeof particle.rotation === 'number') {", + " particle.rotation -= particle.lastRotNoise;", + " particle.rotation +=", + " generators[particle.generatorIndex[3]].noise2D(0, particle.age * frequency) *", + " Math.PI *", + " power *", + " rotationAmount;", + " }", + " else {", + " particle.lastRotNoise.invert();", + " particle.rotation.multiply(particle.lastRotNoise);", + " tempQ$2", + " .set(generators[particle.generatorIndex[0]].noise2D(0, particle.age * frequency) *", + " power *", + " rotationAmount, generators[particle.generatorIndex[1]].noise2D(0, particle.age * frequency) *", + " power *", + " rotationAmount, generators[particle.generatorIndex[2]].noise2D(0, particle.age * frequency) *", + " power *", + " rotationAmount, generators[particle.generatorIndex[3]].noise2D(0, particle.age * frequency) *", + " power *", + " rotationAmount)", + " .normalize();", + " particle.rotation.multiply(tempQ$2);", + " particle.lastRotNoise.copy(tempQ$2);", + " }", + " }", + " }", + " toJSON() {", + " return {", + " type: this.type,", + " frequency: this.frequency.toJSON(),", + " power: this.power.toJSON(),", + " positionAmount: this.positionAmount.toJSON(),", + " rotationAmount: this.rotationAmount.toJSON(),", + " };", + " }", + " frameUpdate(delta) {", + " this.duration += delta;", + " }", + " static fromJSON(json) {", + " return new Noise(ValueGeneratorFromJSON(json.frequency), ValueGeneratorFromJSON(json.power), ValueGeneratorFromJSON(json.positionAmount), ValueGeneratorFromJSON(json.rotationAmount));", + " }", + " clone() {", + " return new Noise(this.frequency.clone(), this.power.clone(), this.positionAmount.clone(), this.rotationAmount.clone());", + " }", + " reset() { }", + "}", + "", + "class TextureSequencer {", + " constructor(scaleX = 0, scaleY = 0, position = new THREE.Vector3()) {", + " this.scaleX = scaleX;", + " this.scaleY = scaleY;", + " this.position = position;", + " this.locations = [];", + " }", + " transform(position, index) {", + " position.x = this.locations[index % this.locations.length].x * this.scaleX + this.position.x;", + " position.y = this.locations[index % this.locations.length].y * this.scaleY + this.position.y;", + " position.z = this.position.z;", + " }", + " static fromJSON(json) {", + " const textureSequencer = new TextureSequencer(json.scaleX, json.scaleY, new THREE.Vector3(json.position[0], json.position[1], json.position[2]));", + " textureSequencer.locations = json.locations.map((loc) => new THREE.Vector2(loc.x, loc.y));", + " return textureSequencer;", + " }", + " clone() {", + " const textureSequencer = new TextureSequencer(this.scaleX, this.scaleY, this.position.clone());", + " textureSequencer.locations = this.locations.map(loc => loc.clone());", + " return textureSequencer;", + " }", + " toJSON() {", + " return {", + " scaleX: this.scaleX,", + " scaleY: this.scaleY,", + " position: this.position,", + " locations: this.locations.map(loc => ({", + " x: loc.x,", + " y: loc.y,", + " }))", + " };", + " }", + " fromImage(img, threshold) {", + " const canvas = document.createElement(\"canvas\");", + " canvas.width = img.width;", + " canvas.height = img.height;", + " const ctx = canvas.getContext(\"2d\");", + " if (!ctx) {", + " return;", + " }", + " ctx.drawImage(img, 0, 0);", + " const data = ctx.getImageData(0, 0, canvas.width, canvas.height, { colorSpace: \"srgb\" });", + " canvas.remove();", + " this.locations.length = 0;", + " for (let i = 0; i < data.height; i++) {", + " for (let j = 0; j < data.width; j++) {", + " if (data.data[(i * data.width + j) * 4 + 3] > threshold) {", + " this.locations.push(new THREE.Vector2(j, data.height - i));", + " }", + " }", + " }", + " }", + "}", + "", + "function SequencerFromJSON(json) {", + " switch (json.type) {", + " case 'TextureSequencer':", + " return TextureSequencer.fromJSON(json);", + " default:", + " return new TextureSequencer();", + " }", + "}", + "", + "class ApplySequences {", + " constructor(delayBetweenParticles) {", + " this.type = 'ApplySequences';", + " this.sequencers = [];", + " this.time = 0;", + " this.index = 0;", + " this.pCount = 0;", + " this.tempV = new THREE.Vector3();", + " this.delay = delayBetweenParticles;", + " }", + " initialize(particle) {", + " particle.id = this.pCount;", + " particle.dst = new THREE.Vector3();", + " particle.begin = new THREE.Vector3();", + " particle.inMotion = false;", + " this.pCount++;", + " }", + " reset() {", + " this.time = 0;", + " this.index = 0;", + " this.pCount = 0;", + " }", + " update(particle, delta) {", + " const sequencer = this.sequencers[this.index];", + " const delay = particle.id * this.delay;", + " if (this.time >= sequencer[0].a + delay && this.time <= sequencer[0].b + delay) {", + " if (!particle.inMotion) {", + " particle.inMotion = true;", + " particle.begin.copy(particle.position);", + " sequencer[1].transform(particle.dst, particle.id);", + " }", + " particle.position.lerpVectors(particle.begin, particle.dst, ApplySequences.BEZIER.genValue((this.time - sequencer[0].a - delay) / (sequencer[0].b - sequencer[0].a)));", + " }", + " else if (this.time > sequencer[0].b + delay) {", + " particle.inMotion = false;", + " }", + " }", + " frameUpdate(delta) {", + " while (this.index + 1 < this.sequencers.length && this.time >= this.sequencers[this.index + 1][0].a) {", + " this.index++;", + " }", + " this.time += delta;", + " }", + " appendSequencer(range, sequencer) {", + " this.sequencers.push([range, sequencer]);", + " }", + " toJSON() {", + " return {", + " type: this.type,", + " delay: this.delay,", + " sequencers: this.sequencers.map(([range, sequencer]) => ({", + " range: range.toJSON(),", + " sequencer: sequencer.toJSON(),", + " })),", + " };", + " }", + " static fromJSON(json) {", + " const seq = new ApplySequences(json.delay);", + " json.sequencers.forEach((sequencerJson) => {", + " seq.sequencers.push([ValueGeneratorFromJSON(sequencerJson.range), SequencerFromJSON(sequencerJson.sequencer)]);", + " });", + " return seq;", + " }", + " clone() {", + " const applySequences = new ApplySequences(this.delay);", + " applySequences.sequencers = this.sequencers.map(seq => [seq[0].clone(), seq[1].clone()]);", + " return applySequences;", + " }", + "}", + "ApplySequences.BEZIER = new Bezier(0, 0, 1, 1);", + "", + "let physicsResolver;", + "function setPhysicsResolver(resolver) {", + " physicsResolver = resolver;", + "}", + "function getPhysicsResolver() {", + " return physicsResolver;", + "}", + "class ApplyCollision {", + " constructor(resolver, bounce) {", + " this.resolver = resolver;", + " this.bounce = bounce;", + " this.type = 'ApplyCollision';", + " this.tempV = new THREE.Vector3();", + " }", + " initialize(particle) {", + " }", + " update(particle, delta) {", + " if (this.resolver.resolve(particle.position, this.tempV)) {", + " particle.velocity.reflect(this.tempV).multiplyScalar(this.bounce);", + " }", + " }", + " frameUpdate(delta) {", + " }", + " toJSON() {", + " return {", + " type: this.type,", + " bounce: this.bounce,", + " };", + " }", + " static fromJSON(json) {", + " return new ApplyCollision(getPhysicsResolver(), json.bounce);", + " }", + " clone() {", + " return new ApplyCollision(this.resolver, this.bounce);", + " }", + " reset() {", + " }", + "}", + "", + "class ColorBySpeed {", + " constructor(color, speedRange) {", + " this.color = color;", + " this.speedRange = speedRange;", + " this.type = 'ColorBySpeed';", + " }", + " initialize(particle) {", + " if (this.color.type === 'memorizedFunction') {", + " particle.colorOverLifeMemory = {};", + " this.color.startGen(particle.colorOverLifeMemory);", + " }", + " }", + " update(particle, delta) {", + " const t = (particle.startSpeed - this.speedRange.a) / (this.speedRange.b - this.speedRange.a);", + " if (this.color.type === 'memorizedFunction') {", + " this.color.genColor(particle.color, t, particle.colorOverLifeMemory);", + " }", + " else {", + " this.color.genColor(particle.color, t);", + " }", + " particle.color.x *= particle.startColor.x;", + " particle.color.y *= particle.startColor.y;", + " particle.color.z *= particle.startColor.z;", + " particle.color.w *= particle.startColor.w;", + " }", + " frameUpdate(delta) { }", + " toJSON() {", + " return {", + " type: this.type,", + " color: this.color.toJSON(),", + " speedRange: this.speedRange.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " return new ColorBySpeed(ColorGeneratorFromJSON(json.color), IntervalValue.fromJSON(json.speedRange));", + " }", + " clone() {", + " return new ColorBySpeed(this.color.clone(), this.speedRange.clone());", + " }", + " reset() { }", + "}", + "", + "class SizeBySpeed {", + " initialize(particle) { }", + " constructor(size, speedRange) {", + " this.size = size;", + " this.speedRange = speedRange;", + " this.type = 'SizeBySpeed';", + " }", + " update(particle) {", + " const t = (particle.startSpeed - this.speedRange.a) / (this.speedRange.b - this.speedRange.a);", + " particle.size = particle.startSize * this.size.genValue(t);", + " }", + " toJSON() {", + " return {", + " type: this.type,", + " size: this.size.toJSON(),", + " speedRange: this.speedRange.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " return new SizeBySpeed(ValueGeneratorFromJSON(json.size), IntervalValue.fromJSON(json.speedRange));", + " }", + " frameUpdate(delta) { }", + " clone() {", + " return new SizeBySpeed(this.size.clone(), this.speedRange.clone());", + " }", + " reset() { }", + "}", + "", + "class RotationBySpeed {", + " constructor(angularVelocity, speedRange) {", + " this.angularVelocity = angularVelocity;", + " this.speedRange = speedRange;", + " this.type = 'RotationBySpeed';", + " this.tempQuat = new THREE.Quaternion();", + " }", + " initialize(particle) { }", + " update(particle, delta) {", + " const t = (particle.startSpeed - this.speedRange.a) / (this.speedRange.b - this.speedRange.a);", + " particle.rotation += delta * this.angularVelocity.genValue(t);", + " }", + " toJSON() {", + " return {", + " type: this.type,", + " angularVelocity: this.angularVelocity.toJSON(),", + " speedRange: this.speedRange.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " return new RotationBySpeed(ValueGeneratorFromJSON(json.angularVelocity), IntervalValue.fromJSON(json.speedRange));", + " }", + " frameUpdate(delta) { }", + " clone() {", + " return new RotationBySpeed(this.angularVelocity.clone(), this.speedRange.clone());", + " }", + " reset() { }", + "}", + "", + "class LimitSpeedOverLife {", + " initialize(particle) { }", + " constructor(speed, dampen) {", + " this.speed = speed;", + " this.dampen = dampen;", + " this.type = 'LimitSpeedOverLife';", + " }", + " update(particle, delta) {", + " let speed = particle.velocity.length();", + " let limit = this.speed.genValue(particle.age / particle.life);", + " if (speed > limit) {", + " const percent = (speed - limit) / speed;", + " particle.velocity.multiplyScalar(1 - percent * this.dampen * delta * 20);", + " }", + " }", + " toJSON() {", + " return {", + " type: this.type,", + " speed: this.speed.toJSON(),", + " dampen: this.dampen,", + " };", + " }", + " static fromJSON(json) {", + " return new LimitSpeedOverLife(ValueGeneratorFromJSON(json.speed), json.dampen);", + " }", + " frameUpdate(delta) { }", + " clone() {", + " return new LimitSpeedOverLife(this.speed.clone(), this.dampen);", + " }", + " reset() { }", + "}", + "", + "const BehaviorTypes = {", + " ApplyForce: {", + " type: 'ApplyForce',", + " constructor: ApplyForce,", + " params: [", + " ['direction', 'vec3'],", + " ['magnitude', 'value'],", + " ],", + " loadJSON: ApplyForce.fromJSON,", + " },", + " Noise: {", + " type: 'Noise',", + " constructor: Noise,", + " params: [", + " ['frequency', 'value'],", + " ['power', 'value'],", + " ['positionAmount', 'value'],", + " ['rotationAmount', 'value'],", + " ],", + " loadJSON: Noise.fromJSON,", + " },", + " TurbulenceField: {", + " type: 'TurbulenceField',", + " constructor: TurbulenceField,", + " params: [", + " ['scale', 'vec3'],", + " ['octaves', 'number'],", + " ['velocityMultiplier', 'vec3'],", + " ['timeScale', 'vec3'],", + " ],", + " loadJSON: TurbulenceField.fromJSON,", + " },", + " GravityForce: {", + " type: 'GravityForce',", + " constructor: GravityForce,", + " params: [", + " ['center', 'vec3'],", + " ['magnitude', 'number'],", + " ],", + " loadJSON: GravityForce.fromJSON,", + " },", + " ColorOverLife: {", + " type: 'ColorOverLife',", + " constructor: ColorOverLife,", + " params: [['color', 'colorFunc']],", + " loadJSON: ColorOverLife.fromJSON,", + " },", + " RotationOverLife: {", + " type: 'RotationOverLife',", + " constructor: RotationOverLife,", + " params: [['angularVelocity', 'valueFunc']],", + " loadJSON: RotationOverLife.fromJSON,", + " },", + " Rotation3DOverLife: {", + " type: 'Rotation3DOverLife',", + " constructor: Rotation3DOverLife,", + " params: [['angularVelocity', 'rotationFunc']],", + " loadJSON: Rotation3DOverLife.fromJSON,", + " },", + " SizeOverLife: {", + " type: 'SizeOverLife',", + " constructor: SizeOverLife,", + " params: [['size', 'valueFunc']],", + " loadJSON: SizeOverLife.fromJSON,", + " },", + " ColorBySpeed: {", + " type: 'ColorBySpeed',", + " constructor: ColorBySpeed,", + " params: [", + " ['color', 'colorFunc'],", + " ['speedRange', 'range'],", + " ],", + " loadJSON: ColorBySpeed.fromJSON,", + " },", + " RotationBySpeed: {", + " type: 'RotationBySpeed',", + " constructor: RotationBySpeed,", + " params: [", + " ['angularVelocity', 'valueFunc'],", + " ['speedRange', 'range'],", + " ],", + " loadJSON: RotationBySpeed.fromJSON,", + " },", + " SizeBySpeed: {", + " type: 'SizeBySpeed',", + " constructor: SizeBySpeed,", + " params: [", + " ['size', 'valueFunc'],", + " ['speedRange', 'range'],", + " ],", + " loadJSON: SizeBySpeed.fromJSON,", + " },", + " SpeedOverLife: {", + " type: 'SpeedOverLife',", + " constructor: SpeedOverLife,", + " params: [['speed', 'valueFunc']],", + " loadJSON: SpeedOverLife.fromJSON,", + " },", + " FrameOverLife: {", + " type: 'FrameOverLife',", + " constructor: FrameOverLife,", + " params: [['frame', 'valueFunc']],", + " loadJSON: FrameOverLife.fromJSON,", + " },", + " ForceOverLife: {", + " type: 'ForceOverLife',", + " constructor: ForceOverLife,", + " params: [", + " ['x', 'valueFunc'],", + " ['y', 'valueFunc'],", + " ['z', 'valueFunc'],", + " ],", + " loadJSON: ForceOverLife.fromJSON,", + " },", + " OrbitOverLife: {", + " type: 'OrbitOverLife',", + " constructor: OrbitOverLife,", + " params: [", + " ['orbitSpeed', 'valueFunc'],", + " ['axis', 'vec3'],", + " ],", + " loadJSON: OrbitOverLife.fromJSON,", + " },", + " WidthOverLength: {", + " type: 'WidthOverLength',", + " constructor: WidthOverLength,", + " params: [['width', 'valueFunc']],", + " loadJSON: WidthOverLength.fromJSON,", + " },", + " ChangeEmitDirection: {", + " type: 'ChangeEmitDirection',", + " constructor: ChangeEmitDirection,", + " params: [['angle', 'value']],", + " loadJSON: ChangeEmitDirection.fromJSON,", + " },", + " EmitSubParticleSystem: {", + " type: 'EmitSubParticleSystem',", + " constructor: EmitSubParticleSystem,", + " params: [", + " ['particleSystem', 'self'],", + " ['useVelocityAsBasis', 'boolean'],", + " ['subParticleSystem', 'particleSystem'],", + " ['mode', 'number'],", + " ['emitProbability', 'number'],", + " ],", + " loadJSON: EmitSubParticleSystem.fromJSON,", + " },", + " LimitSpeedOverLife: {", + " type: 'LimitSpeedOverLife',", + " constructor: LimitSpeedOverLife,", + " params: [", + " ['speed', 'valueFunc'],", + " ['dampen', 'number'],", + " ],", + " loadJSON: LimitSpeedOverLife.fromJSON,", + " },", + "};", + "function BehaviorFromJSON(json, particleSystem) {", + " return BehaviorTypes[json.type].loadJSON(json, particleSystem);", + "}", + "", + "let EmitterMode = void 0;", + "(function (EmitterMode) {", + " EmitterMode[EmitterMode[\"Random\"] = 0] = \"Random\";", + " EmitterMode[EmitterMode[\"Loop\"] = 1] = \"Loop\";", + " EmitterMode[EmitterMode[\"PingPong\"] = 2] = \"PingPong\";", + " EmitterMode[EmitterMode[\"Burst\"] = 3] = \"Burst\";", + "})(EmitterMode || (EmitterMode = {}));", + "function getValueFromEmitterMode(mode, currentValue, spread) {", + " let u;", + " if (EmitterMode.Random === mode) {", + " currentValue = Math.random();", + " }", + " if (spread > 0) {", + " u = Math.floor(currentValue / spread) * spread;", + " }", + " else {", + " u = currentValue;", + " }", + " switch (mode) {", + " case EmitterMode.Loop:", + " u = u % 1;", + " break;", + " case EmitterMode.PingPong:", + " u = Math.abs((u % 2) - 1);", + " break;", + " }", + " return u;", + "}", + "", + "class ConeEmitter {", + " constructor(parameters = {}) {", + " var _a, _b, _c, _d, _e, _f, _g;", + " this.type = 'cone';", + " this.currentValue = 0;", + " this.radius = (_a = parameters.radius) !== null && _a !== void 0 ? _a : 10;", + " this.arc = (_b = parameters.arc) !== null && _b !== void 0 ? _b : 2.0 * Math.PI;", + " this.thickness = (_c = parameters.thickness) !== null && _c !== void 0 ? _c : 1;", + " this.angle = (_d = parameters.angle) !== null && _d !== void 0 ? _d : Math.PI / 6;", + " this.mode = (_e = parameters.mode) !== null && _e !== void 0 ? _e : EmitterMode.Random;", + " this.spread = (_f = parameters.spread) !== null && _f !== void 0 ? _f : 0;", + " this.speed = (_g = parameters.speed) !== null && _g !== void 0 ? _g : new ConstantValue(1);", + " }", + " update(system, delta) {", + " if (EmitterMode.Random != this.mode) {", + " this.currentValue += this.speed.genValue(system.emissionState.time / system.duration) * delta;", + " }", + " }", + " initialize(p) {", + " const u = getValueFromEmitterMode(this.mode, this.currentValue, this.spread);", + " const rand = THREE.MathUtils.lerp(1 - this.thickness, 1, Math.random());", + " const theta = u * this.arc;", + " const r = Math.sqrt(rand);", + " const sinTheta = Math.sin(theta);", + " const cosTheta = Math.cos(theta);", + " p.position.x = r * cosTheta;", + " p.position.y = r * sinTheta;", + " p.position.z = 0;", + " const angle = this.angle * r;", + " p.velocity.set(0, 0, Math.cos(angle)).addScaledVector(p.position, Math.sin(angle)).multiplyScalar(p.startSpeed);", + " p.position.multiplyScalar(this.radius);", + " }", + " toJSON() {", + " return {", + " type: 'cone',", + " radius: this.radius,", + " arc: this.arc,", + " thickness: this.thickness,", + " angle: this.angle,", + " mode: this.mode,", + " spread: this.spread,", + " speed: this.speed.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " return new ConeEmitter({", + " radius: json.radius,", + " arc: json.arc,", + " thickness: json.thickness,", + " angle: json.angle,", + " mode: json.mode,", + " speed: json.speed ? ValueGeneratorFromJSON(json.speed) : undefined,", + " spread: json.spread,", + " });", + " }", + " clone() {", + " return new ConeEmitter({", + " radius: this.radius,", + " arc: this.arc,", + " thickness: this.thickness,", + " angle: this.angle,", + " mode: this.mode,", + " speed: this.speed.clone(),", + " spread: this.spread,", + " });", + " }", + "}", + "", + "class CircleEmitter {", + " constructor(parameters = {}) {", + " var _a, _b, _c, _d, _e, _f;", + " this.type = 'circle';", + " this.currentValue = 0;", + " this.radius = (_a = parameters.radius) !== null && _a !== void 0 ? _a : 10;", + " this.arc = (_b = parameters.arc) !== null && _b !== void 0 ? _b : 2.0 * Math.PI;", + " this.thickness = (_c = parameters.thickness) !== null && _c !== void 0 ? _c : 1;", + " this.mode = (_d = parameters.mode) !== null && _d !== void 0 ? _d : EmitterMode.Random;", + " this.spread = (_e = parameters.spread) !== null && _e !== void 0 ? _e : 0;", + " this.speed = (_f = parameters.speed) !== null && _f !== void 0 ? _f : new ConstantValue(1);", + " }", + " update(system, delta) {", + " this.currentValue += this.speed.genValue(system.emissionState.time / system.duration) * delta;", + " }", + " initialize(p) {", + " const u = getValueFromEmitterMode(this.mode, this.currentValue, this.spread);", + " const r = THREE.MathUtils.lerp(1 - this.thickness, 1, Math.random());", + " const theta = u * this.arc;", + " p.position.x = Math.cos(theta);", + " p.position.y = Math.sin(theta);", + " p.position.z = 0;", + " p.velocity.copy(p.position).multiplyScalar(p.startSpeed);", + " p.position.multiplyScalar(this.radius * r);", + " }", + " toJSON() {", + " return {", + " type: 'circle',", + " radius: this.radius,", + " arc: this.arc,", + " thickness: this.thickness,", + " mode: this.mode,", + " spread: this.spread,", + " speed: this.speed.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " return new CircleEmitter({", + " radius: json.radius,", + " arc: json.arc,", + " thickness: json.thickness,", + " mode: json.mode,", + " speed: json.speed ? ValueGeneratorFromJSON(json.speed) : undefined,", + " spread: json.spread,", + " });", + " }", + " clone() {", + " return new CircleEmitter({", + " radius: this.radius,", + " arc: this.arc,", + " thickness: this.thickness,", + " mode: this.mode,", + " speed: this.speed.clone(),", + " spread: this.spread,", + " });", + " }", + "}", + "", + "class DonutEmitter {", + " constructor(parameters = {}) {", + " var _a, _b, _c, _d, _e, _f, _g;", + " this.type = 'donut';", + " this.currentValue = 0;", + " this.radius = (_a = parameters.radius) !== null && _a !== void 0 ? _a : 10;", + " this.arc = (_b = parameters.arc) !== null && _b !== void 0 ? _b : 2.0 * Math.PI;", + " this.thickness = (_c = parameters.thickness) !== null && _c !== void 0 ? _c : 1;", + " this.donutRadius = (_d = parameters.donutRadius) !== null && _d !== void 0 ? _d : this.radius * 0.2;", + " this.mode = (_e = parameters.mode) !== null && _e !== void 0 ? _e : EmitterMode.Random;", + " this.spread = (_f = parameters.spread) !== null && _f !== void 0 ? _f : 0;", + " this.speed = (_g = parameters.speed) !== null && _g !== void 0 ? _g : new ConstantValue(1);", + " }", + " update(system, delta) {", + " if (EmitterMode.Random != this.mode) {", + " this.currentValue += this.speed.genValue(system.emissionState.time / system.duration) * delta;", + " }", + " }", + " initialize(p) {", + " const u = getValueFromEmitterMode(this.mode, this.currentValue, this.spread);", + " const v = Math.random();", + " const rand = THREE.MathUtils.lerp(1 - this.thickness, 1, Math.random());", + " const theta = u * this.arc;", + " const phi = v * Math.PI * 2;", + " const sinTheta = Math.sin(theta);", + " const cosTheta = Math.cos(theta);", + " p.position.x = this.radius * cosTheta;", + " p.position.y = this.radius * sinTheta;", + " p.position.z = 0;", + " p.velocity.z = this.donutRadius * rand * Math.sin(phi);", + " p.velocity.x = this.donutRadius * rand * Math.cos(phi) * cosTheta;", + " p.velocity.y = this.donutRadius * rand * Math.cos(phi) * sinTheta;", + " p.position.add(p.velocity);", + " p.velocity.normalize().multiplyScalar(p.startSpeed);", + " }", + " toJSON() {", + " return {", + " type: 'donut',", + " radius: this.radius,", + " arc: this.arc,", + " thickness: this.thickness,", + " donutRadius: this.donutRadius,", + " mode: this.mode,", + " spread: this.spread,", + " speed: this.speed.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " return new DonutEmitter({", + " radius: json.radius,", + " arc: json.arc,", + " thickness: json.thickness,", + " donutRadius: json.donutRadius,", + " mode: json.mode,", + " speed: json.speed ? ValueGeneratorFromJSON(json.speed) : undefined,", + " spread: json.spread,", + " });", + " }", + " clone() {", + " return new DonutEmitter({", + " radius: this.radius,", + " arc: this.arc,", + " thickness: this.thickness,", + " donutRadius: this.donutRadius,", + " mode: this.mode,", + " speed: this.speed.clone(),", + " spread: this.spread,", + " });", + " }", + "}", + "", + "class PointEmitter {", + " constructor() {", + " this.type = 'point';", + " }", + " update(system, delta) { }", + " initialize(p) {", + " const u = Math.random();", + " const v = Math.random();", + " const theta = u * Math.PI * 2;", + " const phi = Math.acos(2.0 * v - 1.0);", + " const r = Math.cbrt(Math.random());", + " const sinTheta = Math.sin(theta);", + " const cosTheta = Math.cos(theta);", + " const sinPhi = Math.sin(phi);", + " const cosPhi = Math.cos(phi);", + " p.velocity.x = r * sinPhi * cosTheta;", + " p.velocity.y = r * sinPhi * sinTheta;", + " p.velocity.z = r * cosPhi;", + " p.velocity.multiplyScalar(p.startSpeed);", + " p.position.setScalar(0);", + " }", + " toJSON() {", + " return {", + " type: 'point',", + " };", + " }", + " static fromJSON(json) {", + " return new PointEmitter();", + " }", + " clone() {", + " return new PointEmitter();", + " }", + "}", + "", + "class SphereEmitter {", + " constructor(parameters = {}) {", + " var _a, _b, _c, _d, _e, _f;", + " this.type = 'sphere';", + " this.currentValue = 0;", + " this.radius = (_a = parameters.radius) !== null && _a !== void 0 ? _a : 10;", + " this.arc = (_b = parameters.arc) !== null && _b !== void 0 ? _b : 2.0 * Math.PI;", + " this.thickness = (_c = parameters.thickness) !== null && _c !== void 0 ? _c : 1;", + " this.mode = (_d = parameters.mode) !== null && _d !== void 0 ? _d : EmitterMode.Random;", + " this.spread = (_e = parameters.spread) !== null && _e !== void 0 ? _e : 0;", + " this.speed = (_f = parameters.speed) !== null && _f !== void 0 ? _f : new ConstantValue(1);", + " }", + " update(system, delta) {", + " if (EmitterMode.Random != this.mode) {", + " this.currentValue += this.speed.genValue(system.emissionState.time / system.duration) * delta;", + " }", + " }", + " initialize(p) {", + " const u = getValueFromEmitterMode(this.mode, this.currentValue, this.spread);", + " const v = Math.random();", + " const rand = THREE.MathUtils.lerp(1 - this.thickness, 1, Math.random());", + " const theta = u * this.arc;", + " const phi = Math.acos(2.0 * v - 1.0);", + " const sinTheta = Math.sin(theta);", + " const cosTheta = Math.cos(theta);", + " const sinPhi = Math.sin(phi);", + " const cosPhi = Math.cos(phi);", + " p.position.x = sinPhi * cosTheta;", + " p.position.y = sinPhi * sinTheta;", + " p.position.z = cosPhi;", + " p.velocity.copy(p.position).multiplyScalar(p.startSpeed);", + " p.position.multiplyScalar(this.radius * rand);", + " }", + " toJSON() {", + " return {", + " type: 'sphere',", + " radius: this.radius,", + " arc: this.arc,", + " thickness: this.thickness,", + " mode: this.mode,", + " spread: this.spread,", + " speed: this.speed.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " return new SphereEmitter({", + " radius: json.radius,", + " arc: json.arc,", + " thickness: json.thickness,", + " mode: json.mode,", + " speed: json.speed ? ValueGeneratorFromJSON(json.speed) : undefined,", + " spread: json.spread,", + " });", + " }", + " clone() {", + " return new SphereEmitter({", + " radius: this.radius,", + " arc: this.arc,", + " thickness: this.thickness,", + " mode: this.mode,", + " speed: this.speed.clone(),", + " spread: this.spread,", + " });", + " }", + "}", + "", + "class HemisphereEmitter {", + " constructor(parameters = {}) {", + " var _a, _b, _c, _d, _e, _f;", + " this.type = 'sphere';", + " this.currentValue = 0;", + " this.radius = (_a = parameters.radius) !== null && _a !== void 0 ? _a : 10;", + " this.arc = (_b = parameters.arc) !== null && _b !== void 0 ? _b : 2.0 * Math.PI;", + " this.thickness = (_c = parameters.thickness) !== null && _c !== void 0 ? _c : 1;", + " this.mode = (_d = parameters.mode) !== null && _d !== void 0 ? _d : EmitterMode.Random;", + " this.spread = (_e = parameters.spread) !== null && _e !== void 0 ? _e : 0;", + " this.speed = (_f = parameters.speed) !== null && _f !== void 0 ? _f : new ConstantValue(1);", + " }", + " update(system, delta) {", + " if (EmitterMode.Random != this.mode) {", + " this.currentValue += this.speed.genValue(system.emissionState.time / system.duration) * delta;", + " }", + " }", + " initialize(p) {", + " const u = getValueFromEmitterMode(this.mode, this.currentValue, this.spread);", + " const v = Math.random();", + " const rand = THREE.MathUtils.lerp(1 - this.thickness, 1, Math.random());", + " const theta = u * this.arc;", + " const phi = Math.acos(v);", + " const sinTheta = Math.sin(theta);", + " const cosTheta = Math.cos(theta);", + " const sinPhi = Math.sin(phi);", + " const cosPhi = Math.cos(phi);", + " p.position.x = sinPhi * cosTheta;", + " p.position.y = sinPhi * sinTheta;", + " p.position.z = cosPhi;", + " p.velocity.copy(p.position).multiplyScalar(p.startSpeed);", + " p.position.multiplyScalar(this.radius * rand);", + " }", + " toJSON() {", + " return {", + " type: 'hemisphere',", + " radius: this.radius,", + " arc: this.arc,", + " thickness: this.thickness,", + " mode: this.mode,", + " spread: this.spread,", + " speed: this.speed.toJSON(),", + " };", + " }", + " static fromJSON(json) {", + " return new HemisphereEmitter({", + " radius: json.radius,", + " arc: json.arc,", + " thickness: json.thickness,", + " mode: json.mode,", + " speed: json.speed ? ValueGeneratorFromJSON(json.speed) : undefined,", + " spread: json.spread,", + " });", + " }", + " clone() {", + " return new HemisphereEmitter({", + " radius: this.radius,", + " arc: this.arc,", + " thickness: this.thickness,", + " mode: this.mode,", + " speed: this.speed.clone(),", + " spread: this.spread,", + " });", + " }", + "}", + "", + "class GridEmitter {", + " constructor(parameters = {}) {", + " var _a, _b, _c, _d;", + " this.type = 'grid';", + " this.width = (_a = parameters.width) !== null && _a !== void 0 ? _a : 1;", + " this.height = (_b = parameters.height) !== null && _b !== void 0 ? _b : 1;", + " this.column = (_c = parameters.column) !== null && _c !== void 0 ? _c : 10;", + " this.row = (_d = parameters.row) !== null && _d !== void 0 ? _d : 10;", + " }", + " initialize(p) {", + " const r = Math.floor(Math.random() * this.row);", + " const c = Math.floor(Math.random() * this.column);", + " p.position.x = (c * this.width) / this.column - this.width / 2;", + " p.position.y = (r * this.height) / this.row - this.height / 2;", + " p.position.z = 0;", + " p.velocity.set(0, 0, p.startSpeed);", + " }", + " toJSON() {", + " return {", + " type: 'grid',", + " width: this.width,", + " height: this.height,", + " column: this.column,", + " row: this.row,", + " };", + " }", + " static fromJSON(json) {", + " return new GridEmitter(json);", + " }", + " clone() {", + " return new GridEmitter({", + " width: this.width,", + " height: this.height,", + " column: this.column,", + " row: this.row,", + " });", + " }", + " update(system, delta) { }", + "}", + "", + "class MeshSurfaceEmitter {", + " get geometry() {", + " return this._geometry;", + " }", + " set geometry(geometry) {", + " this._geometry = geometry;", + " if (geometry === undefined) {", + " return;", + " }", + " if (typeof geometry === 'string') {", + " return;", + " }", + " const tri = new THREE.Triangle();", + " this._triangleIndexToArea.length = 0;", + " let area = 0;", + " if (!geometry.getIndex()) {", + " return;", + " }", + " const array = geometry.getIndex().array;", + " const triCount = array.length / 3;", + " this._triangleIndexToArea.push(0);", + " for (let i = 0; i < triCount; i++) {", + " tri.setFromAttributeAndIndices(geometry.getAttribute('position'), array[i * 3], array[i * 3 + 1], array[i * 3 + 2]);", + " area += tri.getArea();", + " this._triangleIndexToArea.push(area);", + " }", + " geometry.userData.triangleIndexToArea = this._triangleIndexToArea;", + " }", + " constructor(geometry) {", + " this.type = 'mesh_surface';", + " this._triangleIndexToArea = [];", + " this._tempA = new THREE.Vector3();", + " this._tempB = new THREE.Vector3();", + " this._tempC = new THREE.Vector3();", + " if (!geometry) {", + " return;", + " }", + " this.geometry = geometry;", + " }", + " initialize(p) {", + " const geometry = this._geometry;", + " if (!geometry || geometry.getIndex() === null) {", + " p.position.set(0, 0, 0);", + " p.velocity.set(0, 0, 1).multiplyScalar(p.startSpeed);", + " return;", + " }", + " const triCount = this._triangleIndexToArea.length - 1;", + " let left = 0, right = triCount;", + " const target = Math.random() * this._triangleIndexToArea[triCount];", + " while (left + 1 < right) {", + " const mid = Math.floor((left + right) / 2);", + " if (target < this._triangleIndexToArea[mid]) {", + " right = mid;", + " }", + " else {", + " left = mid;", + " }", + " }", + " let u1 = Math.random();", + " let u2 = Math.random();", + " if (u1 + u2 > 1) {", + " u1 = 1 - u1;", + " u2 = 1 - u2;", + " }", + " const index1 = geometry.getIndex().array[left * 3];", + " const index2 = geometry.getIndex().array[left * 3 + 1];", + " const index3 = geometry.getIndex().array[left * 3 + 2];", + " const positionBuffer = geometry.getAttribute('position');", + " this._tempA.fromBufferAttribute(positionBuffer, index1);", + " this._tempB.fromBufferAttribute(positionBuffer, index2);", + " this._tempC.fromBufferAttribute(positionBuffer, index3);", + " this._tempB.sub(this._tempA);", + " this._tempC.sub(this._tempA);", + " this._tempA.addScaledVector(this._tempB, u1).addScaledVector(this._tempC, u2);", + " p.position.copy(this._tempA);", + " this._tempA.copy(this._tempB).cross(this._tempC).normalize();", + " p.velocity.copy(this._tempA).normalize().multiplyScalar(p.startSpeed);", + " }", + " toJSON() {", + " return {", + " type: 'mesh_surface',", + " mesh: this._geometry ? this._geometry.uuid : '',", + " };", + " }", + " static fromJSON(json, meta) {", + " return new MeshSurfaceEmitter(meta.geometries[json.geometry]);", + " }", + " clone() {", + " return new MeshSurfaceEmitter(this._geometry);", + " }", + " update(system, delta) { }", + "}", + "", + "const EmitterShapes = {", + " circle: {", + " type: 'circle',", + " params: [", + " ['radius', 'number'],", + " ['arc', 'radian'],", + " ['thickness', 'number'],", + " ['mode', 'emitterMode'],", + " ['spread', 'number'],", + " ['speed', 'valueFunc'],", + " ],", + " constructor: CircleEmitter,", + " loadJSON: CircleEmitter.fromJSON,", + " },", + " cone: {", + " type: 'cone',", + " params: [", + " ['radius', 'number'],", + " ['arc', 'radian'],", + " ['thickness', 'number'],", + " ['angle', 'radian'],", + " ['mode', 'emitterMode'],", + " ['spread', 'number'],", + " ['speed', 'valueFunc'],", + " ],", + " constructor: ConeEmitter,", + " loadJSON: ConeEmitter.fromJSON,", + " },", + " donut: {", + " type: 'donut',", + " params: [", + " ['radius', 'number'],", + " ['arc', 'radian'],", + " ['thickness', 'number'],", + " ['donutRadius', 'number'],", + " ['mode', 'emitterMode'],", + " ['spread', 'number'],", + " ['speed', 'valueFunc'],", + " ],", + " constructor: DonutEmitter,", + " loadJSON: DonutEmitter.fromJSON,", + " },", + " point: { type: 'point', params: [], constructor: PointEmitter, loadJSON: PointEmitter.fromJSON },", + " sphere: {", + " type: 'sphere',", + " params: [", + " ['radius', 'number'],", + " ['arc', 'radian'],", + " ['thickness', 'number'],", + " ['angle', 'radian'],", + " ['mode', 'emitterMode'],", + " ['spread', 'number'],", + " ['speed', 'valueFunc'],", + " ],", + " constructor: SphereEmitter,", + " loadJSON: SphereEmitter.fromJSON,", + " },", + " hemisphere: {", + " type: 'hemisphere',", + " params: [", + " ['radius', 'number'],", + " ['arc', 'radian'],", + " ['thickness', 'number'],", + " ['angle', 'radian'],", + " ['mode', 'emitterMode'],", + " ['spread', 'number'],", + " ['speed', 'valueFunc'],", + " ],", + " constructor: HemisphereEmitter,", + " loadJSON: HemisphereEmitter.fromJSON,", + " },", + " grid: {", + " type: 'grid',", + " params: [", + " ['width', 'number'],", + " ['height', 'number'],", + " ['rows', 'number'],", + " ['column', 'number'],", + " ],", + " constructor: GridEmitter,", + " loadJSON: GridEmitter.fromJSON,", + " },", + " mesh_surface: {", + " type: 'mesh_surface',", + " params: [['geometry', 'geometry']],", + " constructor: MeshSurfaceEmitter,", + " loadJSON: MeshSurfaceEmitter.fromJSON,", + " },", + "};", + "function EmitterFromJSON(json, meta) {", + " return EmitterShapes[json.type].loadJSON(json, meta);", + "}", + "", + "let RenderMode = void 0;", + "(function (RenderMode) {", + " RenderMode[RenderMode[\"BillBoard\"] = 0] = \"BillBoard\";", + " RenderMode[RenderMode[\"StretchedBillBoard\"] = 1] = \"StretchedBillBoard\";", + " RenderMode[RenderMode[\"Mesh\"] = 2] = \"Mesh\";", + " RenderMode[RenderMode[\"Trail\"] = 3] = \"Trail\";", + " RenderMode[RenderMode[\"HorizontalBillBoard\"] = 4] = \"HorizontalBillBoard\";", + " RenderMode[RenderMode[\"VerticalBillBoard\"] = 5] = \"VerticalBillBoard\";", + "})(RenderMode || (RenderMode = {}));", + "class VFXBatch extends THREE.Mesh {", + " constructor(settings) {", + " super();", + " this.type = 'VFXBatch';", + " this.maxParticles = 1000;", + " this.systems = new Set();", + " const layers = new THREE.Layers();", + " layers.mask = settings.layers.mask;", + " const newMat = settings.material.clone();", + " newMat.defines = {};", + " Object.assign(newMat.defines, settings.material.defines);", + " this.settings = {", + " instancingGeometry: settings.instancingGeometry,", + " renderMode: settings.renderMode,", + " renderOrder: settings.renderOrder,", + " material: newMat,", + " uTileCount: settings.uTileCount,", + " vTileCount: settings.vTileCount,", + " layers: layers,", + " };", + " this.frustumCulled = false;", + " this.renderOrder = this.settings.renderOrder;", + " }", + " addSystem(system) {", + " this.systems.add(system);", + " }", + " removeSystem(system) {", + " this.systems.delete(system);", + " }", + "}", + "", + "const UP = new THREE.Vector3(0, 0, 1);", + "const tempQ$1 = new THREE.Quaternion();", + "const tempV$1 = new THREE.Vector3();", + "const tempV2$1 = new THREE.Vector3();", + "new THREE.Vector3();", + "const PREWARM_FPS$1 = 60;", + "const DEFAULT_GEOMETRY$1 = new THREE.PlaneGeometry(1, 1, 1, 1);", + "class ParticleSystem {", + " set time(time) {", + " this.emissionState.time = time;", + " }", + " get time() {", + " return this.emissionState.time;", + " }", + " get layers() {", + " return this.rendererSettings.layers;", + " }", + " get texture() {", + " return this.rendererSettings.material.map;", + " }", + " set texture(texture) {", + " this.rendererSettings.material.map = texture;", + " this.neededToUpdateRender = true;", + " }", + " get material() {", + " return this.rendererSettings.material;", + " }", + " set material(material) {", + " this.rendererSettings.material = material;", + " this.neededToUpdateRender = true;", + " }", + " get uTileCount() {", + " return this.rendererSettings.uTileCount;", + " }", + " set uTileCount(u) {", + " this.rendererSettings.uTileCount = u;", + " this.neededToUpdateRender = true;", + " }", + " get vTileCount() {", + " return this.rendererSettings.vTileCount;", + " }", + " set vTileCount(v) {", + " this.rendererSettings.vTileCount = v;", + " this.neededToUpdateRender = true;", + " }", + " get instancingGeometry() {", + " return this.rendererSettings.instancingGeometry;", + " }", + " set instancingGeometry(geometry) {", + " this.restart();", + " this.particles.length = 0;", + " this.rendererSettings.instancingGeometry = geometry;", + " this.neededToUpdateRender = true;", + " }", + " get renderMode() {", + " return this.rendererSettings.renderMode;", + " }", + " set renderMode(renderMode) {", + " if ((this.rendererSettings.renderMode != RenderMode.Trail && renderMode === RenderMode.Trail) ||", + " (this.rendererSettings.renderMode == RenderMode.Trail && renderMode !== RenderMode.Trail)) {", + " this.restart();", + " this.particles.length = 0;", + " }", + " if (this.rendererSettings.renderMode !== renderMode) {", + " switch (renderMode) {", + " case RenderMode.Trail:", + " this.rendererEmitterSettings = {", + " startLength: new ConstantValue(30),", + " followLocalOrigin: false,", + " };", + " break;", + " case RenderMode.Mesh:", + " this.rendererEmitterSettings = {", + " geometry: new THREE.PlaneGeometry(1, 1),", + " };", + " this.startRotation = new AxisAngleGenerator(new THREE.Vector3(0, 1, 0), new ConstantValue(0));", + " break;", + " case RenderMode.StretchedBillBoard:", + " this.rendererEmitterSettings = { speedFactor: 0, lengthFactor: 2 };", + " if (this.rendererSettings.renderMode === RenderMode.Mesh) {", + " this.startRotation = new ConstantValue(0);", + " }", + " break;", + " case RenderMode.BillBoard:", + " case RenderMode.VerticalBillBoard:", + " case RenderMode.HorizontalBillBoard:", + " this.rendererEmitterSettings = {};", + " if (this.rendererSettings.renderMode === RenderMode.Mesh) {", + " this.startRotation = new ConstantValue(0);", + " }", + " break;", + " }", + " }", + " this.rendererSettings.renderMode = renderMode;", + " this.neededToUpdateRender = true;", + " }", + " get renderOrder() {", + " return this.rendererSettings.renderOrder;", + " }", + " set renderOrder(renderOrder) {", + " this.rendererSettings.renderOrder = renderOrder;", + " this.neededToUpdateRender = true;", + " }", + " get blending() {", + " return this.rendererSettings.material.blending;", + " }", + " set blending(blending) {", + " this.rendererSettings.material.blending = blending;", + " this.neededToUpdateRender = true;", + " }", + " constructor(parameters) {", + " var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x;", + " this.temp = new THREE.Vector3();", + " this.travelDistance = 0;", + " this.normalMatrix = new THREE.Matrix3();", + " this.firstTimeUpdate = true;", + " this.autoDestroy = parameters.autoDestroy === undefined ? false : parameters.autoDestroy;", + " this.duration = (_a = parameters.duration) !== null && _a !== void 0 ? _a : 1;", + " this.looping = parameters.looping === undefined ? true : parameters.looping;", + " this.prewarm = parameters.prewarm === undefined ? false : parameters.prewarm;", + " this.startLife = (_b = parameters.startLife) !== null && _b !== void 0 ? _b : new ConstantValue(5);", + " this.startSpeed = (_c = parameters.startSpeed) !== null && _c !== void 0 ? _c : new ConstantValue(0);", + " this.startRotation = (_d = parameters.startRotation) !== null && _d !== void 0 ? _d : new ConstantValue(0);", + " this.startSize = (_e = parameters.startSize) !== null && _e !== void 0 ? _e : new ConstantValue(1);", + " this.startColor = (_f = parameters.startColor) !== null && _f !== void 0 ? _f : new ConstantColor(new THREE.Vector4(1, 1, 1, 1));", + " this.emissionOverTime = (_g = parameters.emissionOverTime) !== null && _g !== void 0 ? _g : new ConstantValue(10);", + " this.emissionOverDistance = (_h = parameters.emissionOverDistance) !== null && _h !== void 0 ? _h : new ConstantValue(0);", + " this.emissionBursts = (_j = parameters.emissionBursts) !== null && _j !== void 0 ? _j : [];", + " this.onlyUsedByOther = (_k = parameters.onlyUsedByOther) !== null && _k !== void 0 ? _k : false;", + " this.emitterShape = (_l = parameters.shape) !== null && _l !== void 0 ? _l : new SphereEmitter();", + " this.behaviors = (_m = parameters.behaviors) !== null && _m !== void 0 ? _m : new Array();", + " this.worldSpace = (_o = parameters.worldSpace) !== null && _o !== void 0 ? _o : false;", + " this.rendererEmitterSettings = (_p = parameters.rendererEmitterSettings) !== null && _p !== void 0 ? _p : {};", + " if (parameters.renderMode === RenderMode.StretchedBillBoard) {", + " const stretchedBillboardSettings = this.rendererEmitterSettings;", + " if (parameters.speedFactor !== undefined) {", + " stretchedBillboardSettings.speedFactor = parameters.speedFactor;", + " }", + " stretchedBillboardSettings.speedFactor = (_q = stretchedBillboardSettings.speedFactor) !== null && _q !== void 0 ? _q : 0;", + " stretchedBillboardSettings.lengthFactor = (_r = stretchedBillboardSettings.lengthFactor) !== null && _r !== void 0 ? _r : 0;", + " }", + " this.rendererSettings = {", + " instancingGeometry: (_s = parameters.instancingGeometry) !== null && _s !== void 0 ? _s : DEFAULT_GEOMETRY$1,", + " renderMode: (_t = parameters.renderMode) !== null && _t !== void 0 ? _t : RenderMode.BillBoard,", + " renderOrder: (_u = parameters.renderOrder) !== null && _u !== void 0 ? _u : 0,", + " material: parameters.material,", + " uTileCount: (_v = parameters.uTileCount) !== null && _v !== void 0 ? _v : 1,", + " vTileCount: (_w = parameters.vTileCount) !== null && _w !== void 0 ? _w : 1,", + " layers: (_x = parameters.layers) !== null && _x !== void 0 ? _x : new THREE.Layers(),", + " };", + " this.neededToUpdateRender = true;", + " this.particles = new Array();", + " this.startTileIndex = parameters.startTileIndex || new ConstantValue(0);", + " this.emitter = new ParticleEmitter(this);", + " this.paused = false;", + " this.particleNum = 0;", + " this.emissionState = {", + " burstIndex: 0,", + " burstWaveIndex: 0,", + " time: 0,", + " waitEmiting: 0,", + " travelDistance: 0,", + " };", + " this.emitEnded = false;", + " this.markForDestroy = false;", + " this.prewarmed = false;", + " }", + " pause() {", + " this.paused = true;", + " }", + " play() {", + " this.paused = false;", + " }", + " spawn(count, emissionState, matrix) {", + " tempQ$1.setFromRotationMatrix(matrix);", + " const translation = tempV$1;", + " const quaternion = tempQ$1;", + " const scale = tempV2$1;", + " matrix.decompose(translation, quaternion, scale);", + " for (let i = 0; i < count; i++) {", + " this.particleNum++;", + " while (this.particles.length < this.particleNum) {", + " if (this.rendererSettings.renderMode === RenderMode.Trail) {", + " this.particles.push(new TrailParticle());", + " }", + " else {", + " this.particles.push(new SpriteParticle());", + " }", + " }", + " const particle = this.particles[this.particleNum - 1];", + " particle.speedModifier = 1;", + " this.startColor.genColor(particle.startColor, this.emissionState.time, {});", + " particle.color.copy(particle.startColor);", + " particle.startSpeed = this.startSpeed.genValue(emissionState.time / this.duration);", + " particle.life = this.startLife.genValue(emissionState.time / this.duration);", + " particle.age = 0;", + " particle.startSize = this.startSize.genValue(emissionState.time / this.duration);", + " particle.uvTile = Math.floor(this.startTileIndex.genValue() + 0.001);", + " particle.size = particle.startSize;", + " if (this.rendererSettings.renderMode === RenderMode.Mesh ||", + " this.rendererSettings.renderMode === RenderMode.BillBoard ||", + " this.rendererSettings.renderMode === RenderMode.VerticalBillBoard ||", + " this.rendererSettings.renderMode === RenderMode.HorizontalBillBoard ||", + " this.rendererSettings.renderMode === RenderMode.StretchedBillBoard) {", + " const sprite = particle;", + " if (this.rendererSettings.renderMode === RenderMode.Mesh) {", + " if (!(sprite.rotation instanceof THREE.Quaternion)) {", + " sprite.rotation = new THREE.Quaternion();", + " }", + " if (this.startRotation.type === 'rotation') {", + " this.startRotation.genValue(sprite.rotation, emissionState.time / this.duration);", + " }", + " else {", + " sprite.rotation.setFromAxisAngle(UP, this.startRotation.genValue((emissionState.time / this.duration)));", + " }", + " }", + " else {", + " if (this.startRotation.type === 'rotation') {", + " sprite.rotation = 0;", + " }", + " else {", + " sprite.rotation = this.startRotation.genValue(emissionState.time / this.duration);", + " }", + " }", + " }", + " else if (this.rendererSettings.renderMode === RenderMode.Trail) {", + " const trail = particle;", + " trail.length = this.rendererEmitterSettings.startLength.genValue(emissionState.time / this.duration);", + " }", + " this.emitterShape.initialize(particle);", + " if (this.rendererSettings.renderMode === RenderMode.Trail &&", + " this.rendererEmitterSettings.followLocalOrigin) {", + " const trail = particle;", + " trail.localPosition = new THREE.Vector3().copy(trail.position);", + " }", + " if (this.worldSpace) {", + " particle.position.applyMatrix4(matrix);", + " particle.startSize =", + " (particle.startSize * (Math.abs(scale.x) + Math.abs(scale.y) + Math.abs(scale.z))) / 3;", + " particle.size = particle.startSize;", + " particle.velocity.multiply(scale).applyMatrix3(this.normalMatrix);", + " if (particle.rotation && particle.rotation instanceof THREE.Quaternion) {", + " particle.rotation.multiplyQuaternions(tempQ$1, particle.rotation);", + " }", + " }", + " else {", + " if (this.onlyUsedByOther) {", + " particle.parentMatrix = matrix;", + " }", + " }", + " for (let j = 0; j < this.behaviors.length; j++) {", + " this.behaviors[j].initialize(particle);", + " }", + " }", + " }", + " endEmit() {", + " this.emitEnded = true;", + " if (this.autoDestroy) {", + " this.markForDestroy = true;", + " }", + " }", + " dispose() {", + " if (this._renderer)", + " this._renderer.deleteSystem(this);", + " this.emitter.dispose();", + " if (this.emitter.parent)", + " this.emitter.parent.remove(this.emitter);", + " }", + " restart() {", + " this.paused = false;", + " this.particleNum = 0;", + " this.emissionState.burstIndex = 0;", + " this.emissionState.burstWaveIndex = 0;", + " this.emissionState.time = 0;", + " this.emissionState.waitEmiting = 0;", + " this.behaviors.forEach((behavior) => {", + " behavior.reset();", + " });", + " this.emitEnded = false;", + " this.markForDestroy = false;", + " this.prewarmed = false;", + " }", + " update(delta) {", + " if (this.paused)", + " return;", + " let currentParent = this.emitter;", + " while (currentParent.parent) {", + " currentParent = currentParent.parent;", + " }", + " if (currentParent.type !== 'Scene') {", + " this.dispose();", + " return;", + " }", + " if (this.firstTimeUpdate) {", + " this.firstTimeUpdate = false;", + " this.emitter.updateWorldMatrix(true, false);", + " }", + " if (this.emitEnded && this.particleNum === 0) {", + " if (this.markForDestroy && this.emitter.parent)", + " this.dispose();", + " return;", + " }", + " if (this.looping && this.prewarm && !this.prewarmed) {", + " this.prewarmed = true;", + " for (let i = 0; i < this.duration * PREWARM_FPS$1; i++) {", + " this.update(1.0 / PREWARM_FPS$1);", + " }", + " }", + " if (delta > 0.1) {", + " delta = 0.1;", + " }", + " if (this.neededToUpdateRender) {", + " if (this._renderer)", + " this._renderer.updateSystem(this);", + " this.neededToUpdateRender = false;", + " }", + " if (!this.onlyUsedByOther) {", + " this.emit(delta, this.emissionState, this.emitter.matrixWorld);", + " }", + " this.emitterShape.update(this, delta);", + " for (let j = 0; j < this.behaviors.length; j++) {", + " for (let i = 0; i < this.particleNum; i++) {", + " if (!this.particles[i].died) {", + " this.behaviors[j].update(this.particles[i], delta);", + " }", + " }", + " this.behaviors[j].frameUpdate(delta);", + " }", + " for (let i = 0; i < this.particleNum; i++) {", + " if (this.rendererEmitterSettings.followLocalOrigin &&", + " this.particles[i].localPosition) {", + " this.particles[i].position.copy(this.particles[i].localPosition);", + " if (this.particles[i].parentMatrix) {", + " this.particles[i].position.applyMatrix4(this.particles[i].parentMatrix);", + " }", + " else {", + " this.particles[i].position.applyMatrix4(this.emitter.matrixWorld);", + " }", + " }", + " else {", + " this.particles[i].position.addScaledVector(this.particles[i].velocity, delta * this.particles[i].speedModifier);", + " }", + " this.particles[i].age += delta;", + " }", + " if (this.rendererSettings.renderMode === RenderMode.Trail) {", + " for (let i = 0; i < this.particleNum; i++) {", + " const particle = this.particles[i];", + " particle.update();", + " }", + " }", + " for (let i = 0; i < this.particleNum; i++) {", + " const particle = this.particles[i];", + " if (particle.died && (!(particle instanceof TrailParticle) || particle.previous.length === 0)) {", + " this.particles[i] = this.particles[this.particleNum - 1];", + " this.particles[this.particleNum - 1] = particle;", + " this.particleNum--;", + " i--;", + " }", + " }", + " }", + " emit(delta, emissionState, emitterMatrix) {", + " if (emissionState.time > this.duration) {", + " if (this.looping) {", + " emissionState.time -= this.duration;", + " emissionState.burstIndex = 0;", + " this.behaviors.forEach((behavior) => {", + " behavior.reset();", + " });", + " }", + " else {", + " if (!this.emitEnded && !this.onlyUsedByOther) {", + " this.endEmit();", + " }", + " }", + " }", + " this.normalMatrix.getNormalMatrix(emitterMatrix);", + " const totalSpawn = Math.ceil(emissionState.waitEmiting);", + " this.spawn(totalSpawn, emissionState, emitterMatrix);", + " emissionState.waitEmiting -= totalSpawn;", + " while (emissionState.burstIndex < this.emissionBursts.length &&", + " this.emissionBursts[emissionState.burstIndex].time <= emissionState.time) {", + " if (Math.random() < this.emissionBursts[emissionState.burstIndex].probability) {", + " const count = this.emissionBursts[emissionState.burstIndex].count.genValue(this.time);", + " this.spawn(count, emissionState, emitterMatrix);", + " }", + " emissionState.burstIndex++;", + " }", + " if (!this.emitEnded) {", + " emissionState.waitEmiting += delta * this.emissionOverTime.genValue(emissionState.time / this.duration);", + " if (emissionState.previousWorldPos != undefined) {", + " this.temp.set(emitterMatrix.elements[12], emitterMatrix.elements[13], emitterMatrix.elements[14]);", + " emissionState.travelDistance += emissionState.previousWorldPos.distanceTo(this.temp);", + " const emitPerMeter = this.emissionOverDistance.genValue(emissionState.time / this.duration);", + " if (emissionState.travelDistance * emitPerMeter > 0) {", + " const count = Math.floor(emissionState.travelDistance * emitPerMeter);", + " emissionState.travelDistance -= count / emitPerMeter;", + " emissionState.waitEmiting += count;", + " }", + " }", + " }", + " if (emissionState.previousWorldPos === undefined)", + " emissionState.previousWorldPos = new THREE.Vector3();", + " emissionState.previousWorldPos.set(emitterMatrix.elements[12], emitterMatrix.elements[13], emitterMatrix.elements[14]);", + " emissionState.time += delta;", + " }", + " toJSON(meta, options = {}) {", + " const isRootObject = meta === undefined || typeof meta === 'string';", + " if (isRootObject) {", + " meta = {", + " geometries: {},", + " materials: {},", + " textures: {},", + " images: {},", + " shapes: {},", + " skeletons: {},", + " animations: {},", + " nodes: {},", + " };", + " }", + " meta.materials[this.rendererSettings.material.uuid] = this.rendererSettings.material.toJSON(meta);", + " if (options.useUrlForImage) {", + " if (this.texture.source !== undefined) {", + " const image = this.texture.source;", + " meta.images[image.uuid] = {", + " uuid: image.uuid,", + " url: this.texture.image.url,", + " };", + " }", + " }", + " let rendererSettingsJSON;", + " if (this.renderMode === RenderMode.Trail) {", + " rendererSettingsJSON = {", + " startLength: this.rendererEmitterSettings.startLength.toJSON(),", + " followLocalOrigin: this.rendererEmitterSettings.followLocalOrigin,", + " };", + " }", + " else if (this.renderMode === RenderMode.Mesh) {", + " rendererSettingsJSON = {};", + " }", + " else if (this.renderMode === RenderMode.StretchedBillBoard) {", + " rendererSettingsJSON = {", + " speedFactor: this.rendererEmitterSettings.speedFactor,", + " lengthFactor: this.rendererEmitterSettings.lengthFactor,", + " };", + " }", + " else {", + " rendererSettingsJSON = {};", + " }", + " const geometry = this.rendererSettings.instancingGeometry;", + " if (meta.geometries && !meta.geometries[geometry.uuid]) {", + " meta.geometries[geometry.uuid] = geometry.toJSON();", + " }", + " return {", + " version: '2.0',", + " autoDestroy: this.autoDestroy,", + " looping: this.looping,", + " prewarm: this.prewarm,", + " duration: this.duration,", + " shape: this.emitterShape.toJSON(),", + " startLife: this.startLife.toJSON(),", + " startSpeed: this.startSpeed.toJSON(),", + " startRotation: this.startRotation.toJSON(),", + " startSize: this.startSize.toJSON(),", + " startColor: this.startColor.toJSON(),", + " emissionOverTime: this.emissionOverTime.toJSON(),", + " emissionOverDistance: this.emissionOverDistance.toJSON(),", + " emissionBursts: this.emissionBursts.map((burst) => ({", + " time: burst.time,", + " count: burst.count.toJSON(),", + " probability: burst.probability,", + " interval: burst.interval,", + " cycle: burst.cycle,", + " })),", + " onlyUsedByOther: this.onlyUsedByOther,", + " instancingGeometry: this.rendererSettings.instancingGeometry.uuid,", + " renderOrder: this.renderOrder,", + " renderMode: this.renderMode,", + " rendererEmitterSettings: rendererSettingsJSON,", + " material: this.rendererSettings.material.uuid,", + " layers: this.layers.mask,", + " startTileIndex: this.startTileIndex.toJSON(),", + " uTileCount: this.uTileCount,", + " vTileCount: this.vTileCount,", + " behaviors: this.behaviors.map((behavior) => behavior.toJSON()),", + " worldSpace: this.worldSpace,", + " };", + " }", + " static fromJSON(json, meta, dependencies) {", + " var _a, _b;", + " const shape = EmitterFromJSON(json.shape, meta);", + " let rendererEmitterSettings;", + " if (json.renderMode === RenderMode.Trail) {", + " let trailSettings = json.rendererEmitterSettings;", + " rendererEmitterSettings = {", + " startLength: trailSettings.startLength != undefined", + " ? ValueGeneratorFromJSON(trailSettings.startLength)", + " : new ConstantValue(30),", + " followLocalOrigin: trailSettings.followLocalOrigin,", + " };", + " }", + " else if (json.renderMode === RenderMode.Mesh) {", + " rendererEmitterSettings = {};", + " }", + " else if (json.renderMode === RenderMode.StretchedBillBoard) {", + " rendererEmitterSettings = json.rendererEmitterSettings;", + " if (json.speedFactor != undefined) {", + " rendererEmitterSettings.speedFactor = json.speedFactor;", + " }", + " }", + " else {", + " rendererEmitterSettings = {};", + " }", + " const layers = new THREE.Layers();", + " if (json.layers) {", + " layers.mask = json.layers;", + " }", + " const ps = new ParticleSystem({", + " autoDestroy: json.autoDestroy,", + " looping: json.looping,", + " prewarm: json.prewarm,", + " duration: json.duration,", + " shape: shape,", + " startLife: ValueGeneratorFromJSON(json.startLife),", + " startSpeed: ValueGeneratorFromJSON(json.startSpeed),", + " startRotation: GeneratorFromJSON(json.startRotation),", + " startSize: ValueGeneratorFromJSON(json.startSize),", + " startColor: ColorGeneratorFromJSON(json.startColor),", + " emissionOverTime: ValueGeneratorFromJSON(json.emissionOverTime),", + " emissionOverDistance: ValueGeneratorFromJSON(json.emissionOverDistance),", + " emissionBursts: (_a = json.emissionBursts) === null || _a === void 0 ? void 0 : _a.map((burst) => ({", + " time: burst.time,", + " count: typeof burst.count === 'number'", + " ? new ConstantValue(burst.count)", + " : ValueGeneratorFromJSON(burst.count),", + " probability: burst.probability,", + " interval: burst.interval,", + " cycle: burst.cycle,", + " })),", + " onlyUsedByOther: json.onlyUsedByOther,", + " instancingGeometry: meta.geometries[json.instancingGeometry],", + " renderMode: json.renderMode,", + " rendererEmitterSettings: rendererEmitterSettings,", + " renderOrder: json.renderOrder,", + " layers: layers,", + " material: json.material", + " ? meta.materials[json.material]", + " : json.texture", + " ? new THREE.MeshBasicMaterial({", + " map: meta.textures[json.texture],", + " transparent: (_b = json.transparent) !== null && _b !== void 0 ? _b : true,", + " blending: json.blending,", + " side: THREE.DoubleSide,", + " })", + " : new THREE.MeshBasicMaterial({", + " color: 0xffffff,", + " transparent: true,", + " blending: THREE.AdditiveBlending,", + " side: THREE.DoubleSide,", + " }),", + " startTileIndex: typeof json.startTileIndex === 'number'", + " ? new ConstantValue(json.startTileIndex)", + " : ValueGeneratorFromJSON(json.startTileIndex),", + " uTileCount: json.uTileCount,", + " vTileCount: json.vTileCount,", + " behaviors: [],", + " worldSpace: json.worldSpace,", + " });", + " ps.behaviors = json.behaviors.map((behaviorJson) => {", + " const behavior = BehaviorFromJSON(behaviorJson, ps);", + " if (behavior.type === 'EmitSubParticleSystem') {", + " dependencies[behaviorJson.subParticleSystem] = behavior;", + " }", + " return behavior;", + " });", + " return ps;", + " }", + " addBehavior(behavior) {", + " this.behaviors.push(behavior);", + " }", + " getRendererSettings() {", + " return this.rendererSettings;", + " }", + " clone() {", + " const newEmissionBursts = [];", + " for (const emissionBurst of this.emissionBursts) {", + " const newEmissionBurst = {};", + " Object.assign(newEmissionBurst, emissionBurst);", + " newEmissionBursts.push(newEmissionBurst);", + " }", + " const newBehaviors = [];", + " for (const behavior of this.behaviors) {", + " newBehaviors.push(behavior.clone());", + " }", + " let rendererEmitterSettings;", + " if (this.renderMode === RenderMode.Trail) {", + " rendererEmitterSettings = {", + " startLength: this.rendererEmitterSettings.startLength.clone(),", + " followLocalOrigin: this.rendererEmitterSettings.followLocalOrigin,", + " };", + " }", + " else {", + " rendererEmitterSettings = {};", + " }", + " const layers = new THREE.Layers();", + " layers.mask = this.layers.mask;", + " return new ParticleSystem({", + " autoDestroy: this.autoDestroy,", + " looping: this.looping,", + " duration: this.duration,", + " shape: this.emitterShape.clone(),", + " startLife: this.startLife.clone(),", + " startSpeed: this.startSpeed.clone(),", + " startRotation: this.startRotation.clone(),", + " startSize: this.startSize.clone(),", + " startColor: this.startColor.clone(),", + " emissionOverTime: this.emissionOverTime.clone(),", + " emissionOverDistance: this.emissionOverDistance.clone(),", + " emissionBursts: newEmissionBursts,", + " onlyUsedByOther: this.onlyUsedByOther,", + " instancingGeometry: this.rendererSettings.instancingGeometry,", + " renderMode: this.renderMode,", + " renderOrder: this.renderOrder,", + " rendererEmitterSettings: rendererEmitterSettings,", + " material: this.rendererSettings.material,", + " startTileIndex: this.startTileIndex,", + " uTileCount: this.uTileCount,", + " vTileCount: this.vTileCount,", + " behaviors: newBehaviors,", + " worldSpace: this.worldSpace,", + " layers: layers,", + " });", + " }", + "}", + "", + "var particle_frag = `", + "", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "", + "void main() {", + "", + " #include ", + " ", + " vec3 outgoingLight = vec3( 0.0 );", + " vec4 diffuseColor = vColor;", + " ", + " #include ", + " ", + " #ifdef USE_MAP", + " diffuseColor *= texture2D( map, vMapUv);", + " #endif", + " ", + " #include ", + "", + " outgoingLight = diffuseColor.rgb;", + " ", + " #ifdef USE_COLOR_AS_ALPHA", + " gl_FragColor = vec4( outgoingLight, diffuseColor.r );", + " #else", + " gl_FragColor = vec4( outgoingLight, diffuseColor.a );", + " #endif", + " ", + " ", + " #include ", + "", + "}", + "`;", + "", + "var particle_physics_frag = `", + "#define STANDARD", + "#ifdef PHYSICAL", + "#define IOR", + "#define SPECULAR", + "#endif", + "uniform vec3 diffuse;", + "uniform vec3 emissive;", + "uniform float roughness;", + "uniform float metalness;", + "uniform float opacity;", + "#ifdef IOR", + "uniform float ior;", + "#endif", + "#ifdef SPECULAR", + "uniform float specularIntensity;", + "uniform vec3 specularColor;", + "#ifdef USE_SPECULARINTENSITYMAP", + "uniform sampler2D specularIntensityMap;", + "#endif", + "#ifdef USE_SPECULARCOLORMAP", + "uniform sampler2D specularColorMap;", + "#endif", + "#endif", + "#ifdef USE_CLEARCOAT", + "uniform float clearcoat;", + "uniform float clearcoatRoughness;", + "#endif", + "#ifdef USE_IRIDESCENCE", + "uniform float iridescence;", + "uniform float iridescenceIOR;", + "uniform float iridescenceThicknessMinimum;", + "uniform float iridescenceThicknessMaximum;", + "#endif", + "#ifdef USE_SHEEN", + "uniform vec3 sheenColor;", + "uniform float sheenRoughness;", + "#ifdef USE_SHEENCOLORMAP", + "uniform sampler2D sheenColorMap;", + "#endif", + "#ifdef USE_SHEENROUGHNESSMAP", + "uniform sampler2D sheenRoughnessMap;", + "#endif", + "#endif", + "", + "varying vec3 vViewPosition;", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "", + "void main() {", + " #include ", + " vec4 diffuseColor = vec4( diffuse, opacity );", + " ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );", + " vec3 totalEmissiveRadiance = emissive;", + " #include ", + " #include ", + " #include ", + " #include ", + " #include ", + " #include ", + " #include ", + " #include ", + " #include ", + " #include ", + " #include ", + " #include ", + " // accumulation", + " #include ", + " #include ", + " #include ", + " #include ", + " // modulation", + " #include ", + " vec3 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;", + " vec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;", + " #include ", + " vec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;", + " #ifdef USE_SHEEN", + " // Sheen energy compensation approximation calculation can be found at the end of", + " // https://drive.google.com/file/d/1T0D1VSyR4AllqIJTQAraEIzjlb5h4FKH/view?usp=sharing", + " float sheenEnergyComp = 1.0 - 0.157 * max3( material.sheenColor );", + " outgoingLight = outgoingLight * sheenEnergyComp + sheenSpecular;", + " #endif", + " #ifdef USE_CLEARCOAT", + " float dotNVcc = saturate( dot( geometry.clearcoatNormal, geometry.viewDir ) );", + " vec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );", + " outgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + clearcoatSpecular * material.clearcoat;", + " #endif", + " #include ", + " #include ", + " #include ", + " #include ", + " #include ", + " #include ", + "}`;", + "", + "var uv_vertex_tile = `", + "", + " #ifdef UV_TILE", + " float col = mod(uvTile, tileCount.x);", + " float row = (tileCount.y - floor(uvTile / tileCount.x) - 1.0);", + " ", + " mat3 tileTransform = mat3(", + " 1.0 / tileCount.x, 0.0, 0.0,", + " 0.0, 1.0 / tileCount.y, 0.0, ", + " col / tileCount.x, row / tileCount.y, 1.0);", + " #else", + " mat3 tileTransform = mat3(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0);", + " #endif", + "", + "#if defined( USE_UV ) || defined( USE_ANISOTROPY )", + "", + "vUv = (tileTransform *vec3( uv, 1 )).xy;", + "", + "#endif", + "#ifdef USE_MAP", + "", + "vMapUv = ( tileTransform * (mapTransform * vec3( MAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_ALPHAMAP", + "", + "vAlphaMapUv = ( tileTransform * (alphaMapTransform * vec3( ALPHAMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_LIGHTMAP", + "", + "vLightMapUv = ( tileTransform * (lightMapTransform * vec3( LIGHTMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_AOMAP", + "", + "vAoMapUv = ( tileTransform * (aoMapTransform * vec3( AOMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_BUMPMAP", + "", + "vBumpMapUv = ( tileTransform * (bumpMapTransform * vec3( BUMPMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_NORMALMAP", + "", + "vNormalMapUv = ( tileTransform * (normalMapTransform * vec3( NORMALMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_DISPLACEMENTMAP", + "", + "vDisplacementMapUv = ( tileTransform * (displacementMapTransform * vec3( DISPLACEMENTMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_EMISSIVEMAP", + "", + "vEmissiveMapUv = ( tileTransform * (emissiveMapTransform * vec3( EMISSIVEMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_METALNESSMAP", + "", + "vMetalnessMapUv = ( tileTransform * (metalnessMapTransform * vec3( METALNESSMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_ROUGHNESSMAP", + "", + "vRoughnessMapUv = ( tileTransform * (roughnessMapTransform * vec3( ROUGHNESSMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_ANISOTROPYMAP", + "", + "vAnisotropyMapUv = ( tileTransform * (anisotropyMapTransform * vec3( ANISOTROPYMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_CLEARCOATMAP", + "", + "vClearcoatMapUv = ( tileTransform * (clearcoatMapTransform * vec3( CLEARCOATMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_CLEARCOAT_NORMALMAP", + "", + "vClearcoatNormalMapUv = ( tileTransform * (clearcoatNormalMapTransform * vec3( CLEARCOAT_NORMALMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_CLEARCOAT_ROUGHNESSMAP", + "", + "vClearcoatRoughnessMapUv = ( tileTransform * (clearcoatRoughnessMapTransform * vec3( CLEARCOAT_ROUGHNESSMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_IRIDESCENCEMAP", + "", + "vIridescenceMapUv = ( tileTransform * (iridescenceMapTransform * vec3( IRIDESCENCEMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_IRIDESCENCE_THICKNESSMAP", + "", + "vIridescenceThicknessMapUv = ( tileTransform * (iridescenceThicknessMapTransform * vec3( IRIDESCENCE_THICKNESSMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_SHEEN_COLORMAP", + "", + "vSheenColorMapUv = ( tileTransform * (sheenColorMapTransform * vec3( SHEEN_COLORMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_SHEEN_ROUGHNESSMAP", + "", + "vSheenRoughnessMapUv = ( tileTransform * (sheenRoughnessMapTransform * vec3( SHEEN_ROUGHNESSMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_SPECULARMAP", + "", + "vSpecularMapUv = ( tileTransform * (specularMapTransform * vec3( SPECULARMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_SPECULAR_COLORMAP", + "", + "vSpecularColorMapUv = ( tileTransform * (specularColorMapTransform * vec3( SPECULAR_COLORMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_SPECULAR_INTENSITYMAP", + "", + "vSpecularIntensityMapUv = ( tileTransform * (specularIntensityMapTransform * vec3( SPECULAR_INTENSITYMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_TRANSMISSIONMAP", + "", + "vTransmissionMapUv = ( tileTransform * transmissionMapTransform * vec3( TRANSMISSIONMAP_UV, 1 ) )).xy;", + "", + "#endif", + "#ifdef USE_THICKNESSMAP", + "", + "vThicknessMapUv = ( tileTransform * thicknessMapTransform * vec3( THICKNESSMAP_UV, 1 ) )).xy;", + "", + "#endif", + "`;", + "", + "var particle_vert = `", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "", + "attribute vec3 offset;", + "attribute float rotation;", + "attribute float size;", + "attribute float uvTile;", + "", + "#ifdef UV_TILE", + "uniform vec2 tileCount;", + "#endif", + "", + "void main() {", + "", + " ${uv_vertex_tile}", + "\t", + " vec2 alignedPosition = ( position.xy ) * size;", + " ", + " vec2 rotatedPosition;", + " rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;", + " rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;", + "#ifdef HORIZONTAL", + " vec4 mvPosition = modelMatrix * vec4( offset, 1.0 );", + " mvPosition.x += rotatedPosition.x;", + " mvPosition.z -= rotatedPosition.y;", + " mvPosition = viewMatrix * mvPosition;", + "#elif defined(VERTICAL)", + " vec4 mvPosition = modelMatrix * vec4( offset, 1.0 );", + " mvPosition.y += rotatedPosition.y;", + " mvPosition = viewMatrix * mvPosition;", + " mvPosition.x += rotatedPosition.x;", + "#else", + " vec4 mvPosition = modelViewMatrix * vec4( offset, 1.0 );", + " mvPosition.xy += rotatedPosition;", + "#endif", + "", + "\tvColor = color;", + "", + "\tgl_Position = projectionMatrix * mvPosition;", + "", + "\t#include ", + "\t#include ", + "", + "}", + "`;", + "", + "var local_particle_vert = `", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "", + "attribute vec3 offset;", + "attribute vec4 rotation;", + "attribute float size;", + "// attribute vec4 color;", + "attribute float uvTile;", + "", + "#ifdef UV_TILE", + "uniform vec2 tileCount;", + "#endif", + "", + "void main() {", + "", + " ${uv_vertex_tile}", + " ", + " float x2 = rotation.x + rotation.x, y2 = rotation.y + rotation.y, z2 = rotation.z + rotation.z;", + " float xx = rotation.x * x2, xy = rotation.x * y2, xz = rotation.x * z2;", + " float yy = rotation.y * y2, yz = rotation.y * z2, zz = rotation.z * z2;", + " float wx = rotation.w * x2, wy = rotation.w * y2, wz = rotation.w * z2;", + " float sx = size, sy = size, sz = size;", + " ", + " mat4 matrix = mat4(( 1.0 - ( yy + zz ) ) * sx, ( xy + wz ) * sx, ( xz - wy ) * sx, 0.0, // 1. column", + " ( xy - wz ) * sy, ( 1.0 - ( xx + zz ) ) * sy, ( yz + wx ) * sy, 0.0, // 2. column", + " ( xz + wy ) * sz, ( yz - wx ) * sz, ( 1.0 - ( xx + yy ) ) * sz, 0.0, // 3. column", + " offset.x, offset.y, offset.z, 1.0);", + " ", + " vec4 mvPosition = modelViewMatrix * (matrix * vec4( position, 1.0 ));", + "", + "\tvColor = color;", + "", + "\tgl_Position = projectionMatrix * mvPosition;", + "", + "\t#include ", + "\t#include ", + "", + "}", + "`;", + "", + "var local_particle_physics_vert = `", + "#define STANDARD", + "varying vec3 vViewPosition;", + "#ifdef USE_TRANSMISSION", + "\tvarying vec3 vWorldPosition;", + "#endif", + "#include ", + "#include ", + "", + "attribute vec3 offset;", + "attribute vec4 rotation;", + "attribute float size;", + "attribute float uvTile;", + "", + "#ifdef UV_TILE", + "uniform vec2 tileCount;", + "#endif", + "", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "", + "void main() {", + " ${uv_vertex_tile}", + "", + " float x2 = rotation.x + rotation.x, y2 = rotation.y + rotation.y, z2 = rotation.z + rotation.z;", + " float xx = rotation.x * x2, xy = rotation.x * y2, xz = rotation.x * z2;", + " float yy = rotation.y * y2, yz = rotation.y * z2, zz = rotation.z * z2;", + " float wx = rotation.w * x2, wy = rotation.w * y2, wz = rotation.w * z2;", + " float sx = size, sy = size, sz = size;", + "", + " mat4 particleMatrix = mat4(( 1.0 - ( yy + zz ) ) * sx, ( xy + wz ) * sx, ( xz - wy ) * sx, 0.0, // 1. column", + " ( xy - wz ) * sy, ( 1.0 - ( xx + zz ) ) * sy, ( yz + wx ) * sy, 0.0, // 2. column", + " ( xz + wy ) * sz, ( yz - wx ) * sz, ( 1.0 - ( xx + yy ) ) * sz, 0.0, // 3. column", + " offset.x, offset.y, offset.z, 1.0);", + "", + "\t#include ", + "\t#include ", + "\t#include ", + "\t#include ", + "\t#include ", + "\t#include ", + "", + "\t// replace defaultnormal_vertex", + "\tvec3 transformedNormal = objectNormal;", + " mat3 m = mat3( particleMatrix );", + " transformedNormal /= vec3( dot( m[ 0 ], m[ 0 ] ), dot( m[ 1 ], m[ 1 ] ), dot( m[ 2 ], m[ 2 ] ) );", + " transformedNormal = m * transformedNormal;", + " transformedNormal = normalMatrix * transformedNormal;", + " #ifdef FLIP_SIDED", + " transformedNormal = - transformedNormal;", + " #endif", + " #ifdef USE_TANGENT", + " vec3 transformedTangent = ( modelViewMatrix * vec4( objectTangent, 0.0 ) ).xyz;", + " #ifdef FLIP_SIDED", + " transformedTangent = - transformedTangent;", + " #endif", + " #endif", + "", + "\t#include ", + "\t#include ", + "\t#include ", + "\t#include ", + "\t#include ", + "", + "\t// replace include ", + " vec4 mvPosition = vec4( transformed, 1.0 );", + " mvPosition = modelViewMatrix * (particleMatrix * mvPosition);", + "\tgl_Position = projectionMatrix * mvPosition;", + "", + "\t#include ", + "\t#include ", + "\t", + "\tvViewPosition = - mvPosition.xyz;", + "\t", + "\t#include ", + "\t#include ", + "\t#include ", + "#ifdef USE_TRANSMISSION", + "\tvWorldPosition = worldPosition.xyz;", + "#endif", + "}", + "`;", + "", + "var stretched_bb_particle_vert = `", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "", + "attribute vec3 offset;", + "attribute float rotation;", + "attribute float size;", + "attribute vec4 velocity;", + "attribute float uvTile;", + "", + "#ifdef UV_TILE", + "uniform vec2 tileCount;", + "#endif", + "", + "uniform float speedFactor;", + "", + "void main() {", + "", + " ${uv_vertex_tile}", + " ", + " float lengthFactor = velocity.w;", + "#ifdef USE_SKEW", + " vec4 mvPosition = modelViewMatrix * vec4( offset, 1.0 );", + " vec3 viewVelocity = normalMatrix * velocity.xyz;", + "", + " vec3 scaledPos = vec3(position.xy * size, position.z);", + " float vlength = length(viewVelocity);", + " vec3 projVelocity = dot(scaledPos, viewVelocity) * viewVelocity / vlength;", + " mvPosition.xyz += scaledPos + projVelocity * (speedFactor / size + lengthFactor / vlength);", + "#else", + " vec4 mvPosition = modelViewMatrix * vec4( offset, 1.0 );", + " vec3 viewVelocity = normalMatrix * velocity.xyz;", + " float vlength = length(viewVelocity); ", + " mvPosition.xyz += position.y * normalize(cross(mvPosition.xyz, viewVelocity)) * size; // switch the cross to match unity implementation", + " mvPosition.xyz -= (position.x + 0.5) * viewVelocity * (1.0 + lengthFactor / vlength) * size; // minus position.x to match unity implementation", + "#endif", + "\tvColor = color;", + "\tgl_Position = projectionMatrix * mvPosition;", + "\t#include ", + "\t#include ", + "}", + "`;", + "", + "function getMaterialUVChannelName(value) {", + " if (value === 0)", + " return 'uv';", + " return `uv${value}`;", + "}", + "", + "new THREE.Vector3(0, 0, 1);", + "class SpriteBatch extends VFXBatch {", + " constructor(settings) {", + " super(settings);", + " this.vector_ = new THREE.Vector3();", + " this.vector2_ = new THREE.Vector3();", + " this.vector3_ = new THREE.Vector3();", + " this.quaternion_ = new THREE.Quaternion();", + " this.quaternion2_ = new THREE.Quaternion();", + " this.quaternion3_ = new THREE.Quaternion();", + " this.rotationMat_ = new THREE.Matrix3();", + " this.rotationMat2_ = new THREE.Matrix3();", + " this.maxParticles = 1000;", + " this.setupBuffers();", + " this.rebuildMaterial();", + " }", + " buildExpandableBuffers() {", + " this.offsetBuffer = new THREE.InstancedBufferAttribute(new Float32Array(this.maxParticles * 3), 3);", + " this.offsetBuffer.setUsage(THREE.DynamicDrawUsage);", + " this.geometry.setAttribute('offset', this.offsetBuffer);", + " this.colorBuffer = new THREE.InstancedBufferAttribute(new Float32Array(this.maxParticles * 4), 4);", + " this.colorBuffer.setUsage(THREE.DynamicDrawUsage);", + " this.geometry.setAttribute('color', this.colorBuffer);", + " if (this.settings.renderMode === RenderMode.Mesh) {", + " this.rotationBuffer = new THREE.InstancedBufferAttribute(new Float32Array(this.maxParticles * 4), 4);", + " this.rotationBuffer.setUsage(THREE.DynamicDrawUsage);", + " this.geometry.setAttribute('rotation', this.rotationBuffer);", + " }", + " else if (this.settings.renderMode === RenderMode.BillBoard ||", + " this.settings.renderMode === RenderMode.HorizontalBillBoard ||", + " this.settings.renderMode === RenderMode.VerticalBillBoard ||", + " this.settings.renderMode === RenderMode.StretchedBillBoard) {", + " this.rotationBuffer = new THREE.InstancedBufferAttribute(new Float32Array(this.maxParticles), 1);", + " this.rotationBuffer.setUsage(THREE.DynamicDrawUsage);", + " this.geometry.setAttribute('rotation', this.rotationBuffer);", + " }", + " this.sizeBuffer = new THREE.InstancedBufferAttribute(new Float32Array(this.maxParticles), 1);", + " this.sizeBuffer.setUsage(THREE.DynamicDrawUsage);", + " this.geometry.setAttribute('size', this.sizeBuffer);", + " this.uvTileBuffer = new THREE.InstancedBufferAttribute(new Float32Array(this.maxParticles), 1);", + " this.uvTileBuffer.setUsage(THREE.DynamicDrawUsage);", + " this.geometry.setAttribute('uvTile', this.uvTileBuffer);", + " if (this.settings.renderMode === RenderMode.StretchedBillBoard) {", + " this.velocityBuffer = new THREE.InstancedBufferAttribute(new Float32Array(this.maxParticles * 4), 4);", + " this.velocityBuffer.setUsage(THREE.DynamicDrawUsage);", + " this.geometry.setAttribute('velocity', this.velocityBuffer);", + " }", + " }", + " setupBuffers() {", + " if (this.geometry)", + " this.geometry.dispose();", + " this.geometry = new THREE.InstancedBufferGeometry();", + " this.geometry.setIndex(this.settings.instancingGeometry.getIndex());", + " if (this.settings.instancingGeometry.hasAttribute('normal')) {", + " this.geometry.setAttribute('normal', this.settings.instancingGeometry.getAttribute('normal'));", + " }", + " this.geometry.setAttribute('position', this.settings.instancingGeometry.getAttribute('position'));", + " this.geometry.setAttribute('uv', this.settings.instancingGeometry.getAttribute('uv'));", + " this.buildExpandableBuffers();", + " }", + " expandBuffers(target) {", + " while (target >= this.maxParticles) {", + " this.maxParticles *= 2;", + " }", + " this.setupBuffers();", + " }", + " rebuildMaterial() {", + " this.layers.mask = this.settings.layers.mask;", + " let uniforms;", + " const defines = {};", + " if (this.settings.material.type === 'MeshStandardMaterial' ||", + " this.settings.material.type === 'MeshPhysicalMaterial') {", + " const mat = this.settings.material;", + " uniforms = THREE.UniformsUtils.merge([", + " THREE.UniformsLib.common,", + " THREE.UniformsLib.envmap,", + " THREE.UniformsLib.aomap,", + " THREE.UniformsLib.lightmap,", + " THREE.UniformsLib.emissivemap,", + " THREE.UniformsLib.bumpmap,", + " THREE.UniformsLib.normalmap,", + " THREE.UniformsLib.displacementmap,", + " THREE.UniformsLib.roughnessmap,", + " THREE.UniformsLib.metalnessmap,", + " THREE.UniformsLib.fog,", + " THREE.UniformsLib.lights,", + " {", + " emissive: { value: new THREE.Color(0x000000) },", + " roughness: { value: 1.0 },", + " metalness: { value: 0.0 },", + " envMapIntensity: { value: 1 },", + " },", + " ]);", + " uniforms['diffuse'].value = mat.color;", + " uniforms['opacity'].value = mat.opacity;", + " uniforms['emissive'].value = mat.emissive;", + " uniforms['roughness'].value = mat.roughness;", + " uniforms['metalness'].value = mat.metalness;", + " if (mat.envMap) {", + " uniforms['envMap'].value = mat.envMap;", + " uniforms['envMapIntensity'].value = mat.envMapIntensity;", + " }", + " if (mat.normalMap) {", + " uniforms['normalMap'].value = mat.normalMap;", + " uniforms['normalScale'].value = mat.normalScale;", + " }", + " if (mat.roughnessMap) {", + " uniforms['roughnessMap'].value = mat.roughnessMap;", + " }", + " if (mat.metalnessMap) {", + " uniforms['metalnessMap'].value = mat.metalnessMap;", + " }", + " if (mat.map) {", + " uniforms['map'] = new THREE.Uniform(mat.map);", + " }", + " }", + " else {", + " uniforms = {};", + " uniforms['map'] = new THREE.Uniform(this.settings.material.map);", + " }", + " if (this.settings.material.alphaTest) {", + " defines['USE_ALPHATEST'] = '';", + " uniforms['alphaTest'] = new THREE.Uniform(this.settings.material.alphaTest);", + " }", + " defines['USE_UV'] = '';", + " const uTileCount = this.settings.uTileCount;", + " const vTileCount = this.settings.vTileCount;", + " if (uTileCount > 1 || vTileCount > 1) {", + " defines['UV_TILE'] = '';", + " uniforms['tileCount'] = new THREE.Uniform(new THREE.Vector2(uTileCount, vTileCount));", + " }", + " if (this.settings.material.defines &&", + " this.settings.material.defines['USE_COLOR_AS_ALPHA'] !== undefined) {", + " defines['USE_COLOR_AS_ALPHA'] = '';", + " }", + " if (this.settings.material.normalMap) {", + " defines['USE_NORMALMAP'] = '';", + " defines['NORMALMAP_UV'] = getMaterialUVChannelName(this.settings.material.normalMap.channel);", + " uniforms['normalMapTransform'] = new THREE.Uniform(new THREE.Matrix3().copy(this.settings.material.normalMap.matrix));", + " }", + " if (this.settings.material.map) {", + " defines['USE_MAP'] = '';", + " defines['MAP_UV'] = getMaterialUVChannelName(this.settings.material.map.channel);", + " uniforms['mapTransform'] = new THREE.Uniform(new THREE.Matrix3().copy(this.settings.material.map.matrix));", + " }", + " defines['USE_COLOR_ALPHA'] = '';", + " let needLights = false;", + " if (this.settings.renderMode === RenderMode.BillBoard ||", + " this.settings.renderMode === RenderMode.VerticalBillBoard ||", + " this.settings.renderMode === RenderMode.HorizontalBillBoard ||", + " this.settings.renderMode === RenderMode.Mesh) {", + " let vertexShader;", + " let fragmentShader;", + " if (this.settings.renderMode === RenderMode.Mesh) {", + " if (this.settings.material.type === 'MeshStandardMaterial' ||", + " this.settings.material.type === 'MeshPhysicalMaterial') {", + " defines['USE_COLOR'] = '';", + " vertexShader = local_particle_physics_vert;", + " fragmentShader = particle_physics_frag;", + " needLights = true;", + " }", + " else {", + " vertexShader = local_particle_vert;", + " fragmentShader = particle_frag;", + " }", + " }", + " else {", + " vertexShader = particle_vert;", + " fragmentShader = particle_frag;", + " }", + " if (this.settings.renderMode === RenderMode.VerticalBillBoard) {", + " defines['VERTICAL'] = '';", + " }", + " else if (this.settings.renderMode === RenderMode.HorizontalBillBoard) {", + " defines['HORIZONTAL'] = '';", + " }", + " this.material = new THREE.ShaderMaterial({", + " uniforms: uniforms,", + " defines: defines,", + " vertexShader: vertexShader,", + " fragmentShader: fragmentShader,", + " transparent: this.settings.material.transparent,", + " depthWrite: !this.settings.material.transparent,", + " blending: this.settings.material.blending,", + " side: this.settings.material.side,", + " alphaTest: this.settings.material.alphaTest,", + " lights: needLights,", + " });", + " }", + " else if (this.settings.renderMode === RenderMode.StretchedBillBoard) {", + " uniforms['speedFactor'] = new THREE.Uniform(1.0);", + " this.material = new THREE.ShaderMaterial({", + " uniforms: uniforms,", + " defines: defines,", + " vertexShader: stretched_bb_particle_vert,", + " fragmentShader: particle_frag,", + " transparent: this.settings.material.transparent,", + " depthWrite: !this.settings.material.transparent,", + " blending: this.settings.material.blending,", + " side: this.settings.material.side,", + " alphaTest: this.settings.material.alphaTest,", + " });", + " }", + " else {", + " throw new Error('render mode unavailable');", + " }", + " }", + " update() {", + " let index = 0;", + " let particleCount = 0;", + " this.systems.forEach((system) => {", + " particleCount += system.particleNum;", + " });", + " if (particleCount > this.maxParticles) {", + " this.expandBuffers(particleCount);", + " }", + " this.systems.forEach((system) => {", + " const particles = system.particles;", + " const particleNum = system.particleNum;", + " const rotation = this.quaternion2_;", + " const translation = this.vector2_;", + " const scale = this.vector3_;", + " system.emitter.matrixWorld.decompose(translation, rotation, scale);", + " this.rotationMat_.setFromMatrix4(system.emitter.matrixWorld);", + " for (let j = 0; j < particleNum; j++ , index++) {", + " const particle = particles[j];", + " if (this.settings.renderMode === RenderMode.Mesh) {", + " let q;", + " if (system.worldSpace) {", + " q = particle.rotation;", + " }", + " else {", + " let parentQ;", + " if (particle.parentMatrix) {", + " parentQ = this.quaternion3_.setFromRotationMatrix(particle.parentMatrix);", + " }", + " else {", + " parentQ = rotation;", + " }", + " q = this.quaternion_;", + " q.copy(particle.rotation).multiply(parentQ);", + " }", + " this.rotationBuffer.setXYZW(index, q.x, q.y, q.z, q.w);", + " }", + " else if (this.settings.renderMode === RenderMode.StretchedBillBoard ||", + " this.settings.renderMode === RenderMode.VerticalBillBoard ||", + " this.settings.renderMode === RenderMode.HorizontalBillBoard ||", + " this.settings.renderMode === RenderMode.BillBoard) {", + " this.rotationBuffer.setX(index, particle.rotation);", + " }", + " let vec;", + " if (system.worldSpace) {", + " vec = particle.position;", + " }", + " else {", + " vec = this.vector_;", + " if (particle.parentMatrix) {", + " vec.copy(particle.position).applyMatrix4(particle.parentMatrix);", + " }", + " else {", + " vec.copy(particle.position).applyMatrix4(system.emitter.matrixWorld);", + " }", + " }", + " this.offsetBuffer.setXYZ(index, vec.x, vec.y, vec.z);", + " this.colorBuffer.setXYZW(index, particle.color.x, particle.color.y, particle.color.z, particle.color.w);", + " if (system.worldSpace) {", + " this.sizeBuffer.setX(index, particle.size);", + " }", + " else {", + " if (particle.parentMatrix) {", + " this.sizeBuffer.setX(index, particle.size);", + " }", + " else {", + " this.sizeBuffer.setX(index, (particle.size * (Math.abs(scale.x) + Math.abs(scale.y) + Math.abs(scale.z))) / 3);", + " }", + " }", + " this.uvTileBuffer.setX(index, particle.uvTile);", + " if (this.settings.renderMode === RenderMode.StretchedBillBoard && this.velocityBuffer) {", + " let speedFactor = system.rendererEmitterSettings.speedFactor;", + " if (speedFactor === 0)", + " speedFactor = 0.001;", + " const lengthFactor = system.rendererEmitterSettings.lengthFactor;", + " let vec;", + " if (system.worldSpace) {", + " vec = particle.velocity;", + " }", + " else {", + " vec = this.vector_;", + " if (particle.parentMatrix) {", + " this.rotationMat2_.setFromMatrix4(particle.parentMatrix);", + " vec.copy(particle.velocity).applyMatrix3(this.rotationMat2_);", + " }", + " else {", + " vec.copy(particle.velocity).applyMatrix3(this.rotationMat_);", + " }", + " }", + " this.velocityBuffer.setXYZW(index, vec.x * speedFactor, vec.y * speedFactor, vec.z * speedFactor, lengthFactor);", + " }", + " }", + " });", + " this.geometry.instanceCount = index;", + " if (index > 0) {", + " this.offsetBuffer.clearUpdateRanges();", + " this.offsetBuffer.addUpdateRange(0, index * 3);", + " this.offsetBuffer.needsUpdate = true;", + " this.sizeBuffer.clearUpdateRanges();", + " this.sizeBuffer.addUpdateRange(0, index);", + " this.sizeBuffer.needsUpdate = true;", + " this.colorBuffer.clearUpdateRanges();", + " this.colorBuffer.addUpdateRange(0, index * 4);", + " this.colorBuffer.needsUpdate = true;", + " this.uvTileBuffer.clearUpdateRanges();", + " this.uvTileBuffer.addUpdateRange(0, index);", + " this.uvTileBuffer.needsUpdate = true;", + " if (this.settings.renderMode === RenderMode.StretchedBillBoard && this.velocityBuffer) {", + " this.velocityBuffer.clearUpdateRanges();", + " this.velocityBuffer.addUpdateRange(0, index * 4);", + " this.velocityBuffer.needsUpdate = true;", + " }", + " if (this.settings.renderMode === RenderMode.Mesh) {", + " this.rotationBuffer.clearUpdateRanges();", + " this.rotationBuffer.addUpdateRange(0, index * 4);", + " this.rotationBuffer.needsUpdate = true;", + " }", + " else if (this.settings.renderMode === RenderMode.StretchedBillBoard ||", + " this.settings.renderMode === RenderMode.HorizontalBillBoard ||", + " this.settings.renderMode === RenderMode.VerticalBillBoard ||", + " this.settings.renderMode === RenderMode.BillBoard) {", + " this.rotationBuffer.clearUpdateRanges();", + " this.rotationBuffer.addUpdateRange(0, index);", + " this.rotationBuffer.needsUpdate = true;", + " }", + " }", + " }", + " dispose() {", + " this.geometry.dispose();", + " }", + "}", + "", + "var trail_frag = `", + "", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "", + "uniform sampler2D alphaMap;", + "uniform float useAlphaMap;", + "uniform float visibility;", + "uniform float alphaTest;", + "uniform vec2 repeat;", + "", + "varying vec4 vColor;", + " ", + "void main() {", + " #include ", + " #include ", + "", + " vec4 c = vColor;", + " ", + " #ifdef USE_MAP", + " #ifdef USE_COLOR_AS_ALPHA", + " vec4 tex = texture2D( map, vUv * repeat );", + " c *= vec4(tex.rgb, tex.r);", + " #else", + " c *= texture2D( map, vUv * repeat );", + " #endif", + " #endif", + " if( useAlphaMap == 1. ) c.a *= texture2D( alphaMap, vUv * repeat ).a;", + " if( c.a < alphaTest ) discard;", + " gl_FragColor = c;", + "", + " #include ", + " #include ", + "}`;", + "", + "var trail_vert = `", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "#include ", + "", + "attribute vec3 previous;", + "attribute vec3 next;", + "attribute float side;", + "attribute float width;", + "", + "uniform vec2 resolution;", + "uniform float lineWidth;", + "uniform float sizeAttenuation;", + " ", + "vec2 fix(vec4 i, float aspect) {", + " vec2 res = i.xy / i.w;", + " res.x *= aspect;", + " return res;", + "}", + " ", + "void main() {", + "", + " ${uv_vertex_tile}", + " ", + " float aspect = resolution.x / resolution.y;", + "", + " vColor = color;", + "", + " mat4 m = projectionMatrix * modelViewMatrix;", + " vec4 finalPosition = m * vec4( position, 1.0 );", + " vec4 prevPos = m * vec4( previous, 1.0 );", + " vec4 nextPos = m * vec4( next, 1.0 );", + "", + " vec2 currentP = fix( finalPosition, aspect );", + " vec2 prevP = fix( prevPos, aspect );", + " vec2 nextP = fix( nextPos, aspect );", + "", + " float w = lineWidth * width;", + "", + " vec2 dir;", + " if( nextP == currentP ) dir = normalize( currentP - prevP );", + " else if( prevP == currentP ) dir = normalize( nextP - currentP );", + " else {", + " vec2 dir1 = normalize( currentP - prevP );", + " vec2 dir2 = normalize( nextP - currentP );", + " dir = normalize( dir1 + dir2 );", + "", + " vec2 perp = vec2( -dir1.y, dir1.x );", + " vec2 miter = vec2( -dir.y, dir.x );", + " //w = clamp( w / dot( miter, perp ), 0., 4., * lineWidth * width );", + "", + " }", + "", + " //vec2 normal = ( cross( vec3( dir, 0. ) vec3( 0., 0., 1. ) ) ).xy;", + " vec4 normal = vec4( -dir.y, dir.x, 0., 1. );", + " normal.xy *= .5 * w;", + " normal *= projectionMatrix;", + " if( sizeAttenuation == 0. ) {", + " normal.xy *= finalPosition.w;", + " normal.xy /= ( vec4( resolution, 0., 1. ) * projectionMatrix ).xy;", + " }", + "", + " finalPosition.xy += normal.xy * side;", + "", + " gl_Position = finalPosition;", + "", + "\t#include ", + "\t#include ", + "\t", + " vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );", + " ", + "\t#include ", + "}`;", + "", + "new THREE.Vector3(0, 0, 1);", + "class TrailBatch extends VFXBatch {", + " constructor(settings) {", + " super(settings);", + " this.vector_ = new THREE.Vector3();", + " this.vector2_ = new THREE.Vector3();", + " this.vector3_ = new THREE.Vector3();", + " this.quaternion_ = new THREE.Quaternion();", + " this.maxParticles = 10000;", + " this.setupBuffers();", + " this.rebuildMaterial();", + " }", + " setupBuffers() {", + " if (this.geometry)", + " this.geometry.dispose();", + " this.geometry = new THREE.BufferGeometry();", + " this.indexBuffer = new THREE.BufferAttribute(new Uint32Array(this.maxParticles * 6), 1);", + " this.indexBuffer.setUsage(THREE.DynamicDrawUsage);", + " this.geometry.setIndex(this.indexBuffer);", + " this.positionBuffer = new THREE.BufferAttribute(new Float32Array(this.maxParticles * 6), 3);", + " this.positionBuffer.setUsage(THREE.DynamicDrawUsage);", + " this.geometry.setAttribute('position', this.positionBuffer);", + " this.previousBuffer = new THREE.BufferAttribute(new Float32Array(this.maxParticles * 6), 3);", + " this.previousBuffer.setUsage(THREE.DynamicDrawUsage);", + " this.geometry.setAttribute('previous', this.previousBuffer);", + " this.nextBuffer = new THREE.BufferAttribute(new Float32Array(this.maxParticles * 6), 3);", + " this.nextBuffer.setUsage(THREE.DynamicDrawUsage);", + " this.geometry.setAttribute('next', this.nextBuffer);", + " this.widthBuffer = new THREE.BufferAttribute(new Float32Array(this.maxParticles * 2), 1);", + " this.widthBuffer.setUsage(THREE.DynamicDrawUsage);", + " this.geometry.setAttribute('width', this.widthBuffer);", + " this.sideBuffer = new THREE.BufferAttribute(new Float32Array(this.maxParticles * 2), 1);", + " this.sideBuffer.setUsage(THREE.DynamicDrawUsage);", + " this.geometry.setAttribute('side', this.sideBuffer);", + " this.uvBuffer = new THREE.BufferAttribute(new Float32Array(this.maxParticles * 4), 2);", + " this.uvBuffer.setUsage(THREE.DynamicDrawUsage);", + " this.geometry.setAttribute('uv', this.uvBuffer);", + " this.colorBuffer = new THREE.BufferAttribute(new Float32Array(this.maxParticles * 8), 4);", + " this.colorBuffer.setUsage(THREE.DynamicDrawUsage);", + " this.geometry.setAttribute('color', this.colorBuffer);", + " }", + " expandBuffers(target) {", + " while (target >= this.maxParticles) {", + " this.maxParticles *= 2;", + " }", + " this.setupBuffers();", + " }", + " rebuildMaterial() {", + " this.layers.mask = this.settings.layers.mask;", + " const uniforms = {", + " lineWidth: { value: 1 },", + " map: { value: null },", + " useMap: { value: 0 },", + " alphaMap: { value: null },", + " useAlphaMap: { value: 0 },", + " resolution: { value: new THREE.Vector2(1, 1) },", + " sizeAttenuation: { value: 1 },", + " visibility: { value: 1 },", + " alphaTest: { value: 0 },", + " repeat: { value: new THREE.Vector2(1, 1) },", + " };", + " const defines = {};", + " defines['USE_UV'] = '';", + " defines['USE_COLOR_ALPHA'] = '';", + " if (this.settings.material.map) {", + " defines['USE_MAP'] = '';", + " defines['MAP_UV'] = getMaterialUVChannelName(this.settings.material.map.channel);", + " uniforms['map'] = new THREE.Uniform(this.settings.material.map);", + " uniforms['mapTransform'] = new THREE.Uniform(new THREE.Matrix3().copy(this.settings.material.map.matrix));", + " }", + " if (this.settings.material.defines &&", + " this.settings.material.defines['USE_COLOR_AS_ALPHA'] !== undefined) {", + " defines['USE_COLOR_AS_ALPHA'] = '';", + " }", + " if (this.settings.renderMode === RenderMode.Trail) {", + " this.material = new THREE.ShaderMaterial({", + " uniforms: uniforms,", + " defines: defines,", + " vertexShader: trail_vert,", + " fragmentShader: trail_frag,", + " transparent: this.settings.material.transparent,", + " depthWrite: !this.settings.material.transparent,", + " side: this.settings.material.side,", + " blending: this.settings.material.blending || THREE.AdditiveBlending,", + " });", + " }", + " else {", + " throw new Error('render mode unavailable');", + " }", + " }", + " update() {", + " let index = 0;", + " let triangles = 0;", + " let particleCount = 0;", + " this.systems.forEach((system) => {", + " for (let j = 0; j < system.particleNum; j++) {", + " particleCount += system.particles[j].previous.length * 2;", + " }", + " });", + " if (particleCount > this.maxParticles) {", + " this.expandBuffers(particleCount);", + " }", + " this.systems.forEach((system) => {", + " const rotation = this.quaternion_;", + " const translation = this.vector2_;", + " const scale = this.vector3_;", + " system.emitter.matrixWorld.decompose(translation, rotation, scale);", + " const particles = system.particles;", + " const particleNum = system.particleNum;", + " const uTileCount = this.settings.uTileCount;", + " const vTileCount = this.settings.vTileCount;", + " const tileWidth = 1 / uTileCount;", + " const tileHeight = 1 / vTileCount;", + " for (let j = 0; j < particleNum; j++) {", + " const particle = particles[j];", + " const col = particle.uvTile % vTileCount;", + " const row = Math.floor(particle.uvTile / vTileCount + 0.001);", + " const iter = particle.previous.values();", + " let curIter = iter.next();", + " let previous = curIter.value;", + " let current = previous;", + " if (!curIter.done)", + " curIter = iter.next();", + " let next;", + " if (curIter.value !== undefined) {", + " next = curIter.value;", + " }", + " else {", + " next = current;", + " }", + " for (let i = 0; i < particle.previous.length; i++ , index += 2) {", + " this.positionBuffer.setXYZ(index, current.position.x, current.position.y, current.position.z);", + " this.positionBuffer.setXYZ(index + 1, current.position.x, current.position.y, current.position.z);", + " if (system.worldSpace) {", + " this.positionBuffer.setXYZ(index, current.position.x, current.position.y, current.position.z);", + " this.positionBuffer.setXYZ(index + 1, current.position.x, current.position.y, current.position.z);", + " }", + " else {", + " if (particle.parentMatrix) {", + " this.vector_.copy(current.position).applyMatrix4(particle.parentMatrix);", + " }", + " else {", + " this.vector_.copy(current.position).applyMatrix4(system.emitter.matrixWorld);", + " }", + " this.positionBuffer.setXYZ(index, this.vector_.x, this.vector_.y, this.vector_.z);", + " this.positionBuffer.setXYZ(index + 1, this.vector_.x, this.vector_.y, this.vector_.z);", + " }", + " if (system.worldSpace) {", + " this.previousBuffer.setXYZ(index, previous.position.x, previous.position.y, previous.position.z);", + " this.previousBuffer.setXYZ(index + 1, previous.position.x, previous.position.y, previous.position.z);", + " }", + " else {", + " if (particle.parentMatrix) {", + " this.vector_.copy(previous.position).applyMatrix4(particle.parentMatrix);", + " }", + " else {", + " this.vector_.copy(previous.position).applyMatrix4(system.emitter.matrixWorld);", + " }", + " this.previousBuffer.setXYZ(index, this.vector_.x, this.vector_.y, this.vector_.z);", + " this.previousBuffer.setXYZ(index + 1, this.vector_.x, this.vector_.y, this.vector_.z);", + " }", + " if (system.worldSpace) {", + " this.nextBuffer.setXYZ(index, next.position.x, next.position.y, next.position.z);", + " this.nextBuffer.setXYZ(index + 1, next.position.x, next.position.y, next.position.z);", + " }", + " else {", + " if (particle.parentMatrix) {", + " this.vector_.copy(next.position).applyMatrix4(particle.parentMatrix);", + " }", + " else {", + " this.vector_.copy(next.position).applyMatrix4(system.emitter.matrixWorld);", + " }", + " this.nextBuffer.setXYZ(index, this.vector_.x, this.vector_.y, this.vector_.z);", + " this.nextBuffer.setXYZ(index + 1, this.vector_.x, this.vector_.y, this.vector_.z);", + " }", + " this.sideBuffer.setX(index, -1);", + " this.sideBuffer.setX(index + 1, 1);", + " if (system.worldSpace) {", + " this.widthBuffer.setX(index, current.size);", + " this.widthBuffer.setX(index + 1, current.size);", + " }", + " else {", + " if (particle.parentMatrix) {", + " this.widthBuffer.setX(index, current.size);", + " this.widthBuffer.setX(index + 1, current.size);", + " }", + " else {", + " const objectScale = (Math.abs(scale.x) + Math.abs(scale.y) + Math.abs(scale.z)) / 3;", + " this.widthBuffer.setX(index, current.size * objectScale);", + " this.widthBuffer.setX(index + 1, current.size * objectScale);", + " }", + " }", + " this.uvBuffer.setXY(index, (i / particle.previous.length + col) * tileWidth, (vTileCount - row - 1) * tileHeight);", + " this.uvBuffer.setXY(index + 1, (i / particle.previous.length + col) * tileWidth, (vTileCount - row) * tileHeight);", + " this.colorBuffer.setXYZW(index, current.color.x, current.color.y, current.color.z, current.color.w);", + " this.colorBuffer.setXYZW(index + 1, current.color.x, current.color.y, current.color.z, current.color.w);", + " if (i + 1 < particle.previous.length) {", + " this.indexBuffer.setX(triangles * 3, index);", + " this.indexBuffer.setX(triangles * 3 + 1, index + 1);", + " this.indexBuffer.setX(triangles * 3 + 2, index + 2);", + " triangles++;", + " this.indexBuffer.setX(triangles * 3, index + 2);", + " this.indexBuffer.setX(triangles * 3 + 1, index + 1);", + " this.indexBuffer.setX(triangles * 3 + 2, index + 3);", + " triangles++;", + " }", + " previous = current;", + " current = next;", + " if (!curIter.done) {", + " curIter = iter.next();", + " if (curIter.value !== undefined) {", + " next = curIter.value;", + " }", + " }", + " }", + " }", + " });", + " this.positionBuffer.clearUpdateRanges();", + " this.positionBuffer.addUpdateRange(0, index * 3);", + " this.positionBuffer.needsUpdate = true;", + " this.previousBuffer.clearUpdateRanges();", + " this.previousBuffer.addUpdateRange(0, index * 3);", + " this.previousBuffer.needsUpdate = true;", + " this.nextBuffer.clearUpdateRanges();", + " this.nextBuffer.addUpdateRange(0, index * 3);", + " this.nextBuffer.needsUpdate = true;", + " this.sideBuffer.clearUpdateRanges();", + " this.sideBuffer.addUpdateRange(0, index);", + " this.sideBuffer.needsUpdate = true;", + " this.widthBuffer.clearUpdateRanges();", + " this.widthBuffer.addUpdateRange(0, index);", + " this.widthBuffer.needsUpdate = true;", + " this.uvBuffer.clearUpdateRanges();", + " this.uvBuffer.addUpdateRange(0, index * 2);", + " this.uvBuffer.needsUpdate = true;", + " this.colorBuffer.clearUpdateRanges();", + " this.colorBuffer.addUpdateRange(0, index * 4);", + " this.colorBuffer.needsUpdate = true;", + " this.indexBuffer.clearUpdateRanges();", + " this.indexBuffer.addUpdateRange(0, triangles * 3);", + " this.indexBuffer.needsUpdate = true;", + " this.geometry.setDrawRange(0, triangles * 3);", + " }", + " dispose() {", + " this.geometry.dispose();", + " }", + "}", + "", + "class BatchedRenderer extends THREE.Object3D {", + " constructor() {", + " super();", + " this.batches = [];", + " this.systemToBatchIndex = new Map();", + " this.type = 'BatchedRenderer';", + " }", + " static equals(a, b) {", + " return (a.material.side === b.material.side &&", + " a.material.blending === b.material.blending &&", + " a.material.transparent === b.material.transparent &&", + " a.material.type === b.material.type &&", + " a.material.alphaTest === b.material.alphaTest &&", + " a.material.map === b.material.map &&", + " a.renderMode === b.renderMode &&", + " a.uTileCount === b.uTileCount &&", + " a.vTileCount === b.vTileCount &&", + " a.instancingGeometry === b.instancingGeometry &&", + " a.renderOrder === b.renderOrder &&", + " a.layers.mask === b.layers.mask);", + " }", + " addSystem(system) {", + " system._renderer = this;", + " const settings = system.getRendererSettings();", + " for (let i = 0; i < this.batches.length; i++) {", + " if (BatchedRenderer.equals(this.batches[i].settings, settings)) {", + " this.batches[i].addSystem(system);", + " this.systemToBatchIndex.set(system, i);", + " return;", + " }", + " }", + " let batch;", + " switch (settings.renderMode) {", + " case RenderMode.Trail:", + " batch = new TrailBatch(settings);", + " break;", + " case RenderMode.Mesh:", + " case RenderMode.BillBoard:", + " case RenderMode.VerticalBillBoard:", + " case RenderMode.HorizontalBillBoard:", + " case RenderMode.StretchedBillBoard:", + " batch = new SpriteBatch(settings);", + " break;", + " }", + " batch.addSystem(system);", + " this.batches.push(batch);", + " this.systemToBatchIndex.set(system, this.batches.length - 1);", + " this.add(batch);", + " }", + " deleteSystem(system) {", + " const batchIndex = this.systemToBatchIndex.get(system);", + " if (batchIndex != undefined) {", + " this.batches[batchIndex].removeSystem(system);", + " this.systemToBatchIndex.delete(system);", + " }", + " }", + " updateSystem(system) {", + " this.deleteSystem(system);", + " this.addSystem(system);", + " }", + " update(delta) {", + " this.systemToBatchIndex.forEach((value, ps) => {", + " ps.update(delta);", + " });", + " for (let i = 0; i < this.batches.length; i++) {", + " this.batches[i].update();", + " }", + " }", + "}", + "", + "const BatchedParticleRenderer = BatchedRenderer;", + "", + "class QuarksLoader extends THREE.ObjectLoader {", + " constructor(manager) {", + " super(manager);", + " }", + " linkReference(object) {", + " const objectsMap = {};", + " object.traverse(function (child) {", + " objectsMap[child.uuid] = child;", + " });", + " object.traverse(function (child) {", + " if (child.type === 'ParticleEmitter') {", + " const system = child.system;", + " system.emitterShape;", + " for (let i = 0; i < system.behaviors.length; i++) {", + " if (system.behaviors[i] instanceof EmitSubParticleSystem) {", + " system.behaviors[i].subParticleSystem = objectsMap[system.behaviors[i].subParticleSystem];", + " }", + " }", + " }", + " });", + " }", + " parse(json, onLoad) {", + " const object = super.parse(json, onLoad);", + " this.linkReference(object);", + " return object;", + " }", + " parseObject(data, geometries, materials, textures, animations) {", + " let object;", + " function getGeometry(name) {", + " if (geometries[name] === undefined) {", + " console.warn('THREE.ObjectLoader: Undefined geometry', name);", + " }", + " return geometries[name];", + " }", + " function getMaterial(name) {", + " if (name === undefined)", + " return undefined;", + " if (Array.isArray(name)) {", + " const array = [];", + " for (let i = 0, l = name.length; i < l; i++) {", + " const uuid = name[i];", + " if (materials[uuid] === undefined) {", + " console.warn('THREE.ObjectLoader: Undefined material', uuid);", + " }", + " array.push(materials[uuid]);", + " }", + " return array;", + " }", + " if (materials[name] === undefined) {", + " console.warn('THREE.ObjectLoader: Undefined material', name);", + " }", + " return materials[name];", + " }", + " function getTexture(uuid) {", + " if (textures[uuid] === undefined) {", + " console.warn('THREE.ObjectLoader: Undefined texture', uuid);", + " }", + " return textures[uuid];", + " }", + " let geometry, material;", + " const meta = {", + " textures: textures,", + " geometries: geometries,", + " materials: materials,", + " };", + " const dependencies = {};", + " switch (data.type) {", + " case 'ParticleEmitter':", + " object = ParticleSystem.fromJSON(data.ps, meta, dependencies).emitter;", + " break;", + " case 'Scene':", + " object = new THREE.Scene();", + " if (data.background !== undefined) {", + " if (Number.isInteger(data.background)) {", + " object.background = new THREE.Color(data.background);", + " }", + " else {", + " object.background = getTexture(data.background);", + " }", + " }", + " if (data.environment !== undefined) {", + " object.environment = getTexture(data.environment);", + " }", + " if (data.fog !== undefined) {", + " if (data.fog.type === 'Fog') {", + " object.fog = new THREE.Fog(data.fog.color, data.fog.near, data.fog.far);", + " }", + " else if (data.fog.type === 'FogExp2') {", + " object.fog = new THREE.FogExp2(data.fog.color, data.fog.density);", + " }", + " }", + " if (data.backgroundBlurriness !== undefined)", + " object.backgroundBlurriness = data.backgroundBlurriness;", + " break;", + " case 'PerspectiveCamera':", + " object = new THREE.PerspectiveCamera(data.fov, data.aspect, data.near, data.far);", + " if (data.focus !== undefined)", + " object.focus = data.focus;", + " if (data.zoom !== undefined)", + " object.zoom = data.zoom;", + " if (data.filmGauge !== undefined)", + " object.filmGauge = data.filmGauge;", + " if (data.filmOffset !== undefined)", + " object.filmOffset = data.filmOffset;", + " if (data.view !== undefined)", + " object.view = Object.assign({}, data.view);", + " break;", + " case 'OrthographicCamera':", + " object = new THREE.OrthographicCamera(data.left, data.right, data.top, data.bottom, data.near, data.far);", + " if (data.zoom !== undefined)", + " object.zoom = data.zoom;", + " if (data.view !== undefined)", + " object.view = Object.assign({}, data.view);", + " break;", + " case 'AmbientLight':", + " object = new THREE.AmbientLight(data.color, data.intensity);", + " break;", + " case 'DirectionalLight':", + " object = new THREE.DirectionalLight(data.color, data.intensity);", + " break;", + " case 'PointLight':", + " object = new THREE.PointLight(data.color, data.intensity, data.distance, data.decay);", + " break;", + " case 'RectAreaLight':", + " object = new THREE.RectAreaLight(data.color, data.intensity, data.width, data.height);", + " break;", + " case 'SpotLight':", + " object = new THREE.SpotLight(data.color, data.intensity, data.distance, data.angle, data.penumbra, data.decay);", + " break;", + " case 'HemisphereLight':", + " object = new THREE.HemisphereLight(data.color, data.groundColor, data.intensity);", + " break;", + " case 'LightProbe':", + " object = new THREE.LightProbe().fromJSON(data);", + " break;", + " case 'SkinnedMesh':", + " geometry = getGeometry(data.geometry);", + " material = getMaterial(data.material);", + " object = new THREE.SkinnedMesh(geometry, material);", + " if (data.bindMode !== undefined)", + " object.bindMode = data.bindMode;", + " if (data.bindMatrix !== undefined)", + " object.bindMatrix.fromArray(data.bindMatrix);", + " if (data.skeleton !== undefined)", + " object.skeleton = data.skeleton;", + " break;", + " case 'Mesh':", + " geometry = getGeometry(data.geometry);", + " material = getMaterial(data.material);", + " object = new THREE.Mesh(geometry, material);", + " break;", + " case 'InstancedMesh': {", + " geometry = getGeometry(data.geometry);", + " material = getMaterial(data.material);", + " const count = data.count;", + " const instanceMatrix = data.instanceMatrix;", + " const instanceColor = data.instanceColor;", + " object = new THREE.InstancedMesh(geometry, material, count);", + " object.instanceMatrix = new THREE.InstancedBufferAttribute(new Float32Array(instanceMatrix.array), 16);", + " if (instanceColor !== undefined)", + " object.instanceColor = new THREE.InstancedBufferAttribute(new Float32Array(instanceColor.array), instanceColor.itemSize);", + " break;", + " }", + " case 'LOD':", + " object = new THREE.LOD();", + " break;", + " case 'Line':", + " object = new THREE.Line(getGeometry(data.geometry), getMaterial(data.material));", + " break;", + " case 'LineLoop':", + " object = new THREE.LineLoop(getGeometry(data.geometry), getMaterial(data.material));", + " break;", + " case 'LineSegments':", + " object = new THREE.LineSegments(getGeometry(data.geometry), getMaterial(data.material));", + " break;", + " case 'PointCloud':", + " case 'Points':", + " object = new THREE.Points(getGeometry(data.geometry), getMaterial(data.material));", + " break;", + " case 'Sprite':", + " object = new THREE.Sprite(getMaterial(data.material));", + " break;", + " case 'Group':", + " object = new THREE.Group();", + " break;", + " case 'Bone':", + " object = new THREE.Bone();", + " break;", + " default:", + " object = new THREE.Object3D();", + " }", + " object.uuid = data.uuid;", + " if (data.name !== undefined)", + " object.name = data.name;", + " if (data.matrix !== undefined) {", + " object.matrix.fromArray(data.matrix);", + " if (data.matrixAutoUpdate !== undefined)", + " object.matrixAutoUpdate = data.matrixAutoUpdate;", + " if (object.matrixAutoUpdate)", + " object.matrix.decompose(object.position, object.quaternion, object.scale);", + " }", + " else {", + " if (data.position !== undefined)", + " object.position.fromArray(data.position);", + " if (data.rotation !== undefined)", + " object.rotation.fromArray(data.rotation);", + " if (data.quaternion !== undefined)", + " object.quaternion.fromArray(data.quaternion);", + " if (data.scale !== undefined)", + " object.scale.fromArray(data.scale);", + " }", + " if (data.castShadow !== undefined)", + " object.castShadow = data.castShadow;", + " if (data.receiveShadow !== undefined)", + " object.receiveShadow = data.receiveShadow;", + " if (data.shadow) {", + " if (data.shadow.bias !== undefined)", + " object.shadow.bias = data.shadow.bias;", + " if (data.shadow.normalBias !== undefined)", + " object.normalBias = data.shadow.normalBias;", + " if (data.shadow.radius !== undefined)", + " object.radius = data.shadow.radius;", + " if (data.shadow.mapSize !== undefined)", + " object.mapSize.fromArray(data.shadow.mapSize);", + " if (data.shadow.camera !== undefined) {", + " object.camera = this.parseObject(data.shadow.camera);", + " }", + " }", + " if (data.visible !== undefined)", + " object.visible = data.visible;", + " if (data.frustumCulled !== undefined)", + " object.frustumCulled = data.frustumCulled;", + " if (data.renderOrder !== undefined)", + " object.renderOrder = data.renderOrder;", + " if (data.userData !== undefined)", + " object.userData = data.userData;", + " if (data.layers !== undefined)", + " object.layers.mask = data.layers;", + " if (data.children !== undefined) {", + " const children = data.children;", + " for (let i = 0; i < children.length; i++) {", + " object.add(this.parseObject(children[i], geometries, materials, textures, animations));", + " }", + " }", + " if (data.animations !== undefined) {", + " const objectAnimations = data.animations;", + " for (let i = 0; i < objectAnimations.length; i++) {", + " const uuid = objectAnimations[i];", + " object.animations.push(animations[uuid]);", + " }", + " }", + " if (data.type === 'LOD') {", + " if (data.autoUpdate !== undefined)", + " object.autoUpdate = data.autoUpdate;", + " const levels = data.levels;", + " for (let l = 0; l < levels.length; l++) {", + " const level = levels[l];", + " const child = object.getObjectByProperty('uuid', level.object);", + " if (child !== undefined) {", + " object.addLevel(child, level.distance);", + " }", + " }", + " }", + " return object;", + " }", + "}", + "", + "const Plugins = [];", + "function loadPlugin(plugin) {", + " const existing = Plugins.find((item) => item.id === plugin.id);", + " if (!existing) {", + " plugin.initialize();", + " for (const emitterShape of plugin.emitterShapes) {", + " if (!EmitterShapes[emitterShape.type]) {", + " EmitterShapes[emitterShape.type] = emitterShape;", + " }", + " }", + " for (const behavior of plugin.behaviors) {", + " if (!BehaviorTypes[behavior.type]) {", + " BehaviorTypes[behavior.type] = behavior;", + " }", + " }", + " }", + "}", + "function unloadPlugin(pluginId) {", + " const plugin = Plugins.find((item) => item.id === pluginId);", + " if (plugin) {", + " for (const emitterShape of plugin.emitterShapes) {", + " if (EmitterShapes[emitterShape.type]) {", + " delete EmitterShapes[emitterShape.type];", + " }", + " }", + " for (const behavior of plugin.behaviors) {", + " if (BehaviorTypes[behavior.type]) {", + " delete BehaviorTypes[behavior.type];", + " }", + " }", + " }", + "}", + "", + "let NodeValueType = void 0;", + "(function (NodeValueType) {", + " NodeValueType[NodeValueType[\"Number\"] = 0] = \"Number\";", + " NodeValueType[NodeValueType[\"Vec2\"] = 1] = \"Vec2\";", + " NodeValueType[NodeValueType[\"Vec3\"] = 2] = \"Vec3\";", + " NodeValueType[NodeValueType[\"Vec4\"] = 3] = \"Vec4\";", + " NodeValueType[NodeValueType[\"Boolean\"] = 4] = \"Boolean\";", + " NodeValueType[NodeValueType[\"AnyType\"] = 5] = \"AnyType\";", + " NodeValueType[NodeValueType[\"NullableAnyType\"] = 6] = \"NullableAnyType\";", + " NodeValueType[NodeValueType[\"EventStream\"] = 7] = \"EventStream\";", + "})(NodeValueType || (NodeValueType = {}));", + "const genDefaultForNodeValueType = (type) => {", + " switch (type) {", + " case NodeValueType.Boolean:", + " return false;", + " case NodeValueType.Number:", + " return 0;", + " case NodeValueType.Vec2:", + " return new THREE.Vector2();", + " case NodeValueType.Vec3:", + " return new THREE.Vector3();", + " case NodeValueType.Vec4:", + " return new THREE.Vector4();", + " case NodeValueType.AnyType:", + " return 0;", + " case NodeValueType.NullableAnyType:", + " return undefined;", + " }", + "};", + "", + "class Node {", + " constructor(type, signatureIndex = -1, data = {}) {", + " this.inputs = [];", + " this.outputs = [];", + " this.signatureIndex = -1;", + " this.position = new THREE.Vector2();", + " this.outputValues = [];", + " this.id = '' + Math.round(Math.random() * 100000);", + " this.type = type;", + " this.signatureIndex = signatureIndex;", + " this.data = data;", + " const realIndex = signatureIndex === -1 ? 0 : signatureIndex;", + " for (let i = 0; i < type.nodeTypeSignatures[realIndex].inputTypes.length; i++) {", + " this.inputs.push(undefined);", + " }", + " for (let i = 0; i < type.nodeTypeSignatures[realIndex].outputTypes.length; i++) {", + " this.outputs.push([]);", + " this.outputValues.push(genDefaultForNodeValueType(type.nodeTypeSignatures[realIndex].outputTypes[i]));", + " }", + " }", + " get inputTypes() {", + " const signatureIndex = this.signatureIndex === -1 ? 0 : this.signatureIndex;", + " return this.type.nodeTypeSignatures[signatureIndex].inputTypes;", + " }", + " get outputTypes() {", + " const signatureIndex = this.signatureIndex === -1 ? 0 : this.signatureIndex;", + " return this.type.nodeTypeSignatures[signatureIndex].outputTypes;", + " }", + " func(context, inputs, outputs) {", + " const signatureIndex = this.signatureIndex === -1 ? 0 : this.signatureIndex;", + " this.type.nodeTypeSignatures[signatureIndex].func(context, this.data, inputs, outputs);", + " }", + "}", + "class Wire {", + " constructor(input, inputIndex, output, outputIndex) {", + " this.input = input;", + " this.inputIndex = inputIndex;", + " this.input.outputs[inputIndex].push(this);", + " this.output = output;", + " this.outputIndex = outputIndex;", + " this.output.inputs[outputIndex] = this;", + " }", + "}", + "", + "class BaseCompiler {", + " constructor() {", + " this.visited = new Set();", + " BaseCompiler.Instance = this;", + " }", + " buildExecutionOrder(graph, context) {", + " graph.nodesInOrder.length = 0;", + " this.visited.clear();", + " for (let i = 0; i < graph.outputNodes.length; i++) {", + " const node = graph.outputNodes[i];", + " if (node.inputs[0] !== undefined) {", + " this._traverse(node, graph, context);", + " }", + " }", + " graph.compiled = true;", + " }", + " _traverse(node, graph, context) {", + " this.visited.add(node.id);", + " for (let i = 0; i < node.inputs.length; i++) {", + " if (node.inputs[i] instanceof Wire) {", + " const inputNode = node.inputs[i].input;", + " if (!this.visited.has(inputNode.id)) {", + " this._traverse(inputNode, graph, context);", + " }", + " }", + " else if (node.inputs[i] !== undefined);", + " else;", + " }", + " graph.nodesInOrder.push(node);", + " }", + "}", + "", + "class Interpreter extends BaseCompiler {", + " constructor() {", + " super();", + " }", + " executeCompiledGraph(graph, context) {", + " const nodes = graph.nodesInOrder;", + " for (let i = 0; i < nodes.length; i++) {", + " const inputValues = [];", + " const node = nodes[i];", + " for (let j = 0; j < node.inputs.length; j++) {", + " if (node.inputs[j] instanceof Wire) {", + " inputValues.push(node.inputs[j].input.outputValues[node.inputs[j].inputIndex]);", + " }", + " else if (node.inputs[j] !== undefined) {", + " inputValues.push(node.inputs[j].getValue(context));", + " }", + " else {", + " inputValues.push(undefined);", + " }", + " }", + " node.func(context, inputValues, node.outputValues);", + " }", + " }", + " run(graph, context) {", + " if (!graph.compiled) {", + " this.buildExecutionOrder(graph, context);", + " }", + " this.executeCompiledGraph(graph, context);", + " }", + "}", + "", + "class NodeType {", + " constructor(name) {", + " this.nodeTypeSignatures = [];", + " this.name = name;", + " }", + " addSignature(inputTypes, outputTypes, func) {", + " this.nodeTypeSignatures.push({", + " inputTypes: inputTypes,", + " outputTypes: outputTypes,", + " func: func,", + " });", + " }", + "}", + "class GraphNodeType extends NodeType {", + " constructor(nodeGraph) {", + " const inputTypes = [];", + " for (let i = 0; i < nodeGraph.inputNodes.length; i++) {", + " if (nodeGraph.inputNodes[i].type.name === 'input') {", + " inputTypes.push(nodeGraph.inputNodes[i].data.type);", + " }", + " }", + " const outputTypes = [];", + " for (let i = 0; i < nodeGraph.outputNodes.length; i++) {", + " if (nodeGraph.outputNodes[i].type.name === 'output') {", + " outputTypes.push(nodeGraph.outputNodes[i].data.type);", + " }", + " }", + " super(nodeGraph.name);", + " this.addSignature(inputTypes, outputTypes, (context, data, inputs, outputs) => {", + " context.inputs = inputs;", + " context.outputs = outputs;", + " Interpreter.Instance.run(nodeGraph, context);", + " });", + " this.nodeGraph = nodeGraph;", + " }", + "}", + "", + "const NodeTypes = {};", + "const addNode = new NodeType('add');", + "addNode.addSignature([NodeValueType.Number, NodeValueType.Number], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0] + inputs[1];", + "});", + "addNode.addSignature([NodeValueType.Vec2, NodeValueType.Vec2], [NodeValueType.Vec2], (context, data, inputs, outputs) => {", + " outputs[0].addVectors(inputs[0], inputs[1]);", + "});", + "addNode.addSignature([NodeValueType.Vec3, NodeValueType.Vec3], [NodeValueType.Vec3], (context, data, inputs, outputs) => {", + " outputs[0].addVectors(inputs[0], inputs[1]);", + "});", + "addNode.addSignature([NodeValueType.Vec4, NodeValueType.Vec4], [NodeValueType.Vec4], (context, data, inputs, outputs) => {", + " outputs[0].addVectors(inputs[0], inputs[1]);", + "});", + "NodeTypes['add'] = addNode;", + "const subNode = new NodeType('sub');", + "subNode.addSignature([NodeValueType.Number, NodeValueType.Number], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0] - inputs[1];", + "});", + "subNode.addSignature([NodeValueType.Vec2, NodeValueType.Vec2], [NodeValueType.Vec2], (context, data, inputs, outputs) => {", + " outputs[0].subVectors(inputs[0], inputs[1]);", + "});", + "subNode.addSignature([NodeValueType.Vec3, NodeValueType.Vec3], [NodeValueType.Vec3], (context, data, inputs, outputs) => {", + " outputs[0].subVectors(inputs[0], inputs[1]);", + "});", + "subNode.addSignature([NodeValueType.Vec4, NodeValueType.Vec4], [NodeValueType.Vec4], (context, data, inputs, outputs) => {", + " outputs[0].subVectors(inputs[0], inputs[1]);", + "});", + "NodeTypes['sub'] = subNode;", + "const mulNode = new NodeType('mul');", + "mulNode.addSignature([NodeValueType.Number, NodeValueType.Number], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0] * inputs[1];", + "});", + "mulNode.addSignature([NodeValueType.Vec2, NodeValueType.Number], [NodeValueType.Vec2], (context, data, inputs, outputs) => {", + " outputs[0].copy(inputs[0]).multiplyScalar(inputs[1]);", + "});", + "mulNode.addSignature([NodeValueType.Vec3, NodeValueType.Number], [NodeValueType.Vec3], (context, data, inputs, outputs) => {", + " outputs[0].copy(inputs[0]).multiplyScalar(inputs[1]);", + "});", + "mulNode.addSignature([NodeValueType.Vec4, NodeValueType.Number], [NodeValueType.Vec4], (context, data, inputs, outputs) => {", + " outputs[0].copy(inputs[0]).multiplyScalar(inputs[1]);", + "});", + "NodeTypes['mul'] = mulNode;", + "const divNode = new NodeType('div');", + "divNode.addSignature([NodeValueType.Number, NodeValueType.Number], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0] / inputs[1];", + "});", + "divNode.addSignature([NodeValueType.Vec2, NodeValueType.Number], [NodeValueType.Vec2], (context, data, inputs, outputs) => {", + " outputs[0].copy(inputs[0]).divideScalar(inputs[1]);", + "});", + "divNode.addSignature([NodeValueType.Vec3, NodeValueType.Number], [NodeValueType.Vec3], (context, data, inputs, outputs) => {", + " outputs[0].copy(inputs[0]).divideScalar(inputs[1]);", + "});", + "divNode.addSignature([NodeValueType.Vec4, NodeValueType.Number], [NodeValueType.Vec4], (context, data, inputs, outputs) => {", + " outputs[0].copy(inputs[0]).divideScalar(inputs[1]);", + "});", + "NodeTypes['div'] = divNode;", + "const sinNode = new NodeType('sin');", + "sinNode.addSignature([NodeValueType.Number], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = Math.sin(inputs[0]);", + "});", + "NodeTypes['sin'] = sinNode;", + "const cosNode = new NodeType('cos');", + "cosNode.addSignature([NodeValueType.Number], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = Math.cos(inputs[0]);", + "});", + "NodeTypes['cos'] = cosNode;", + "const tanNode = new NodeType('tan');", + "tanNode.addSignature([NodeValueType.Number], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = Math.tan(inputs[0]);", + "});", + "NodeTypes['tan'] = tanNode;", + "const absNode = new NodeType('abs');", + "absNode.addSignature([NodeValueType.Number], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = Math.abs(inputs[0]);", + "});", + "NodeTypes['abs'] = absNode;", + "const minNode = new NodeType('min');", + "minNode.addSignature([NodeValueType.Number, NodeValueType.Number], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = Math.min(inputs[0], inputs[1]);", + "});", + "NodeTypes['min'] = minNode;", + "const maxNode = new NodeType('max');", + "maxNode.addSignature([NodeValueType.Number, NodeValueType.Number], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = Math.max(inputs[0], inputs[1]);", + "});", + "NodeTypes['max'] = maxNode;", + "const dot = new NodeType('dot');", + "dot.addSignature([NodeValueType.Vec2, NodeValueType.Vec2], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0].dot(inputs[1]);", + "});", + "dot.addSignature([NodeValueType.Vec3, NodeValueType.Vec3], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0].dot(inputs[1]);", + "});", + "dot.addSignature([NodeValueType.Vec4, NodeValueType.Vec4], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0].dot(inputs[1]);", + "});", + "NodeTypes['dot'] = dot;", + "const cross = new NodeType('cross');", + "cross.addSignature([NodeValueType.Vec3, NodeValueType.Vec3], [NodeValueType.Vec3], (context, data, inputs, outputs) => {", + " outputs[0].crossVectors(inputs[0], inputs[1]);", + "});", + "NodeTypes['cross'] = cross;", + "const length = new NodeType('length');", + "length.addSignature([NodeValueType.Vec2], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0].length();", + "});", + "length.addSignature([NodeValueType.Vec3], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0].length();", + "});", + "length.addSignature([NodeValueType.Vec4], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0].length();", + "});", + "NodeTypes['length'] = length;", + "const lengthSq = new NodeType('lengthSq');", + "lengthSq.addSignature([NodeValueType.Vec2], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0].lengthSq();", + "});", + "lengthSq.addSignature([NodeValueType.Vec3], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0].lengthSq();", + "});", + "lengthSq.addSignature([NodeValueType.Vec4], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0].lengthSq();", + "});", + "NodeTypes['lengthSq'] = lengthSq;", + "const normalize = new NodeType('normalize');", + "normalize.addSignature([NodeValueType.Vec2], [NodeValueType.Vec2], (context, data, inputs, outputs) => {", + " outputs[0].copy(inputs[0]).normalize();", + "});", + "normalize.addSignature([NodeValueType.Vec3], [NodeValueType.Vec3], (context, data, inputs, outputs) => {", + " outputs[0].copy(inputs[0]).normalize();", + "});", + "normalize.addSignature([NodeValueType.Vec4], [NodeValueType.Vec4], (context, data, inputs, outputs) => {", + " outputs[0].copy(inputs[0]).normalize();", + "});", + "NodeTypes['normalize'] = normalize;", + "const distance = new NodeType('distance');", + "distance.addSignature([NodeValueType.Vec2, NodeValueType.Vec2], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0].distanceTo(inputs[1]);", + "});", + "distance.addSignature([NodeValueType.Vec3, NodeValueType.Vec3], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0].distanceTo(inputs[1]);", + "});", + "NodeTypes['distance'] = distance;", + "const andNode = new NodeType('and');", + "andNode.addSignature([NodeValueType.Boolean, NodeValueType.Boolean], [NodeValueType.Boolean], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0] && inputs[1];", + "});", + "NodeTypes['and'] = andNode;", + "const orNode = new NodeType('or');", + "orNode.addSignature([NodeValueType.Boolean, NodeValueType.Boolean], [NodeValueType.Boolean], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0] || inputs[1];", + "});", + "NodeTypes['or'] = orNode;", + "const notNode = new NodeType('not');", + "notNode.addSignature([NodeValueType.Boolean], [NodeValueType.Boolean], (context, data, inputs, outputs) => {", + " outputs[0] = !inputs[0];", + "});", + "NodeTypes['not'] = notNode;", + "const equalNode = new NodeType('equal');", + "equalNode.addSignature([NodeValueType.Number, NodeValueType.Number], [NodeValueType.Boolean], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0] === inputs[1];", + "});", + "equalNode.addSignature([NodeValueType.Vec2, NodeValueType.Vec2], [NodeValueType.Boolean], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0].equals(inputs[1]);", + "});", + "equalNode.addSignature([NodeValueType.Vec3, NodeValueType.Vec3], [NodeValueType.Boolean], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0].equals(inputs[1]);", + "});", + "equalNode.addSignature([NodeValueType.Vec4, NodeValueType.Vec4], [NodeValueType.Boolean], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0].equals(inputs[1]);", + "});", + "NodeTypes['equal'] = equalNode;", + "const lessThanNode = new NodeType('lessThan');", + "lessThanNode.addSignature([NodeValueType.Number, NodeValueType.Number], [NodeValueType.Boolean], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0] < inputs[1];", + "});", + "NodeTypes['lessThan'] = lessThanNode;", + "const greaterThanNode = new NodeType('greaterThan');", + "greaterThanNode.addSignature([NodeValueType.Number, NodeValueType.Number], [NodeValueType.Boolean], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0] > inputs[1];", + "});", + "NodeTypes['greaterThan'] = greaterThanNode;", + "const lessThanOrEqualNode = new NodeType('lessThanOrEqual');", + "lessThanOrEqualNode.addSignature([NodeValueType.Number, NodeValueType.Number], [NodeValueType.Boolean], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0] <= inputs[1];", + "});", + "NodeTypes['lessThanOrEqual'] = lessThanOrEqualNode;", + "const greaterThanOrEqualNode = new NodeType('greaterThanOrEqual');", + "greaterThanOrEqualNode.addSignature([NodeValueType.Number, NodeValueType.Number], [NodeValueType.Boolean], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0] >= inputs[1];", + "});", + "NodeTypes['greaterThanOrEqual'] = greaterThanOrEqualNode;", + "const ifNode = new NodeType('if');", + "ifNode.addSignature([NodeValueType.Boolean, NodeValueType.AnyType, NodeValueType.AnyType], [NodeValueType.AnyType], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0] ? inputs[1] : inputs[2];", + "});", + "NodeTypes['if'] = ifNode;", + "const numberNode = new NodeType('number');", + "numberNode.addSignature([], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = data.value;", + "});", + "NodeTypes['number'] = numberNode;", + "const vec2Node = new NodeType('vec2');", + "vec2Node.addSignature([NodeValueType.Number, NodeValueType.Number], [NodeValueType.Vec2], (context, data, inputs, outputs) => {", + " outputs[0].x = inputs[0];", + " outputs[0].y = inputs[1];", + "});", + "NodeTypes['vec2'] = vec2Node;", + "const vec3Node = new NodeType('vec3');", + "vec3Node.addSignature([NodeValueType.Number, NodeValueType.Number, NodeValueType.Number], [NodeValueType.Vec3], (context, data, inputs, outputs) => {", + " outputs[0].x = inputs[0];", + " outputs[0].y = inputs[1];", + " outputs[0].z = inputs[2];", + "});", + "NodeTypes['vec3'] = vec3Node;", + "const vec4Node = new NodeType('vec4');", + "vec4Node.addSignature([NodeValueType.Number, NodeValueType.Number, NodeValueType.Number, NodeValueType.Number], [NodeValueType.Vec4], (context, data, inputs, outputs) => {", + " outputs[0].x = inputs[0];", + " outputs[0].y = inputs[1];", + " outputs[0].z = inputs[2];", + " outputs[0].w = inputs[3];", + "});", + "NodeTypes['vec4'] = vec4Node;", + "const splitVec2Node = new NodeType('splitVec2');", + "splitVec2Node.addSignature([NodeValueType.Vec2], [NodeValueType.Number, NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0].x;", + " outputs[1] = inputs[0].y;", + "});", + "NodeTypes['splitVec2'] = splitVec2Node;", + "const splitVec3Node = new NodeType('splitVec3');", + "splitVec3Node.addSignature([NodeValueType.Vec3], [NodeValueType.Number, NodeValueType.Number, NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0].x;", + " outputs[1] = inputs[0].y;", + " outputs[2] = inputs[0].z;", + "});", + "NodeTypes['splitVec3'] = splitVec3Node;", + "const splitVec4Node = new NodeType('splitVec4');", + "splitVec4Node.addSignature([NodeValueType.Vec4], [NodeValueType.Number, NodeValueType.Number, NodeValueType.Number, NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0].x;", + " outputs[1] = inputs[0].y;", + " outputs[2] = inputs[0].z;", + " outputs[3] = inputs[0].w;", + "});", + "NodeTypes['splitVec4'] = splitVec4Node;", + "const boolNode = new NodeType('bool');", + "boolNode.addSignature([], [NodeValueType.Boolean], (context, data, inputs, outputs) => {", + " outputs[0] = data.value;", + "});", + "NodeTypes['bool'] = boolNode;", + "const particlePropertyNode = new NodeType('particleProperty');", + "particlePropertyNode.addSignature([NodeValueType.NullableAnyType], [NodeValueType.NullableAnyType], (context, data, inputs, outputs) => {", + " if (inputs[0] !== undefined) {", + " if (typeof inputs[0] === 'object') {", + " context.particle[data.property].copy(inputs[0]);", + " }", + " else {", + " context.particle[data.property] = inputs[0];", + " }", + " }", + " if (context.particle[data.property] !== undefined) {", + " if (typeof outputs[0] === 'object') {", + " outputs[0].copy(context.particle[data.property]);", + " }", + " else {", + " outputs[0] = context.particle[data.property];", + " }", + " }", + "});", + "NodeTypes['particleProperty'] = particlePropertyNode;", + "const emitNode = new NodeType('emit');", + "emitNode.addSignature([NodeValueType.EventStream], [], (context, data, inputs, outputs) => {", + " const arr = inputs[0];", + " for (let i = 0; i < arr.length; i++) {", + " context.signal(i, arr[i]);", + " }", + "});", + "NodeTypes['emit'] = emitNode;", + "const graphPropertyNode = new NodeType('graphProperty');", + "graphPropertyNode.addSignature([NodeValueType.NullableAnyType], [NodeValueType.NullableAnyType], (context, data, inputs, outputs) => {", + " if (inputs[0] !== undefined) {", + " if (typeof inputs[0] === 'object') {", + " context.graph[data.property].copy(inputs[0]);", + " }", + " else {", + " context.graph[data.property] = inputs[0];", + " }", + " }", + " if (context.graph[data.property] !== undefined) {", + " if (typeof outputs[0] === 'object') {", + " outputs[0].copy(context.graph[data.property]);", + " }", + " else {", + " outputs[0] = context.graph[data.property];", + " }", + " }", + "});", + "NodeTypes['graphProperty'] = graphPropertyNode;", + "const startEventNode = new NodeType('startEvent');", + "startEventNode.addSignature([], [NodeValueType.EventStream], (context, data, inputs, outputs) => {", + " outputs[0] = [{ type: 'start' }];", + "});", + "NodeTypes['startEvent'] = startEventNode;", + "const repeaterNode = new NodeType('repeater');", + "repeaterNode.addSignature([NodeValueType.EventStream, NodeValueType.Number], [NodeValueType.EventStream], (context, data, inputs, outputs) => {", + " const arr = inputs[0];", + " const count = inputs[1];", + " const result = [];", + " for (let j = 0; j < arr.length; j++) {", + " for (let i = 0; i < count; i++) {", + " result.push(arr[j]);", + " }", + " }", + " outputs[0] = result;", + "});", + "NodeTypes['repeater'] = repeaterNode;", + "const timeNode = new NodeType('time');", + "timeNode.addSignature([], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = context.emissionState.time;", + "});", + "NodeTypes['time'] = timeNode;", + "const deltaNode = new NodeType('delta');", + "deltaNode.addSignature([], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = context.delta;", + "});", + "NodeTypes['delta'] = deltaNode;", + "const outputNode = new NodeType('output');", + "outputNode.addSignature([NodeValueType.Number], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0];", + "});", + "outputNode.addSignature([NodeValueType.Vec2], [NodeValueType.Vec2], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0];", + "});", + "outputNode.addSignature([NodeValueType.Vec3], [NodeValueType.Vec3], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0];", + "});", + "outputNode.addSignature([NodeValueType.Vec4], [NodeValueType.Vec4], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0];", + "});", + "outputNode.addSignature([NodeValueType.Boolean], [NodeValueType.Boolean], (context, data, inputs, outputs) => {", + " outputs[0] = inputs[0];", + "});", + "NodeTypes['output'] = outputNode;", + "const lerpNode = new NodeType('lerp');", + "lerpNode.addSignature([NodeValueType.Number, NodeValueType.Number, NodeValueType.Number], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] =", + " inputs[0] * (1 - inputs[2]) + inputs[1] * inputs[2];", + "});", + "lerpNode.addSignature([NodeValueType.Vec2, NodeValueType.Vec2, NodeValueType.Number], [NodeValueType.Vec2], (context, data, inputs, outputs) => {", + " outputs[0].lerpVectors(inputs[0], inputs[1], inputs[2]);", + "});", + "lerpNode.addSignature([NodeValueType.Vec3, NodeValueType.Vec3, NodeValueType.Number], [NodeValueType.Vec3], (context, data, inputs, outputs) => {", + " outputs[0].lerpVectors(inputs[0], inputs[1], inputs[2]);", + "});", + "lerpNode.addSignature([NodeValueType.Vec4, NodeValueType.Vec4, NodeValueType.Number], [NodeValueType.Vec4], (context, data, inputs, outputs) => {", + " outputs[0].lerpVectors(inputs[0], inputs[1], inputs[2]);", + "});", + "NodeTypes['lerp'] = lerpNode;", + "const normalD = (x) => {", + " return (1 / Math.sqrt(2 * Math.PI)) * Math.exp(x * x * -0.5);", + "};", + "const normalDistributionNode = new NodeType('normDistrib');", + "normalDistributionNode.addSignature([NodeValueType.Number], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = normalD(inputs[0]);", + "});", + "NodeTypes['normDistrib'] = normalDistributionNode;", + "const normcdfNode = new NodeType('normcdf');", + "normcdfNode.addSignature([NodeValueType.Number], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " let x = inputs[0];", + " const a1 = 0.254829592;", + " const a2 = -0.284496736;", + " const a3 = 1.421413741;", + " const a4 = -1.453152027;", + " const a5 = 1.061405429;", + " const p = 0.3275911;", + " let sign = 1;", + " if (x < 0)", + " sign = -1;", + " x = Math.abs(x) / Math.sqrt(2.0);", + " const t = 1.0 / (1.0 + p * x);", + " const y = 1.0 - ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);", + " outputs[0] = 0.5 * (1.0 + sign * y);", + "});", + "NodeTypes['normcdf'] = normcdfNode;", + "const normcdfInvNode = new NodeType('normcdfInv');", + "const rationalApproximation = (t) => {", + " const c = [2.515517, 0.802853, 0.010328];", + " const d = [1.432788, 0.189269, 0.001308];", + " return t - ((c[2] * t + c[1]) * t + c[0]) / (((d[2] * t + d[1]) * t + d[0]) * t + 1.0);", + "};", + "const normcdfInv = (p) => {", + " if (p < 0.5) {", + " return -rationalApproximation(Math.sqrt(-2.0 * Math.log(p)));", + " }", + " else {", + " return rationalApproximation(Math.sqrt(-2.0 * Math.log(1 - p)));", + " }", + "};", + "normcdfInvNode.addSignature([NodeValueType.Number], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = normcdfInv(inputs[0]);", + "});", + "NodeTypes['normcdfInv'] = normcdfInvNode;", + "const clampNode = new NodeType('clamp');", + "clampNode.addSignature([NodeValueType.Number, NodeValueType.Number, NodeValueType.Number], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = Math.max(Math.min(inputs[0], inputs[2]), inputs[1]);", + "});", + "NodeTypes['clamp'] = clampNode;", + "const smoothstepNode = new NodeType('smoothstep');", + "smoothstepNode.addSignature([NodeValueType.Number, NodeValueType.Number, NodeValueType.Number], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " const x = Math.max(Math.min(inputs[0], inputs[2]), inputs[1]);", + " outputs[0] = x * x * (3 - 2 * x);", + "});", + "NodeTypes['smoothstep'] = smoothstepNode;", + "const randomNode = new NodeType('random');", + "randomNode.addSignature([NodeValueType.Number, NodeValueType.Number], [NodeValueType.Number], (context, data, inputs, outputs) => {", + " outputs[0] = Math.random() * (inputs[1] - inputs[0]) + inputs[0];", + "});", + "randomNode.addSignature([NodeValueType.Vec2, NodeValueType.Vec2], [NodeValueType.Vec2], (context, data, inputs, outputs) => {", + " let random = Math.random();", + " outputs[0].lerpVectors(inputs[0], inputs[1], random);", + "});", + "randomNode.addSignature([NodeValueType.Vec3, NodeValueType.Vec3], [NodeValueType.Vec3], (context, data, inputs, outputs) => {", + " let random = Math.random();", + " outputs[0].lerpVectors(inputs[0], inputs[1], random);", + "});", + "randomNode.addSignature([NodeValueType.Vec4, NodeValueType.Vec4], [NodeValueType.Vec4], (context, data, inputs, outputs) => {", + " let random = Math.random();", + " outputs[0].lerpVectors(inputs[0], inputs[1], random);", + "});", + "NodeTypes['random'] = randomNode;", + "const vrandNode = new NodeType('vrand');", + "vrandNode.addSignature([], [NodeValueType.Vec2], (context, data, inputs, outputs) => {", + " let x = normcdfInv(Math.random());", + " let y = normcdfInv(Math.random());", + " const mag = Math.sqrt(x * x + y * y);", + " outputs[0].set(x / mag, y / mag);", + "});", + "vrandNode.addSignature([], [NodeValueType.Vec3], (context, data, inputs, outputs) => {", + " let x = normcdfInv(Math.random());", + " let y = normcdfInv(Math.random());", + " let z = normcdfInv(Math.random());", + " const mag = Math.sqrt(x * x + y * y + z * z);", + " outputs[0].set(x / mag, y / mag, z / mag);", + "});", + "vrandNode.addSignature([], [NodeValueType.Vec4], (context, data, inputs, outputs) => {", + " let x = normcdfInv(Math.random());", + " let y = normcdfInv(Math.random());", + " let z = normcdfInv(Math.random());", + " let w = normcdfInv(Math.random());", + " const mag = Math.sqrt(x * x + y * y + z * z + w * w);", + " outputs[0].set(x / mag, y / mag, z / mag, w / mag);", + "});", + "NodeTypes['vrand'] = vrandNode;", + "const bsdfNode = new NodeType('bsdf');", + "bsdfNode.addSignature([NodeValueType.Vec3, NodeValueType.Vec3, NodeValueType.Vec3, NodeValueType.Number], [], (context, data, inputs, outputs) => { });", + "NodeTypes['bsdf'] = bsdfNode;", + "const OutputNodeTypeNames = new Set(['output', 'particleProperty', 'graphProperty', 'emit']);", + "", + "class NodeGraph {", + " constructor(name) {", + " this.inputNodes = [];", + " this.outputNodes = [];", + " this.allNodes = new Map();", + " this.wires = [];", + " this.compiled = false;", + " this.nodesInOrder = [];", + " this.version = '1.0';", + " this.uuid = THREE.MathUtils.generateUUID();", + " this.name = name;", + " this.revision = 0;", + " }", + " addWire(wire) {", + " this.wires.push(wire);", + " this.revision++;", + " }", + " addNode(node) {", + " this.allNodes.set(node.id, node);", + " if (node.type === NodeTypes['input']) {", + " this.inputNodes.push(node);", + " }", + " else if (OutputNodeTypeNames.has(node.type.name)) {", + " this.outputNodes.push(node);", + " }", + " this.revision++;", + " }", + " getNode(id) {", + " return this.allNodes.get(id);", + " }", + " deleteNode(node) {", + " this.allNodes.delete(node.id);", + " this.revision++;", + " }", + " deleteWire(wire) {", + " let index = wire.input.outputs[wire.inputIndex].indexOf(wire);", + " if (index !== -1) {", + " wire.input.outputs[wire.inputIndex].splice(index, 1);", + " }", + " wire.output.inputs[wire.outputIndex] = undefined;", + " index = this.wires.indexOf(wire);", + " if (index != -1) {", + " this.wires[index] = this.wires[this.wires.length - 1];", + " this.wires.pop();", + " }", + " this.revision++;", + " }", + " toJSON() {", + " throw new Error('not implemented');", + " }", + " clone() {", + " throw new Error('not implemented');", + " }", + "}", + "", + "new THREE.Vector3(0, 0, 1);", + "const tempQ = new THREE.Quaternion();", + "const tempV = new THREE.Vector3();", + "const tempV2 = new THREE.Vector3();", + "const PREWARM_FPS = 60;", + "const DEFAULT_GEOMETRY = new THREE.PlaneGeometry(1, 1, 1, 1);", + "class NodeVFX {", + " set time(time) {", + " this.emissionState.time = time;", + " }", + " get time() {", + " return this.emissionState.time;", + " }", + " get layers() {", + " return this.rendererSettings.layers;", + " }", + " get texture() {", + " return this.rendererSettings.material.map;", + " }", + " set texture(texture) {", + " this.rendererSettings.material.map = texture;", + " this.neededToUpdateRender = true;", + " }", + " get material() {", + " return this.rendererSettings.material;", + " }", + " set material(material) {", + " this.rendererSettings.material = material;", + " this.neededToUpdateRender = true;", + " }", + " get instancingGeometry() {", + " return this.rendererSettings.instancingGeometry;", + " }", + " set instancingGeometry(geometry) {", + " this.restart();", + " this.particles.length = 0;", + " this.rendererSettings.instancingGeometry = geometry;", + " this.neededToUpdateRender = true;", + " }", + " get renderMode() {", + " return this.rendererSettings.renderMode;", + " }", + " set renderMode(renderMode) {", + " if ((this.rendererSettings.renderMode != RenderMode.Trail && renderMode === RenderMode.Trail) ||", + " (this.rendererSettings.renderMode == RenderMode.Trail && renderMode !== RenderMode.Trail)) {", + " this.restart();", + " this.particles.length = 0;", + " }", + " if (this.rendererSettings.renderMode !== renderMode) {", + " switch (renderMode) {", + " case RenderMode.Trail:", + " this.rendererEmitterSettings = {", + " startLength: 30,", + " followLocalOrigin: false,", + " };", + " break;", + " case RenderMode.Mesh:", + " this.rendererEmitterSettings = {", + " geometry: new THREE.PlaneGeometry(1, 1),", + " };", + " break;", + " case RenderMode.BillBoard:", + " case RenderMode.VerticalBillBoard:", + " case RenderMode.HorizontalBillBoard:", + " case RenderMode.StretchedBillBoard:", + " this.rendererEmitterSettings = {};", + " break;", + " }", + " }", + " this.rendererSettings.renderMode = renderMode;", + " this.neededToUpdateRender = true;", + " }", + " get renderOrder() {", + " return this.rendererSettings.renderOrder;", + " }", + " set renderOrder(renderOrder) {", + " this.rendererSettings.renderOrder = renderOrder;", + " this.neededToUpdateRender = true;", + " }", + " get blending() {", + " return this.rendererSettings.material.blending;", + " }", + " set blending(blending) {", + " this.rendererSettings.material.blending = blending;", + " this.neededToUpdateRender = true;", + " }", + " constructor(parameters) {", + " var _a, _b, _c, _d, _e, _f, _g;", + " this.temp = new THREE.Vector3();", + " this.travelDistance = 0;", + " this.normalMatrix = new THREE.Matrix3();", + " this.speedFactor = 0;", + " this.autoDestroy = parameters.autoDestroy === undefined ? false : parameters.autoDestroy;", + " this.duration = (_a = parameters.duration) !== null && _a !== void 0 ? _a : 1;", + " this.looping = parameters.looping === undefined ? true : parameters.looping;", + " this.prewarm = parameters.prewarm === undefined ? false : parameters.prewarm;", + " this.worldSpace = (_b = parameters.worldSpace) !== null && _b !== void 0 ? _b : false;", + " this.rendererEmitterSettings = (_c = parameters.rendererEmitterSettings) !== null && _c !== void 0 ? _c : {};", + " this.emissionGraph = parameters.emissionGraph;", + " this.updateGraph = parameters.updateGraph;", + " this.interpreter = new Interpreter();", + " this.rendererSettings = {", + " instancingGeometry: (_d = parameters.instancingGeometry) !== null && _d !== void 0 ? _d : DEFAULT_GEOMETRY,", + " renderMode: (_e = parameters.renderMode) !== null && _e !== void 0 ? _e : RenderMode.BillBoard,", + " renderOrder: (_f = parameters.renderOrder) !== null && _f !== void 0 ? _f : 0,", + " material: parameters.material,", + " layers: (_g = parameters.layers) !== null && _g !== void 0 ? _g : new THREE.Layers(),", + " uTileCount: 1,", + " vTileCount: 1,", + " };", + " this.neededToUpdateRender = true;", + " this.particles = new Array();", + " this.emitter = new ParticleEmitter(this);", + " this.paused = false;", + " this.particleNum = 0;", + " this.emissionState = {", + " time: 0,", + " };", + " this.emitEnded = false;", + " this.markForDestroy = false;", + " this.prewarmed = false;", + " }", + " pause() {", + " this.paused = true;", + " }", + " play() {", + " this.paused = false;", + " }", + " spawn(emissionState, matrix) {", + " tempQ.setFromRotationMatrix(matrix);", + " const translation = tempV;", + " const quaternion = tempQ;", + " const scale = tempV2;", + " matrix.decompose(translation, quaternion, scale);", + " this.particleNum++;", + " while (this.particles.length < this.particleNum) {", + " this.particles.push(new NodeParticle());", + " }", + " const particle = this.particles[this.particleNum - 1];", + " particle.reset();", + " this.interpreter.run(this.updateGraph, { particle: particle, emissionState: this.emissionState });", + " if (this.rendererSettings.renderMode === RenderMode.Trail &&", + " this.rendererEmitterSettings.followLocalOrigin) {", + " const trail = particle;", + " trail.localPosition = new THREE.Vector3().copy(trail.position);", + " }", + " if (this.worldSpace) {", + " particle.position.applyMatrix4(matrix);", + " particle.size *= (Math.abs(scale.x) + Math.abs(scale.y) + Math.abs(scale.z)) / 3;", + " particle.velocity.multiply(scale).applyMatrix3(this.normalMatrix);", + " if (particle.rotation && particle.rotation instanceof THREE.Quaternion) {", + " particle.rotation.multiplyQuaternions(tempQ, particle.rotation);", + " }", + " }", + " }", + " endEmit() {", + " this.emitEnded = true;", + " if (this.autoDestroy) {", + " this.markForDestroy = true;", + " }", + " }", + " dispose() {", + " if (this._renderer)", + " this._renderer.deleteSystem(this);", + " this.emitter.dispose();", + " if (this.emitter.parent)", + " this.emitter.parent.remove(this.emitter);", + " }", + " restart() {", + " this.paused = false;", + " this.particleNum = 0;", + " this.emissionState.time = 0;", + " this.emitEnded = false;", + " this.markForDestroy = false;", + " this.prewarmed = false;", + " }", + " update(delta) {", + " if (this.paused)", + " return;", + " let currentParent = this.emitter;", + " while (currentParent.parent) {", + " currentParent = currentParent.parent;", + " }", + " if (currentParent.type !== 'Scene') {", + " this.dispose();", + " return;", + " }", + " if (this.emitEnded && this.particleNum === 0) {", + " if (this.markForDestroy && this.emitter.parent)", + " this.dispose();", + " return;", + " }", + " if (this.looping && this.prewarm && !this.prewarmed) {", + " this.prewarmed = true;", + " for (let i = 0; i < this.duration * PREWARM_FPS; i++) {", + " this.update(1.0 / PREWARM_FPS);", + " }", + " }", + " if (delta > 0.1) {", + " delta = 0.1;", + " }", + " if (this.neededToUpdateRender) {", + " if (this._renderer)", + " this._renderer.updateSystem(this);", + " this.neededToUpdateRender = false;", + " }", + " this.emit(delta, this.emissionState, this.emitter.matrixWorld);", + " const context = { particle: undefined, emissionState: this.emissionState, delta };", + " for (let i = 0; i < this.particleNum; i++) {", + " context.particle = this.particles[i];", + " this.interpreter.run(this.updateGraph, context);", + " }", + " for (let i = 0; i < this.particleNum; i++) {", + " if (this.rendererEmitterSettings.followLocalOrigin &&", + " this.particles[i].localPosition) {", + " this.particles[i].position.copy(this.particles[i].localPosition);", + " if (this.particles[i].parentMatrix) {", + " this.particles[i].position.applyMatrix4(this.particles[i].parentMatrix);", + " }", + " else {", + " this.particles[i].position.applyMatrix4(this.emitter.matrixWorld);", + " }", + " }", + " else {", + " this.particles[i].position.addScaledVector(this.particles[i].velocity, delta);", + " }", + " this.particles[i].age += delta;", + " }", + " if (this.rendererSettings.renderMode === RenderMode.Trail) {", + " for (let i = 0; i < this.particleNum; i++) {", + " const particle = this.particles[i];", + " particle.update();", + " }", + " }", + " for (let i = 0; i < this.particleNum; i++) {", + " const particle = this.particles[i];", + " if (particle.died && (!(particle instanceof TrailParticle) || particle.previous.length === 0)) {", + " this.particles[i] = this.particles[this.particleNum - 1];", + " this.particles[this.particleNum - 1] = particle;", + " this.particleNum--;", + " i--;", + " }", + " }", + " }", + " emit(delta, emissionState, emitterMatrix) {", + " if (emissionState.time > this.duration) {", + " if (this.looping) {", + " emissionState.time -= this.duration;", + " }", + " else {", + " if (!this.emitEnded) {", + " this.endEmit();", + " }", + " }", + " }", + " this.normalMatrix.getNormalMatrix(emitterMatrix);", + " const context = {", + " signal: () => {", + " this.spawn(emissionState, emitterMatrix);", + " },", + " emissionState,", + " delta,", + " };", + " if (!this.emitEnded) {", + " this.interpreter.run(this.emissionGraph, context);", + " }", + " if (this.previousWorldPos === undefined)", + " this.previousWorldPos = new THREE.Vector3();", + " this.emitter.getWorldPosition(this.previousWorldPos);", + " emissionState.time += delta;", + " }", + " toJSON(meta, options = {}) {", + " return {};", + " }", + " getRendererSettings() {", + " return this.rendererSettings;", + " }", + " clone() {", + " return this;", + " }", + "}", + "", + "gdjs.__particleEmmiter3DExtension = {", + " ParticleEmitter3DRenderer,", + " ParticleEmitterAdapter,", + " ParticleEmitterHelper,", + "", + " ApplyCollision,", + " ApplyForce,", + " ApplySequences,", + " AxisAngleGenerator,", + " BatchedParticleRenderer,", + " BatchedRenderer,", + " BehaviorFromJSON,", + " BehaviorTypes,", + " Bezier,", + " ChangeEmitDirection,", + " ColorGeneratorFromJSON,", + " ColorOverLife,", + " ColorRange,", + " ConeEmitter,", + " ConstantColor,", + " ConstantValue,", + " DonutEmitter,", + " EmitSubParticleSystem,", + " EmitterFromJSON,", + " EmitterMode,", + " EmitterShapes,", + " EulerGenerator,", + " ForceOverLife,", + " FrameOverLife,", + " GeneratorFromJSON,", + " Gradient,", + " GraphNodeType,", + " GravityForce,", + " GridEmitter,", + " Interpreter,", + " IntervalValue,", + " MeshSurfaceEmitter,", + " Node,", + " NodeGraph,", + " NodeType,", + " NodeTypes,", + " NodeValueType,", + " Noise,", + " OrbitOverLife,", + " ParticleEmitter,", + " ParticleSystem,", + " PiecewiseBezier,", + " PiecewiseFunction,", + " Plugins,", + " PointEmitter,", + " QuarksLoader,", + " RandomColor,", + " RandomColorBetweenGradient,", + " RandomQuatGenerator,", + " RecordState,", + " RenderMode,", + " Rotation3DOverLife,", + " RotationGeneratorFromJSON,", + " RotationOverLife,", + " SequencerFromJSON,", + " SizeOverLife,", + " SpeedOverLife,", + " SphereEmitter,", + " SpriteBatch,", + " SpriteParticle,", + " SubParticleEmitMode,", + " TextureSequencer,", + " TrailBatch,", + " TrailParticle,", + " TurbulenceField,", + " VFXBatch,", + " ValueGeneratorFromJSON,", + " WidthOverLength,", + " Wire,", + " genDefaultForNodeValueType,", + " getPhysicsResolver,", + " loadPlugin,", + " setPhysicsResolver,", + " unloadPlugin,", + "}" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onScenePreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "// See doStepPostEvents\r", + "runtimeScene.__particleEmmiter3DExtension = runtimeScene.__particleEmmiter3DExtension || {};\r", + "runtimeScene.__particleEmmiter3DExtension.emittersStepped = 0;" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "DefineHelperClasses" + }, + { + "functionName": "onScenePreEvents" + } + ] + }, + "eventsBasedBehaviors": [], + "eventsBasedObjects": [ + { + "areaMaxX": 64, + "areaMaxY": 64, + "areaMaxZ": 64, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "assetStoreTag": "3d particles", + "defaultName": "ParticleEmitter", + "description": "Display a large number of particles to create visual effects (fire, explosions, smoke, magic, rain, snow...). Particles are emitted along the emitter's local Z axis (upward by default): rotate the instance to aim them (180° on the X axis emits downward). The instance size has no effect: use the \"Emission zone radius\" property to emit from a whole area.", + "fullName": "3D particle emitter", + "helpPath": "", + "iconUrl": "", + "is3D": true, + "isUsingLegacyInstancesRenderer": true, + "name": "ParticleEmitter3D", + "previewIconUrl": "", + "objects": [ + { + "adaptCollisionMaskAutomatically": true, + "assetStoreId": "", + "name": "Particle", + "type": "Sprite", + "updateIfNotVisible": false, + "variables": [], + "effects": [], + "behaviors": [], + "animations": [ + { + "name": "Image", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [] + } + ] + } + ] + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Particle" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [], + "editionSettings": [], + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ParticleEmitter3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const {", + " ParticleEmitterAdapter,", + " ParticleEmitter3DRenderer,", + " ParticleEmitterHelper,", + "", + " ParticleSystem,", + " IntervalValue,", + " ConstantValue,", + " ConstantColor,", + " ColorOverLife,", + " SizeOverLife,", + " ApplyForce,", + " WidthOverLength,", + " Gradient,", + " PiecewiseBezier,", + " Bezier,", + " PointEmitter,", + " ConeEmitter,", + " RenderMode", + "} = gdjs.__particleEmmiter3DExtension;", + "", + "/** @type {gdjs.CustomRuntimeObject} */", + "const object = objects[0];", + "// Force the size of the object because it doesn't contain any instance.", + "object._innerArea = {", + " min: [-16, -16, -16],", + " max: [16, 16, 16],", + " };", + "const hitbox = new gdjs.Polygon();", + "hitbox.vertices = [[-16, -16], [16, -16], [16, 16], [-16, 16]];", + "object._untransformedHitBoxes = [hitbox];", + "object._unrotatedAABB.min[0] = -16;", + "object._unrotatedAABB.min[1] = -16;", + "object._unrotatedAABB.max[0] = 16;", + "object._unrotatedAABB.max[1] = 16;", + "object._updateUntransformedHitBoxes = () => {};", + "", + "// Here runtimeScene is the gdjs.CustomRuntimeObjectInstanceContainer inside the custom object.", + "const gameScene = object.getRuntimeScene();", + "const game = runtimeScene.getGame();", + "", + "const texture = game.getImageManager().getThreeTexture('');", + "", + "// Set the blending here because changes are not applied after the emitter creation.", + "const blendingString = object._getBlending();", + "const blending =", + " blendingString === \"Additive\" ? THREE.AdditiveBlending :", + " blendingString === \"Normal\" ? THREE.NormalBlending :", + " blendingString === \"Subtractive\" ? THREE.SubtractiveBlending :", + " blendingString === \"Multiply\" ? THREE.MultiplyBlending :", + " blendingString === \"None\" ? THREE.NoBlending :", + " THREE.AdditiveBlending;", + "", + "", + "// Build a configuration with the right kind of objects.", + "// These values are not important as they are overrided by the object properties values.", + "const muzzle = {", + " duration: 10,", + " looping: false,", + " startLife: new IntervalValue(1, 2),", + " startSpeed: new IntervalValue(100, 200),", + " startSize: new IntervalValue(20, 50),", + " startColor: new ConstantColor(new THREE.Vector4(1, 1, 1, 1)),", + " worldSpace: true,", + "", + " maxParticle: 5,", + " emissionOverTime: new ConstantValue(50),", + " emissionBursts: [{", + " time: 0,", + " count: new ConstantValue(1),", + " cycle: 1,", + " interval: 0.01,", + " probability: 1,", + " }],", + "", + " shape: new PointEmitter(),", + " material: new THREE.MeshBasicMaterial({", + " map: texture,", + " blending: blending,", + " transparent: true,", + " side: THREE.DoubleSide", + " }),", + " startTileIndex: 0,", + " uTileCount: 1,", + " vTileCount: 1,", + " renderOrder: 2,", + " renderMode: RenderMode.BillBoard", + "};", + "const particleSystem = new ParticleSystem(muzzle);", + "", + "const colorOverLife = new ColorOverLife(", + " new Gradient(", + " [", + " [new THREE.Vector3(1, 0, 0), 0],", + " [new THREE.Vector3(1, 0, 0), 1],", + " ],", + " [", + " [1, 0],", + " [1, 1],", + " ]", + " ));", + "particleSystem.addBehavior(colorOverLife);", + "const sizeOverLife = new SizeOverLife(new PiecewiseBezier([[new Bezier(1, 2 / 3, 1 / 3, 0), 0]]));", + "particleSystem.addBehavior(sizeOverLife);", + "const applyForce = new ApplyForce(new THREE.Vector3(0, 0, -1), new ConstantValue(0));", + "particleSystem.addBehavior(applyForce);", + "const widthOverLength = new WidthOverLength(new PiecewiseBezier([[new Bezier(1, 2 / 3, 1 / 3, 0), 0]]));", + "particleSystem.addBehavior(widthOverLength);", + "", + "particleSystem.emitter.name = object.getName() + object.getNameId();", + "", + "const inverseWorldScale = gameScene.getRenderer3DInverseWorldScale ? gameScene.getRenderer3DInverseWorldScale() : 1;", + "object.__particleEmitterAdapter = new ParticleEmitterAdapter(inverseWorldScale, particleSystem, colorOverLife, sizeOverLife, applyForce, widthOverLength);", + "object.__particleSystem = particleSystem;", + "", + "// This is a hack that may break in future releases.", + "// Replace the group that would hold children objects by the emmiter.", + "const layer = object.getInstanceContainer().getLayer(object.getLayer());", + "const group = object.getRenderer()._threeGroup;", + "layer.getRenderer().remove3DRendererObject(group);", + "particleSystem.emitter.position.copy(group.position);", + "particleSystem.emitter.rotation.order = 'ZYX';", + "particleSystem.emitter.rotation.copy(group.rotation);", + "", + "const particleEmitter3DRenderer = new ParticleEmitter3DRenderer(object, object._instanceContainer, object.getInstanceContainer());", + "object._renderer = particleEmitter3DRenderer;", + "if (game.isInGameEdition && game.isInGameEdition()) {", + " const particleEmitterHelper = new ParticleEmitterHelper(object);", + " particleEmitter3DRenderer._threeGroup = particleEmitterHelper;", + " layer.getRenderer().add3DRendererObject(particleEmitterHelper);", + "}", + "else {", + " particleEmitter3DRenderer._threeGroup = particleSystem.emitter;", + " layer.getRenderer().add3DRendererObject(particleSystem.emitter);", + "}", + "", + "// Update the parents too, so that the first particles are emitted with the", + "// scale of the layer (the 3D renderer world scale).", + "particleSystem.emitter.updateWorldMatrix(true, true);", + "", + "", + "// See doStepPostEvents", + "gameScene.__particleEmmiter3DExtension = gameScene.__particleEmmiter3DExtension || {};", + "gameScene.__particleEmmiter3DExtension.emittersCount = gameScene.__particleEmmiter3DExtension.emittersCount || 0;", + "gameScene.__particleEmmiter3DExtension.emittersCount++;", + "", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::UpdateFromProperties" + }, + "parameters": [ + "Object", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetRotationCenter" + }, + "parameters": [ + "Object", + "0", + "0", + "0" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDestroy", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject} */", + "const object = objects[0];", + "// Here runtimeScene is the gdjs.CustomRuntimeObjectInstanceContainer inside the custom object.", + "const gameScene = object.getRuntimeScene();", + "", + "object.__particleSystem.dispose();", + "", + "// See doStepPostEvents", + "gameScene.__particleEmmiter3DExtension.emittersCount--;", + "", + "if (gameScene.__particleEmmiter3DExtension.emittersCount === 0) {", + " // Update batch system now because doStepPostEvents won't be called.", + " gameScene.__particleEmmiter3DExtension.layerNames = gameScene.__particleEmmiter3DExtension.layerNames || [];", + " const layerNames = gameScene.__particleEmmiter3DExtension.layerNames;", + " gameScene.getAllLayerNames(layerNames);", + " for (const layerName of layerNames) {", + " const layer = gameScene.getLayer(layerName);", + " if (layer.__particleEmmiter3DExtension) {", + " layer.__particleEmmiter3DExtension.batchSystem.update(layer.getElapsedTime() / 1000);", + " }", + " }", + "}" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::UpdateFromProperties" + }, + "parameters": [ + "Object", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Update from properties.", + "fullName": "Update from properties", + "functionType": "Action", + "name": "UpdateFromProperties", + "private": true, + "sentence": "Update from properties of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetZ" + }, + "parameters": [ + "Object", + "=", + "Object.Z()", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetRotationX" + }, + "parameters": [ + "Object", + "=", + "Object.PropertyRotationX()", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetRotationY" + }, + "parameters": [ + "Object", + "=", + "Object.PropertyRotationY()", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetRenderMode" + }, + "parameters": [ + "Object", + "=", + "RenderMode", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetTrailStartLengthMin" + }, + "parameters": [ + "Object", + "=", + "TrailStartLengthMin", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetTrailStartLengthMax" + }, + "parameters": [ + "Object", + "=", + "TrailStartLengthMax", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetTrailEndWidthRatio" + }, + "parameters": [ + "Object", + "=", + "TrailEndWidthRatio", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetStartColor" + }, + "parameters": [ + "Object", + "=", + "Object.StartColor()", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetEndColor" + }, + "parameters": [ + "Object", + "=", + "Object.EndColor()", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetStartOpacity" + }, + "parameters": [ + "Object", + "=", + "Object.StartOpacity()", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetEndOpacity" + }, + "parameters": [ + "Object", + "=", + "Object.EndOpacity()", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetFlow" + }, + "parameters": [ + "Object", + "=", + "Object.Flow()", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetStartSizeMin" + }, + "parameters": [ + "Object", + "=", + "Object.StartSizeMin()", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetStartSizeMax" + }, + "parameters": [ + "Object", + "=", + "Object.StartSizeMax()", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetEndScale" + }, + "parameters": [ + "Object", + "=", + "Object.EndScale()", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetStartSpeedMin" + }, + "parameters": [ + "Object", + "=", + "Object.StartSpeedMin()", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetStartSpeedMax" + }, + "parameters": [ + "Object", + "=", + "Object.StartSpeedMax()", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetLifespanMin" + }, + "parameters": [ + "Object", + "=", + "Object.LifespanMin()", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetLifespanMax" + }, + "parameters": [ + "Object", + "=", + "Object.LifespanMax()", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetDuration" + }, + "parameters": [ + "Object", + "=", + "Object.Duration()", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetSpayConeAngle" + }, + "parameters": [ + "Object", + "=", + "Object.SpayConeAngle()", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetZoneRadius" + }, + "parameters": [ + "Object", + "=", + "Object.ZoneRadius()", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetGravity" + }, + "parameters": [ + "Object", + "=", + "Object.Gravity()", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetGravityTop" + }, + "parameters": [ + "Object", + "=", + "Object.GravityTop()", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::UpdateImage" + }, + "parameters": [ + "Object", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "IsTrailFollowingLocalOrigin", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetIsTrailFollowingLocalOrigin" + }, + "parameters": [ + "Object", + "=", + "TrailStartLengthMax" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsTrailFollowingLocalOrigin", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetIsTrailFollowingLocalOrigin" + }, + "parameters": [ + "Object", + "yes", + "TrailStartLengthMax" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "AreParticlesRelative", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetAreParticlesRelative" + }, + "parameters": [ + "Object", + "no", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "AreParticlesRelative", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetAreParticlesRelative" + }, + "parameters": [ + "Object", + "yes", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "TODO: Blending can't be changed." + }, + { + "disabled": true, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetBlending" + }, + "parameters": [ + "Object", + "=", + "Object.Blending()", + "" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::UpdateHelper" + }, + "parameters": [ + "Object", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "Update helper", + "functionType": "Action", + "name": "UpdateHelper", + "private": true, + "sentence": "Update graphical helper of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const game = runtimeScene.getGame();", + "if (game.isInGameEdition && game.isInGameEdition()) {", + " const particleEmitterHelper = objects[0].get3DRendererObject();", + " particleEmitterHelper.update();", + "}" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "Update particle image", + "functionType": "Action", + "name": "UpdateImage", + "private": true, + "sentence": "Update particle image of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */\r", + "const object = objects[0];\r", + "/** @type {SpriteObjectDataType} */\r", + "const particleSpriteData = object._instanceContainer._objects.get(\"Particle\");\r", + "/** @type {gdjs.SpriteAnimationData} */\r", + "const animation = particleSpriteData.animations[0];\r", + "const animationFrame = animation ? animation.directions[0].sprites[0] : null;\r", + "const resourceName = animationFrame ? animationFrame.image : '';\r", + "object.__particleEmitterAdapter.setImage(resourceName);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::HasEnded" + }, + "parameters": [ + "Object", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::Delete" + }, + "parameters": [ + "Object", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "ParticleEmitter3D::ParticleEmitter3D::HasEnded" + }, + "parameters": [ + "Object", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::RegisterInLayer" + }, + "parameters": [ + "Object", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "// Here runtimeScene is the gdjs.CustomRuntimeObjectInstanceContainer inside the custom object.", + "const gameScene = object.getRuntimeScene();", + "", + "// Update batch system after having moved every emitter.", + "// See onScenePreEvents", + "gameScene.__particleEmmiter3DExtension.emittersStepped++;", + "if (gameScene.__particleEmmiter3DExtension.emittersStepped === gameScene.__particleEmmiter3DExtension.emittersCount) {", + " gameScene.__particleEmmiter3DExtension.layerNames = gameScene.__particleEmmiter3DExtension.layerNames || [];", + " const layerNames = gameScene.__particleEmmiter3DExtension.layerNames;", + " gameScene.getAllLayerNames(layerNames);", + " for (const layerName of layerNames) {", + " const layer = gameScene.getLayer(layerName);", + " if (layer.__particleEmmiter3DExtension) {", + " const timeDelta = layer.getElapsedTime() / 1000;", + " if (timeDelta > 0) {", + " layer.__particleEmmiter3DExtension.batchSystem.update(timeDelta);", + " }", + " }", + " }", + "}" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "Register in layer", + "functionType": "Action", + "name": "RegisterInLayer", + "private": true, + "sentence": "Register _PARAM0_ in layer", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject} */", + "const object = objects[0];", + "", + "// TODO Handle layer changes?", + "if (object.__emitterLayerName === undefined) {", + " // Here runtimeScene is the gdjs.CustomRuntimeObjectInstanceContainer inside the custom object.", + " const gameScene = object.getRuntimeScene();", + "", + " let rootObject = object;", + " while (rootObject.getInstanceContainer().getOwner) {", + " rootObject = rootObject.getInstanceContainer().getOwner();", + " }", + "", + " const layer = gameScene.getLayer(rootObject.getLayer());", + " layer.__particleEmmiter3DExtension = layer.__particleEmmiter3DExtension || {};", + " if (!layer.__particleEmmiter3DExtension.batchSystem) {", + " const batchSystem = new gdjs.__particleEmmiter3DExtension.BatchedRenderer();", + " const worldScale = gameScene.getRenderer3DWorldScale ? gameScene.getRenderer3DWorldScale() : 1;", + " batchSystem.scale.set(worldScale, -worldScale, worldScale);", + " const threeScene = layer.getRenderer().getThreeScene();", + " if (threeScene) {", + " threeScene.add(batchSystem);", + " }", + " layer.__particleEmmiter3DExtension.batchSystem = batchSystem;", + " }", + " layer.__particleEmmiter3DExtension.batchSystem.addSystem(object.__particleSystem);", + " object.__emitterLayerName = layer.getName();", + "}" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Delete itself", + "fullName": "Delete itself", + "functionType": "Action", + "name": "Delete", + "private": true, + "sentence": "Delete _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];", + "", + "object.deleteFromScene(object.getParent());", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check that emission has ended and no particle is alive anymore.", + "fullName": "Emission has ended", + "functionType": "Condition", + "name": "HasEnded", + "sentence": "Emission from _PARAM0_ has ended", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "\r", + "eventsFunctionContext.returnValue =\r", + " object._getShouldAutodestruct()\r", + " && object.__particleSystem.emitEnded\r", + " && object.__particleSystem.particleNum === 0;\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Restart particule emission from the beginning.", + "fullName": "Restart", + "functionType": "Action", + "name": "Restart", + "sentence": "Restart particule emission from _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject} */\r", + "const object = objects[0];\r", + "\r", + "object.__particleSystem.restart();\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the Z position of the emitter.", + "fullName": "Z elevaltion (deprecated)", + "functionType": "ExpressionAndCondition", + "group": "Position", + "name": "Z", + "private": true, + "sentence": "the Z position", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.PropertyZ()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Z", + "group": "Position", + "name": "SetZ", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Z", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the rotation on X axis of the emitter.", + "fullName": "Rotation on X axis (deprecated)", + "functionType": "ExpressionAndCondition", + "group": "Angle", + "name": "RotationX", + "private": true, + "sentence": "the rotation on X axis", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.PropertyRotationX()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "RotationX", + "group": "Angle", + "name": "SetRotationX", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "RotationX", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "\r", + "object.__particleSystem.emitter.rotation.x = eventsFunctionContext.getArgument(\"Value\") * Math.PI / 180;" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the rotation on Y axis of the emitter.", + "fullName": "Rotation on Y axis (deprecated)", + "functionType": "ExpressionAndCondition", + "group": "Angle", + "name": "RotationY", + "private": true, + "sentence": "the rotation on Y axis", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.PropertyRotationY()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "RotationY", + "group": "Angle", + "name": "SetRotationY", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "RotationY", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "\r", + "object.__particleSystem.emitter.rotation.y = (90 + eventsFunctionContext.getArgument(\"Value\")) * Math.PI / 180;" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the start color of the object.", + "fullName": "Start color", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter color configuration", + "name": "StartColor", + "sentence": "the start color", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "StartColor" + ] + } + ] + } + ], + "expressionType": { + "type": "color" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "StartColor", + "group": "3D particle emitter color configuration", + "name": "SetStartColor", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "StartColor", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const startColor = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setStartColor(startColor);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the end color of the object.", + "fullName": "End color", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter color configuration", + "name": "EndColor", + "sentence": "the end color", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "EndColor" + ] + } + ] + } + ], + "expressionType": { + "type": "color" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "EndColor", + "group": "3D particle emitter color configuration", + "name": "SetEndColor", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "EndColor", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const endColor = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setEndColor(endColor);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the start opacity of the object.", + "fullName": "Start opacity", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter color configuration", + "name": "StartOpacity", + "sentence": "the start opacity", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "StartOpacity" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "StartOpacity", + "group": "3D particle emitter color configuration", + "name": "SetStartOpacity", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StartOpacity", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const startOpacity = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setStartOpacity(startOpacity);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the end opacity of the object.", + "fullName": "End opacity", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter color configuration", + "name": "EndOpacity", + "sentence": "the end opacity", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "EndOpacity" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "EndOpacity", + "group": "3D particle emitter color configuration", + "name": "SetEndOpacity", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "EndOpacity", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const endOpacity = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setEndOpacity(endOpacity);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the flow of particles of the object (particles per second).", + "fullName": "Flow of particles", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter configuration", + "name": "Flow", + "sentence": "the flow of particles", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Flow" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Flow", + "group": "3D particle emitter configuration", + "name": "SetFlow", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Flow", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const flow = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setFlow(flow);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the start min size of the object.", + "fullName": "Start min size", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter configuration", + "name": "StartSizeMin", + "sentence": "the start min size", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "StartSizeMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "StartSizeMin", + "group": "3D particle emitter configuration", + "name": "SetStartSizeMin", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StartSizeMin", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const startMinSize = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setStartMinSize(startMinSize);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the start max size of the object.", + "fullName": "Start max size", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter configuration", + "name": "StartSizeMax", + "sentence": "the start max size", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "StartSizeMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "StartSizeMax", + "group": "3D particle emitter configuration", + "name": "SetStartSizeMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StartSizeMax", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const startMaxSize = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setStartMaxSize(startMaxSize);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the end scale of the object.", + "fullName": "End scale", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter configuration", + "name": "EndScale", + "sentence": "the end scale", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "EndScale" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "EndScale", + "group": "3D particle emitter configuration", + "name": "SetEndScale", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "EndScale", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const endScale = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setEndScale(endScale);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the min start speed of the object.", + "fullName": "Min start speed", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter speed configuration", + "name": "StartSpeedMin", + "sentence": "the min start speed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "StartSpeedMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "StartSpeedMin", + "group": "3D particle emitter speed configuration", + "name": "SetStartSpeedMin", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StartSpeedMin", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const startSpeedMin = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setStartSpeedMin(startSpeedMin);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the max start speed of the object.", + "fullName": "Max start speed", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter speed configuration", + "name": "StartSpeedMax", + "sentence": "the max start speed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "StartSpeedMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "StartSpeedMax", + "group": "3D particle emitter speed configuration", + "name": "SetStartSpeedMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StartSpeedMax", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const startSpeedMax = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setStartSpeedMax(startSpeedMax);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the min lifespan of the object.", + "fullName": "Min lifespan", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter configuration", + "name": "LifespanMin", + "sentence": "the min lifespan", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "LifespanMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "LifespanMin", + "group": "3D particle emitter configuration", + "name": "SetLifespanMin", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "LifespanMin", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const lifespanMin = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setLifespanMin(lifespanMin);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the max lifespan of the object.", + "fullName": "Max lifespan", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter configuration", + "name": "LifespanMax", + "sentence": "the max lifespan", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "LifespanMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "LifespanMax", + "group": "3D particle emitter configuration", + "name": "SetLifespanMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "LifespanMax", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const lifespanMax = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setLifespanMax(lifespanMax);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the emission duration of the object.", + "fullName": "Emission duration", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter configuration", + "name": "Duration", + "sentence": "the emission duration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Duration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Duration", + "group": "3D particle emitter configuration", + "name": "SetDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Duration", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const duration = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setDuration(duration);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if particles move with the emitter.", + "fullName": "Particles move with the emitter", + "functionType": "Condition", + "group": "3D particle emitter speed configuration", + "name": "AreParticlesRelative", + "sentence": "_PARAM0_ particles move with the emitter", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "AreParticlesRelative", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Change if particles move with the emitter.", + "fullName": "Particles move with the emitter", + "functionType": "Action", + "group": "3D particle emitter speed configuration", + "name": "SetAreParticlesRelative", + "sentence": "_PARAM0_ particles move with the emitter: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "AreParticlesRelative", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "AreParticlesRelative", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const areParticlesRelative = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setParticlesRelative(areParticlesRelative);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + }, + { + "defaultValue": "yes", + "description": "AreParticlesRelative", + "name": "Value", + "optional": true, + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "the spray cone angle of the object.", + "fullName": "Spray cone angle", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter configuration", + "name": "SpayConeAngle", + "sentence": "the spray cone angle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "SpayConeAngle" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "SpayConeAngle", + "group": "3D particle emitter configuration", + "name": "SetSpayConeAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "SpayConeAngle", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const sprayConeAngle = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setSprayConeAngle(sprayConeAngle);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the emission zone radius of the object: particles are emitted from a disc of this radius (0 emits from a single point).", + "fullName": "Emission zone radius", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter configuration", + "name": "ZoneRadius", + "sentence": "the emission zone radius", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ZoneRadius" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ZoneRadius", + "group": "3D particle emitter configuration", + "name": "SetZoneRadius", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ZoneRadius", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const zoneRadius = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setZoneRadius(zoneRadius);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the blending of the object.", + "fullName": "Blending", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter color configuration", + "name": "Blending", + "sentence": "the blending", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "Blending" + ] + } + ] + } + ], + "expressionType": { + "supplementaryInformation": "[\"Normal\",\"Additive\",\"Substractive\",\"Multiply\",\"None\"]", + "type": "stringWithSelector" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Blending", + "group": "3D particle emitter color configuration", + "name": "SetBlending", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "Blending", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const blending = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setBlending(blending);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the gravity top of the object.", + "fullName": "Gravity top", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter speed configuration", + "name": "GravityTop", + "sentence": "the gravity top", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "GravityTop" + ] + } + ] + } + ], + "expressionType": { + "supplementaryInformation": "[\"Y-\",\"Z+\"]", + "type": "stringWithSelector" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "GravityTop", + "group": "3D particle emitter speed configuration", + "name": "SetGravityTop", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "GravityTop", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const gravityTop = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setGravityTop(gravityTop);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the gravity of the object.", + "fullName": "Gravity", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter speed configuration", + "name": "Gravity", + "sentence": "the gravity", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Gravity" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Gravity", + "group": "3D particle emitter speed configuration", + "name": "SetGravity", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Gravity", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const gravity = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setGravity(gravity);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if delete when emission ends.", + "fullName": "Delete when emission ends", + "functionType": "Condition", + "group": "3D particle emitter configuration", + "name": "ShouldAutodestruct", + "sentence": "_PARAM0_ delete when emission ends", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldAutodestruct", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Change if delete when emission ends.", + "fullName": "Delete when emission ends", + "functionType": "Action", + "group": "3D particle emitter configuration", + "name": "SetShouldAutodestruct", + "sentence": "_PARAM0_ delete when emission ends: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "ShouldAutodestruct", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "ShouldAutodestruct", + "True", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + }, + { + "defaultValue": "yes", + "description": "ShouldAutodestruct", + "name": "Value", + "optional": true, + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "the start min trail length of the object.", + "fullName": "Start min trail length", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter trail configuration", + "name": "TrailStartLengthMin", + "sentence": "the start min trail length", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "TrailStartLengthMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "TrailStartLengthMin", + "group": "3D particle emitter trail configuration", + "name": "SetTrailStartLengthMin", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TrailStartLengthMin", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setTrailStartLengthMin(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the start max trail length of the object.", + "fullName": "Start max trail length", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter trail configuration", + "name": "TrailStartLengthMax", + "sentence": "the start max trail length", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "TrailStartLengthMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "TrailStartLengthMax", + "group": "3D particle emitter trail configuration", + "name": "SetTrailStartLengthMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TrailStartLengthMax", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setTrailStartLengthMax(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the trail should follow the object.", + "fullName": "Trail following the object", + "functionType": "Condition", + "group": "3D particle emitter trail configuration", + "name": "IsTrailFollowingLocalOrigin", + "sentence": "The trail is following _PARAM0_ ", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsTrailFollowingLocalOrigin", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Change if the trail should follow the object or not.", + "fullName": "Make the trail follow", + "functionType": "Action", + "group": "3D particle emitter trail configuration", + "name": "SetIsTrailFollowingLocalOrigin", + "sentence": "Make the trail follow _PARAM0_: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "False", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsTrailFollowingLocalOrigin", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsTrailFollowingLocalOrigin", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setTrailFollowingLocalOrigin(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + }, + { + "defaultValue": "yes", + "description": "IsTrailFollowingLocalOrigin", + "name": "Value", + "optional": true, + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "the tail end width ratio of the object.", + "fullName": "Tail end width ratio", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter trail configuration", + "name": "TrailEndWidthRatio", + "sentence": "the tail end width ratio", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "TrailEndWidthRatio" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "TrailEndWidthRatio", + "group": "3D particle emitter trail configuration", + "name": "SetTrailEndWidthRatio", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TrailEndWidthRatio", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setTrailEndWidthRatio(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the render mode of the object.", + "fullName": "Render mode", + "functionType": "ExpressionAndCondition", + "group": "3D particle emitter configuration", + "name": "RenderMode", + "private": true, + "sentence": "the render mode", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "RenderMode" + ] + } + ] + } + ], + "expressionType": { + "supplementaryInformation": "[\"Billboard\",\"Trail\"]", + "type": "stringWithSelector" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "RenderMode", + "group": "3D particle emitter configuration", + "name": "SetRenderMode", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "RenderMode", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__particleEmitterAdapter.setRenderMode(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Update the trail properties because the RenderMode setter reset them" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "RenderMode", + "=", + "\"Trail\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetTrailStartLengthMin" + }, + "parameters": [ + "Object", + "=", + "TrailStartLengthMin", + "" + ] + }, + { + "type": { + "value": "ParticleEmitter3D::ParticleEmitter3D::SetTrailStartLengthMax" + }, + "parameters": [ + "Object", + "=", + "TrailStartLengthMax", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "ParticleEmitter3D::ParticleEmitter3D", + "type": "object" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "onDestroy" + }, + { + "functionName": "onHotReloading" + }, + { + "functionName": "UpdateFromProperties" + }, + { + "functionName": "UpdateHelper" + }, + { + "functionName": "UpdateImage" + }, + { + "functionName": "doStepPostEvents" + }, + { + "functionName": "RegisterInLayer" + }, + { + "functionName": "Delete" + }, + { + "functionName": "HasEnded" + }, + { + "functionName": "Restart" + }, + { + "folderName": "Position", + "children": [ + { + "functionName": "Z" + }, + { + "functionName": "SetZ" + } + ] + }, + { + "folderName": "Angle", + "children": [ + { + "functionName": "RotationX" + }, + { + "functionName": "SetRotationX" + }, + { + "functionName": "RotationY" + }, + { + "functionName": "SetRotationY" + } + ] + }, + { + "folderName": "3D particle emitter color configuration", + "children": [ + { + "functionName": "StartColor" + }, + { + "functionName": "SetStartColor" + }, + { + "functionName": "EndColor" + }, + { + "functionName": "SetEndColor" + }, + { + "functionName": "StartOpacity" + }, + { + "functionName": "SetStartOpacity" + }, + { + "functionName": "EndOpacity" + }, + { + "functionName": "SetEndOpacity" + }, + { + "functionName": "Blending" + }, + { + "functionName": "SetBlending" + } + ] + }, + { + "folderName": "3D particle emitter configuration", + "children": [ + { + "functionName": "Flow" + }, + { + "functionName": "SetFlow" + }, + { + "functionName": "StartSizeMin" + }, + { + "functionName": "SetStartSizeMin" + }, + { + "functionName": "StartSizeMax" + }, + { + "functionName": "SetStartSizeMax" + }, + { + "functionName": "EndScale" + }, + { + "functionName": "SetEndScale" + }, + { + "functionName": "LifespanMin" + }, + { + "functionName": "SetLifespanMin" + }, + { + "functionName": "LifespanMax" + }, + { + "functionName": "SetLifespanMax" + }, + { + "functionName": "Duration" + }, + { + "functionName": "SetDuration" + }, + { + "functionName": "SpayConeAngle" + }, + { + "functionName": "SetSpayConeAngle" + }, + { + "functionName": "ZoneRadius" + }, + { + "functionName": "SetZoneRadius" + }, + { + "functionName": "ShouldAutodestruct" + }, + { + "functionName": "SetShouldAutodestruct" + }, + { + "functionName": "RenderMode" + }, + { + "functionName": "SetRenderMode" + } + ] + }, + { + "folderName": "3D particle emitter speed configuration", + "children": [ + { + "functionName": "StartSpeedMin" + }, + { + "functionName": "SetStartSpeedMin" + }, + { + "functionName": "StartSpeedMax" + }, + { + "functionName": "SetStartSpeedMax" + }, + { + "functionName": "AreParticlesRelative" + }, + { + "functionName": "SetAreParticlesRelative" + }, + { + "functionName": "GravityTop" + }, + { + "functionName": "SetGravityTop" + }, + { + "functionName": "Gravity" + }, + { + "functionName": "SetGravity" + } + ] + }, + { + "folderName": "3D particle emitter trail configuration", + "children": [ + { + "functionName": "TrailStartLengthMin" + }, + { + "functionName": "SetTrailStartLengthMin" + }, + { + "functionName": "TrailStartLengthMax" + }, + { + "functionName": "SetTrailStartLengthMax" + }, + { + "functionName": "IsTrailFollowingLocalOrigin" + }, + { + "functionName": "SetIsTrailFollowingLocalOrigin" + }, + { + "functionName": "TrailEndWidthRatio" + }, + { + "functionName": "SetTrailEndWidthRatio" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "255;0;0", + "type": "Color", + "label": "Start color", + "group": "Color", + "name": "StartColor" + }, + { + "value": "255;255;0", + "type": "Color", + "label": "End color", + "group": "Color", + "name": "EndColor" + }, + { + "value": "255", + "type": "Number", + "unit": "Dimensionless", + "label": "Start opacity", + "group": "Color", + "name": "StartOpacity" + }, + { + "value": "0", + "type": "Number", + "unit": "Dimensionless", + "label": "End opacity", + "group": "Color", + "name": "EndOpacity" + }, + { + "value": "50", + "type": "Number", + "label": "Flow of particles (particles per second)", + "name": "Flow" + }, + { + "value": "10", + "type": "Number", + "unit": "Pixel", + "label": "Start min size", + "group": "Size", + "name": "StartSizeMin" + }, + { + "value": "20", + "type": "Number", + "unit": "Pixel", + "label": "Start max size", + "group": "Size", + "name": "StartSizeMax" + }, + { + "value": "0", + "type": "Number", + "unit": "Dimensionless", + "label": "End scale", + "group": "Size", + "name": "EndScale" + }, + { + "value": "100", + "type": "Number", + "unit": "PixelSpeed", + "label": "Start min speed", + "group": "Speed", + "name": "StartSpeedMin", + "description": "Initial speed of the particles, in the emission direction (the local Z axis of the emitter)." + }, + { + "value": "100", + "type": "Number", + "unit": "PixelSpeed", + "label": "Start max speed", + "group": "Speed", + "name": "StartSpeedMax" + }, + { + "value": "1", + "type": "Number", + "unit": "Second", + "label": "Min lifespan", + "name": "LifespanMin" + }, + { + "value": "2", + "type": "Number", + "unit": "Second", + "label": "Max lifespan", + "name": "LifespanMax" + }, + { + "value": "5", + "type": "Number", + "unit": "Second", + "label": "Emission duration", + "name": "Duration", + "description": "0 emits forever." + }, + { + "value": "", + "type": "Boolean", + "label": "Particles move with the emitter", + "group": "Speed", + "name": "AreParticlesRelative", + "description": "When checked, the particles already emitted follow the emitter when it moves or rotates. Leave it unchecked for rain, snow or smoke that must stay where it was emitted while the emitter follows the camera or a character." + }, + { + "value": "30", + "type": "Number", + "unit": "DegreeAngle", + "label": "Spray cone angle", + "group": "Position", + "name": "SpayConeAngle", + "description": "Spread of the particles around the emission direction, which is the local Z axis of the emitter (upward by default, rotate the instance to aim it). 0 or more than 180 emits in every direction, unless an emission zone radius is set: then 0 emits straight." + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Emission zone radius", + "description": "Particles are emitted from a disc of this radius, perpendicular to the emission direction. 0 emits from a single point. Use a large radius (like 1000) for rain or snow falling over a whole area.", + "group": "Position", + "name": "ZoneRadius" + }, + { + "value": "Additive", + "type": "Choice", + "label": "Blending", + "group": "Color", + "choices": [ + { + "label": "Normal", + "value": "Normal" + }, + { + "label": "Additive", + "value": "Additive" + }, + { + "label": "Subtractive", + "value": "Subtractive" + }, + { + "label": "Multiply", + "value": "Multiply" + }, + { + "label": "None", + "value": "None" + } + ], + "name": "Blending", + "description": "Additive makes particles glow (fire, magic, energy). Use Normal for rain, snow, smoke or dust. Only applied when the object is created." + }, + { + "value": "Y-", + "type": "Choice", + "label": "Gravity top", + "group": "Speed", + "choices": [ + { + "label": "Y-", + "value": "Y-" + }, + { + "label": "Z+", + "value": "Z+" + } + ], + "name": "GravityTop", + "description": "Z+: gravity pulls particles down along the Z axis (the usual choice in 3D games). Y-: gravity pulls particles toward Y+." + }, + { + "value": "0", + "type": "Number", + "unit": "PixelAcceleration", + "label": "Gravity", + "group": "Speed", + "name": "Gravity", + "description": "Acceleration pulling the particles in the direction given by the gravity top. 0 disables gravity." + }, + { + "value": "true", + "type": "Boolean", + "label": "Delete when emission ends", + "name": "ShouldAutodestruct", + "description": "Delete the object once the emission duration is over and every particle disappeared. Uncheck it and set the emission duration to 0 for a continuous effect like fire, rain or snow." + }, + { + "value": "Center-center", + "type": "String", + "label": "", + "description": "Only used by the scene editor.", + "hidden": true, + "name": "ParentOrigin" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Z (elevation)", + "description": "Deprecated", + "group": "Position", + "hidden": true, + "name": "Z" + }, + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "Rotation on X axis", + "group": "Position", + "hidden": true, + "name": "RotationX" + }, + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "Rotation on Y axis", + "group": "Position", + "hidden": true, + "name": "RotationY" + }, + { + "value": "120", + "type": "Number", + "unit": "Dimensionless", + "label": "Start min length", + "group": "Trail", + "advanced": true, + "name": "TrailStartLengthMin", + "description": "Number of past positions kept to draw the trail, one per frame: the visible length is this number multiplied by the distance traveled by the particle each frame." + }, + { + "value": "120", + "type": "Number", + "unit": "Dimensionless", + "label": "Start max length", + "group": "Trail", + "advanced": true, + "name": "TrailStartLengthMax", + "description": "Number of past positions kept to draw the trail, one per frame: the visible length is this number multiplied by the distance traveled by the particle each frame." + }, + { + "value": "Billboard", + "type": "Choice", + "label": "Render mode", + "choices": [ + { + "label": "Image facing the camera", + "value": "Billboard" + }, + { + "label": "Image following a trail", + "value": "Trail" + } + ], + "advanced": true, + "name": "RenderMode", + "description": "\"Image facing the camera\" draws each particle as an image. \"Image following a trail\" stretches the image along the path of each particle (sparks, rain streaks)." + }, + { + "value": "", + "type": "Boolean", + "label": "Follow the object", + "group": "Trail", + "advanced": true, + "name": "IsTrailFollowingLocalOrigin" + }, + { + "value": "0", + "type": "Number", + "unit": "Dimensionless", + "label": "Tail end width ratio", + "group": "Trail", + "advanced": true, + "name": "TrailEndWidthRatio" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Flow" + }, + { + "propertyName": "LifespanMin" + }, + { + "propertyName": "LifespanMax" + }, + { + "propertyName": "Duration" + }, + { + "propertyName": "ShouldAutodestruct" + }, + { + "propertyName": "RenderMode" + }, + { + "folderName": "Color", + "children": [ + { + "propertyName": "StartColor" + }, + { + "propertyName": "EndColor" + }, + { + "propertyName": "StartOpacity" + }, + { + "propertyName": "EndOpacity" + }, + { + "propertyName": "Blending" + } + ] + }, + { + "folderName": "Size", + "children": [ + { + "propertyName": "StartSizeMin" + }, + { + "propertyName": "StartSizeMax" + }, + { + "propertyName": "EndScale" + } + ] + }, + { + "folderName": "Trail", + "children": [ + { + "propertyName": "TrailStartLengthMin" + }, + { + "propertyName": "TrailStartLengthMax" + }, + { + "propertyName": "TrailEndWidthRatio" + }, + { + "propertyName": "IsTrailFollowingLocalOrigin" + } + ] + }, + { + "folderName": "Speed", + "children": [ + { + "propertyName": "StartSpeedMin" + }, + { + "propertyName": "StartSpeedMax" + }, + { + "propertyName": "AreParticlesRelative" + }, + { + "propertyName": "GravityTop" + }, + { + "propertyName": "Gravity" + } + ] + }, + { + "folderName": "Position", + "children": [ + { + "propertyName": "SpayConeAngle" + }, + { + "propertyName": "ZoneRadius" + }, + { + "propertyName": "Z" + }, + { + "propertyName": "RotationX" + }, + { + "propertyName": "RotationY" + } + ] + }, + { + "propertyName": "ParentOrigin" + } + ] + } + } + ] + }, + { + "author": "westboy31, Tristan Rhodes (https://victrisgames.itch.io/)", + "category": "Camera", + "dimension": "", + "extensionNamespace": "", + "fullName": "Camera shake", + "gdevelopVersion": ">=5.5.222", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXZlY3Rvci1kaWZmZXJlbmNlLWFiIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTMsMUMxLjg5LDEgMSwxLjg5IDEsM1Y1SDNWM0g1VjFIM003LDFWM0gxMFYxSDdNMTIsMVYzSDE0VjVIMTZWM0MxNiwxLjg5IDE1LjExLDEgMTQsMUgxMk0xLDdWMTBIM1Y3SDFNMTQsN0MxNCw3IDE0LDExLjY3IDE0LDE0QzExLjY3LDE0IDcsMTQgNywxNEM3LDE0IDcsMTggNywyMEM3LDIxLjExIDcuODksMjIgOSwyMkgyMEMyMS4xMSwyMiAyMiwyMS4xMSAyMiwyMFY5QzIyLDcuODkgMjEuMTEsNyAyMCw3QzE4LDcgMTQsNyAxNCw3TTE2LDlIMjBWMjBIOVYxNkgxNEMxNS4xMSwxNiAxNiwxNS4xMSAxNiwxNFY5TTEsMTJWMTRDMSwxNS4xMSAxLjg5LDE2IDMsMTZINVYxNEgzVjEySDFaIiAvPjwvc3ZnPg==", + "name": "CameraShake", + "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/vector-difference-ab.svg", + "shortDescription": "Shake layer cameras.", + "version": "3.2.0", + "description": [ + "Shake layer cameras with translation, rotation and zoom.", + "", + "- Short shaking can be used to give impact (explosion, hit)", + "- Shaking can go indefinitely to set an ambiance (engine vibration, earthquake, pulsing)", + "- Low frequency shaking allows to simulate slow moving objects (ship rocking back and forth)", + "", + "Release notes:", + "- Version 3.0.0", + " - No adaptation of the game events is needed.", + " - It fixes an issue when used with scrolling, the amplitude will feel bigger in this case.", + " - The shaking relies on noise which could feel a bit different.", + " - This extension can no longer do impulses. For this, another extension \"Camera impulse\" can be used." + ], + "origin": { + "identifier": "CameraShake", + "name": "gdevelop-extension-store" + }, + "tags": [ + "shaking", + "camera", + "effect", + "screen", + "shake" + ], + "authorIds": [ + "gqDaZjCfevOOxBYkK6zlhtZnXCg1", + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2", + "m4hBMBTUilft4s1V4FQQPakVDGx1" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [ + { + "folded": true, + "name": "DefaultFrequency", + "type": "number", + "value": 12 + }, + { + "folded": true, + "name": "DefaultAmplitudeX", + "type": "number", + "value": 4 + }, + { + "folded": true, + "name": "DefaultAmplitudeY", + "type": "number", + "value": 4 + }, + { + "folded": true, + "name": "DefaultAmplitudeAngle", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "DefaultAmplitudeZoom", + "type": "number", + "value": 1 + }, + { + "folded": true, + "name": "Duration", + "type": "number", + "value": 0 + }, + { + "name": "Layers", + "type": "structure", + "children": [] + }, + { + "name": "Layer", + "type": "structure", + "children": [ + { + "name": "AmplitudeAngle", + "type": "number", + "value": 0 + }, + { + "name": "AmplitudeX", + "type": "number", + "value": 0 + }, + { + "name": "AmplitudeY", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "AmplitudeZoom", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "CameraDeltaAngle", + "type": "number", + "value": 0 + }, + { + "name": "CameraDeltaX", + "type": "number", + "value": 0 + }, + { + "name": "CameraDeltaY", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "CameraDeltaZoom", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "Frequency", + "type": "number", + "value": 0 + } + ] + }, + { + "folded": true, + "name": "LayerName", + "type": "string", + "value": "" + }, + { + "folded": true, + "name": "Time", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "StartEaseDuration", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "StopEaseDuration", + "type": "number", + "value": 0 + } + ], + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onSceneLoaded", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "CameraShake::SetLayerShakable" + }, + "parameters": [ + "", + "", + "\"\"", + "" + ] + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onScenePostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Step time counters." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Time", + "+", + "TimeDelta()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CameraShake::IsShaking" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "Time", + "<", + "StartEaseDuration" + ] + }, + { + "type": { + "inverted": true, + "value": "NumberVariable" + }, + "parameters": [ + "Time", + ">", + "Duration - StopEaseDuration" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "EaseFactor", + "=", + "clamp(Time / StartEaseDuration, 0, 1)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "Time", + ">", + "Duration - StopEaseDuration" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "EaseFactor", + "=", + "clamp((Duration - Time) / StopEaseDuration, 0, 1)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::ForEachChildVariable", + "iterableVariableName": "Layers", + "valueIteratorVariableName": "Layer", + "keyIteratorVariableName": "LayerName", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Layer.Shakable", + "True", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "ActualLayerName", + "=", + "LayerName" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "LayerName", + "=", + "\"__BaseLayer\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "ActualLayerName", + "=", + "\"\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Use user defined default values when there is no layer specific value set." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "CameraShake::SetFrequency" + }, + "parameters": [ + "", + "DefaultFrequency", + "\"\"", + "" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AmplitudeX", + "=", + "DefaultAmplitudeX" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AmplitudeY", + "=", + "DefaultAmplitudeY" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AmplitudeAngle", + "=", + "DefaultAmplitudeAngle" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AmplitudeZoom", + "=", + "DefaultAmplitudeZoom" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Layers[LayerName].CameraDeltaX", + "=", + "0" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Layers[LayerName].CameraDeltaY", + "=", + "0" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Layers[LayerName].CameraDeltaAngle", + "=", + "0" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Layers[LayerName].CameraDeltaZoom", + "=", + "0" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "VariableChildExists2" + }, + "parameters": [ + "Layer", + "\"Frequency\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "CameraShake::SetFrequency" + }, + "parameters": [ + "", + "Layer.Frequency", + "\"\"", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "VariableChildExists2" + }, + "parameters": [ + "Layer", + "\"AmplitudeX\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AmplitudeX", + "=", + "Layer.AmplitudeX" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "VariableChildExists2" + }, + "parameters": [ + "Layer", + "\"AmplitudeY\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AmplitudeY", + "=", + "Layer.AmplitudeY" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "VariableChildExists2" + }, + "parameters": [ + "Layer", + "\"AmplitudeAngle\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AmplitudeAngle", + "=", + "Layer.AmplitudeAngle" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "VariableChildExists2" + }, + "parameters": [ + "Layer", + "\"AmplitudeZoom\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "AmplitudeZoom", + "=", + "Layer.AmplitudeZoom" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Shake the layer camera.\nSave the camera displacement to revert it in onScenePostEvents." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "AmplitudeX", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Layers[LayerName].CameraDeltaX", + "=", + "CameraShake::Noise2d(\"\", TimeFromStart(), 1000) * AmplitudeX * EaseFactor" + ] + }, + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "+", + "Layers[LayerName].CameraDeltaX", + "ActualLayerName", + "0" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "AmplitudeY", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Layers[LayerName].CameraDeltaY", + "=", + "CameraShake::Noise2d(\"\", TimeFromStart(), 2000) * AmplitudeY * EaseFactor" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "+", + "Layers[LayerName].CameraDeltaY", + "ActualLayerName", + "0" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "AmplitudeAngle", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Layers[LayerName].CameraDeltaAngle", + "=", + "CameraShake::Noise2d(\"\", TimeFromStart(), 3000) * AmplitudeAngle * EaseFactor" + ] + }, + { + "type": { + "value": "SetCameraAngle" + }, + "parameters": [ + "", + "+", + "Layers[LayerName].CameraDeltaAngle", + "ActualLayerName", + "0" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "AmplitudeZoom", + "!=", + "1" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Layers[LayerName].CameraDeltaZoom", + "=", + "pow(AmplitudeZoom, CameraShake::Noise2d(\"\", TimeFromStart(), 4000) * EaseFactor)" + ] + }, + { + "type": { + "value": "ZoomCamera" + }, + "parameters": [ + "", + "CameraZoom(ActualLayerName, 0) * Layers[LayerName].CameraDeltaZoom", + "ActualLayerName", + "0" + ] + } + ] + } + ] + } + ] + } + ], + "variables": [ + { + "folded": true, + "name": "AmplitudeX", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "AmplitudeY", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "AmplitudeAngle", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "AmplitudeZoom", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "EaseFactor", + "type": "number", + "value": 1 + }, + { + "folded": true, + "name": "ActualLayerName", + "type": "string", + "value": "" + } + ] + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onScenePreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Revert the shaking." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CameraShake::IsShaking" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::ForEachChildVariable", + "iterableVariableName": "Layers", + "valueIteratorVariableName": "Layer", + "keyIteratorVariableName": "LayerName", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Layer.Shakable", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraDeltaX", + "=", + "Layer.CameraDeltaX" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraDeltaY", + "=", + "Layer.CameraDeltaY" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraDeltaAngle", + "=", + "Layer.CameraDeltaAngle" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraDeltaZoom", + "=", + "Layer.CameraDeltaZoom" + ] + }, + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "ActualLayerName", + "=", + "LayerName" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "LayerName", + "=", + "\"__BaseLayer\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "ActualLayerName", + "=", + "\"\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CameraDeltaX", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "-", + "CameraDeltaX", + "ActualLayerName", + "0" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CameraDeltaY", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "-", + "CameraDeltaY", + "ActualLayerName", + "0" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CameraDeltaAngle", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetCameraAngle" + }, + "parameters": [ + "", + "-", + "CameraDeltaAngle", + "ActualLayerName", + "0" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CameraDeltaZoom", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "ZoomCamera" + }, + "parameters": [ + "", + "CameraZoom(ActualLayerName) / CameraDeltaZoom", + "ActualLayerName", + "0" + ] + } + ] + } + ] + } + ], + "variables": [ + { + "folded": true, + "name": "ActualLayerName", + "type": "string", + "value": "" + }, + { + "folded": true, + "name": "CameraDeltaX", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "CameraDeltaY", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "CameraDeltaAngle", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "CameraDeltaZoom", + "type": "number", + "value": 0 + } + ] + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Shake the camera on layers chosen with configuration actions.", + "fullName": "Shake camera", + "functionType": "Action", + "name": "ShakeCamera", + "sentence": "Shake camera for _PARAM1_ seconds with _PARAM2_ seconds of easing to start and _PARAM3_ seconds to stop", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Time", + "=", + "0" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Duration", + "=", + "NewDuration" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StartEaseDuration", + "=", + "NewStartEaseDuration" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StopEaseDuration", + "=", + "NewStopEaseDuration" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "Duration", + "<", + "StartEaseDuration + StopEaseDuration" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StartEaseDuration", + "=", + "StartEaseDuration * Duration / (StartEaseDuration + StopEaseDuration)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StopEaseDuration", + "=", + "StopEaseDuration * Duration / (StartEaseDuration + StopEaseDuration)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Duration (in seconds)", + "name": "NewDuration", + "type": "expression" + }, + { + "description": "Ease duration to start (in seconds)", + "name": "NewStartEaseDuration", + "type": "expression" + }, + { + "description": "Ease duration to stop (in seconds)", + "name": "NewStopEaseDuration", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Shake the camera on the specified layer, using one or more ways to shake (position, angle, zoom). This action is deprecated. Please use the other one with the same name.", + "fullName": "Shake camera (deprecated)", + "functionType": "Action", + "name": "CameraShake", + "private": true, + "sentence": "Shake camera on _PARAM3_ layer for _PARAM5_ seconds. Use an amplitude of _PARAM1_px on X axis and _PARAM2_px on Y axis, angle rotation amplitude _PARAM6_ degrees, and zoom amplitude _PARAM7_ percent. Wait _PARAM8_ seconds between shakes. Keep shaking until stopped: _PARAM9_", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Camera Shake", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ClearVariableChildren" + }, + "parameters": [ + "Layers" + ] + }, + { + "type": { + "value": "CameraShake::SetLayerShakable" + }, + "parameters": [ + "", + "", + "NewLayer", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Time", + "=", + "0" + ] + }, + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "Layer", + "=", + "NewLayer" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Duration", + "=", + "NewDuration" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StartEaseDuration", + "=", + "0" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StopEaseDuration", + "=", + "NewDuration" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DefaultAmplitudeX", + "=", + "abs(AmplitudeX)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DefaultAmplitudeY", + "=", + "abs(AmplitudeY)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DefaultAmplitudeAngle", + "=", + "AmplitudeAngle" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DefaultAmplitudeZoom", + "=", + "1 + AmplitudeZoom / 100" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "ShakePeriod", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DefaultFrequency", + "=", + "1 / ShakePeriod" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "ShakePeriod", + "=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DefaultFrequency", + "=", + "1 / 0.08" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Determine if the shake should keep going until stopped" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShakeForever", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Duration", + "=", + "1234567890" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Add default values if none were provided" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "NewDuration", + "=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Duration", + "=", + "0.5" + ] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "description": "Amplitude of shaking on the X axis (in pixels)", + "name": "AmplitudeX", + "type": "expression" + }, + { + "description": "Amplitude of shaking on the Y axis (in pixels)", + "name": "AmplitudeY", + "type": "expression" + }, + { + "description": "Layer (base layer if empty)", + "name": "NewLayer", + "type": "layer" + }, + { + "description": "Camera index (Default: 0)", + "name": "Camera", + "type": "expression" + }, + { + "description": "Duration (in seconds) (Default: 0.5)", + "name": "NewDuration", + "type": "expression" + }, + { + "description": "Angle rotation amplitude (in degrees) (For example: 2)", + "name": "AmplitudeAngle", + "type": "expression" + }, + { + "description": "Zoom factor amplitude", + "name": "AmplitudeZoom", + "type": "expression" + }, + { + "description": "Period between shakes (in seconds) (Default: 0.08)", + "name": "ShakePeriod", + "type": "expression" + }, + { + "description": "Keep shaking until stopped", + "longDescription": "Duration value will be ignored", + "name": "ShakeForever", + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Start shaking the camera indefinitely.", + "fullName": "Start camera shaking", + "functionType": "Action", + "name": "StartShaking", + "sentence": "Start shaking the camera with _PARAM1_ seconds of easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Time", + "=", + "0" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Duration", + "=", + "1234567890" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StartEaseDuration", + "=", + "EaseDuration" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Ease duration (in seconds)", + "name": "EaseDuration", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Stop shaking the camera.", + "fullName": "Stop camera shaking", + "functionType": "Action", + "name": "StopShaking", + "sentence": "Stop shaking the camera with _PARAM1_ seconds of easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Time", + "=", + "0" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Duration", + "=", + "EaseDuration" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StopEaseDuration", + "=", + "EaseDuration" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Ease duration (in seconds)", + "name": "EaseDuration", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Mark a layer as shakable.", + "fullName": "Shakable layer", + "functionType": "Action", + "group": "Camera shake configuration", + "name": "SetLayerShakable", + "sentence": "Mark the layer: _PARAM2_ as shakable: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "LayerName", + "=", + "NewLayerName" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "NewLayerName", + "=", + "\"\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "LayerName", + "=", + "\"__BaseLayer\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "Shakable", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "Layers[LayerName].Shakable", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Shakable", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "Layers[LayerName].Shakable", + "True", + "" + ] + } + ] + } + ], + "parameters": [ + { + "defaultValue": "yes", + "description": "Shakable", + "name": "Shakable", + "optional": true, + "type": "yesorno" + }, + { + "description": "Layer", + "name": "NewLayerName", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the camera is shaking.", + "fullName": "Camera is shaking", + "functionType": "Condition", + "name": "IsShaking", + "sentence": "Camera is shaking", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "Time", + "<", + "Duration" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Change the translation amplitude of the shaking (in pixels).", + "fullName": "Layer translation amplitude", + "functionType": "Action", + "group": "Camera shake configuration", + "name": "SetLayerTranslationAmplitude", + "sentence": "Change the translation amplitude of the shaking to _PARAM1_; _PARAM2_ (layer: _PARAM3_)", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "LayerName", + "=", + "NewLayerName" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "NewLayerName", + "=", + "\"\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "LayerName", + "=", + "\"__BaseLayer\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Layers[LayerName].AmplitudeX", + "=", + "AmplitudeX" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Layers[LayerName].AmplitudeY", + "=", + "AmplitudeY" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Amplitude of shaking on the X axis (in pixels)", + "name": "AmplitudeX", + "type": "expression" + }, + { + "description": "Amplitude of shaking on the Y axis (in pixels)", + "name": "AmplitudeY", + "type": "expression" + }, + { + "description": "Layer", + "name": "NewLayerName", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Change the rotation amplitude of the shaking (in degrees).", + "fullName": "Layer rotation amplitude", + "functionType": "Action", + "group": "Camera shake configuration", + "name": "SetLayerRotationAmplitude", + "sentence": "Change the rotation amplitude of the shaking to _PARAM1_ degrees (layer: _PARAM2_)", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "LayerName", + "=", + "NewLayerName" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "NewLayerName", + "=", + "\"\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "LayerName", + "=", + "\"__BaseLayer\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Layers[LayerName].AmplitudeAngle", + "=", + "AmplitudeAngle" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Angle (in degree)", + "name": "AmplitudeAngle", + "type": "expression" + }, + { + "description": "NewLayerName", + "name": "NewLayerName", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Change the zoom factor amplitude of the shaking. The shaking will zoom and unzoom by this factor (for instance 1.0625 is a valid value).", + "fullName": "Layer zoom amplitude", + "functionType": "Action", + "group": "Camera shake configuration", + "name": "SetLayerZoomAmplitude", + "sentence": "Change the zoom factor amplitude of the shaking to _PARAM1_ (layer: _PARAM2_)", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "LayerName", + "=", + "NewLayerName" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "NewLayerName", + "=", + "\"\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "LayerName", + "=", + "\"__BaseLayer\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Layers[LayerName].AmplitudeZoom", + "=", + "AmplitudeZoom" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Zoom factor", + "name": "AmplitudeZoom", + "type": "expression" + }, + { + "description": "NewLayerName", + "name": "NewLayerName", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Change the number of back and forth per seconds.", + "fullName": "Layer shaking frequency", + "functionType": "Action", + "group": "Camera shake configuration", + "name": "SetLayerShakingFrequency", + "sentence": "Change the shaking frequency to _PARAM1_ (layer: _PARAM2_)", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "LayerName", + "=", + "NewLayerName" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "NewLayerName", + "=", + "\"\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "LayerName", + "=", + "\"__BaseLayer\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Layers[LayerName].Frequency", + "=", + "Frequency" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Frequency", + "name": "Frequency", + "type": "expression" + }, + { + "description": "NewLayerName", + "name": "NewLayerName", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Change the default translation amplitude of the shaking (in pixels).", + "fullName": "Default translation amplitude", + "functionType": "Action", + "group": "Camera shake configuration", + "name": "SetDefaultTranslationAmplitude", + "sentence": "Change the default translation amplitude of the shaking to _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DefaultAmplitudeX", + "=", + "AmplitudeX" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DefaultAmplitudeY", + "=", + "AmplitudeY" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Amplitude of shaking on the X axis (in pixels)", + "name": "AmplitudeX", + "type": "expression" + }, + { + "description": "Amplitude of shaking on the Y axis (in pixels)", + "name": "AmplitudeY", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Change the default rotation amplitude of the shaking (in degrees).", + "fullName": "Default rotation amplitude", + "functionType": "Action", + "group": "Camera shake configuration", + "name": "SetDefaultRotationAmplitude", + "sentence": "Change the default rotation amplitude of the shaking to _PARAM1_ degrees", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DefaultAmplitudeAngle", + "=", + "AmplitudeAngle" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Angle (in degree)", + "name": "AmplitudeAngle", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Change the default zoom factor amplitude of the shaking. The shaking will zoom and unzoom by this factor (for instance 1.0625 is a valid value).", + "fullName": "Default zoom amplitude", + "functionType": "Action", + "group": "Camera shake configuration", + "name": "SetDefaultZoomAmplitude", + "sentence": "Change the default zoom factor amplitude of the shaking to _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DefaultAmplitudeZoom", + "=", + "AmplitudeZoom" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Zoom factor", + "name": "AmplitudeZoom", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Change the default number of back and forth per seconds.", + "fullName": "Default shaking frequency", + "functionType": "Action", + "group": "Camera shake configuration", + "name": "SetDefaultShakingFrequency", + "sentence": "Change the default shaking frequency to _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DefaultFrequency", + "=", + "Frequency" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Frequency", + "name": "Frequency", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onFirstSceneLoaded", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (gdjs._cameraShakeExtension) {", + " return;", + "}", + "", + "/** Noise generator manager. */", + "class NoiseManager {", + " /**", + " * Create the manager of noise generators.", + " */", + " constructor() {", + " this.seed = gdjs.randomInRange(1, Number.MAX_SAFE_INTEGER);", + " /** @type {Map} */", + " this.generators = new Map();", + " }", + "", + " /**", + " * @param name {string}", + " * @return {NoiseGenerator}", + " */", + " getGenerator(name) {", + " let generator = this.generators.get(name);", + " if (!generator) {", + " generator = new NoiseGenerator(name + this.seed);", + " this.generators.set(name, generator);", + " }", + " return generator;", + " }", + "", + " /**", + " * @param seed {number}", + " */", + " setSeed(seed) {", + " this.seed = seed;", + " this.generators.forEach(generator => generator.setSeed(name + this.seed));", + " }", + "", + " /**", + " * @param name {string}", + " */", + " deleteGenerator(name) {", + " this.generators.delete(name);", + " }", + "", + " /**", + " */", + " deleteAllGenerators() {", + " this.generators.clear();", + " }", + "}", + "", + "/** Noise generator with octaves. */", + "class NoiseGenerator {", + " /**", + " * Create a noise generator with a seed.", + " * @param seed {string}", + " */", + " constructor(seed) {", + " this.simplexNoise = new SimplexNoise(seed);", + " this.frequency = 1;", + " this.octaves = 1;", + " this.persistence = 0.5;", + " this.lacunarity = 2;", + " this.xLoopPeriod = 0;", + " this.yLoopPeriod = 0;", + " }", + "", + " /**", + " * @param seed {string}", + " */", + " setSeed(seed) {", + " this.simplexNoise = new SimplexNoise(seed);", + " }", + "", + " /**", + " * @param x {float}", + " * @param y {float}", + " * @param z {float} optionnal", + " * @param w {float} optionnal", + " * @return {float}", + " */", + " noise(x, y, z, w) {", + " if (this.xLoopPeriod && this.yLoopPeriod) {", + " const circleRatioX = 2 * Math.PI / this.xLoopPeriod;", + " const circleRatioY = 2 * Math.PI / this.yLoopPeriod;", + " const angleX = circleRatioX * x;", + " const angleY = circleRatioY * y;", + " x = Math.cos(angleX) / circleRatioX;", + " y = Math.sin(angleX) / circleRatioX;", + " z = Math.cos(angleY) / circleRatioY;", + " w = Math.sin(angleY) / circleRatioY;", + " }", + " else if (this.xLoopPeriod) {", + " const circleRatio = 2 * Math.PI / this.xLoopPeriod;", + " const angleX = circleRatio * x;", + " w = z;", + " z = y;", + " x = Math.cos(angleX) / circleRatio;", + " y = Math.sin(angleX) / circleRatio;", + " }", + " else if (this.yLoopPeriod) {", + " const circleRatio = 2 * Math.PI / this.xLoopPeriod;", + " const angleX = circleRatio * x;", + " w = z;", + " // Make the circle perimeter equals to the looping period", + " // to keep the same perceived frequency with or without looping.", + " y = Math.cos(angleX) / circleRatio;", + " z = Math.sin(angleX) / circleRatio;", + " }", + " let noiseFunction = this.simplexNoise.noise4D.bind(this.simplexNoise);", + " if (z === undefined) {", + " noiseFunction = this.simplexNoise.noise2D.bind(this.simplexNoise);", + " }", + " else if (w === undefined) {", + " noiseFunction = this.simplexNoise.noise3D.bind(this.simplexNoise);", + " }", + " let frequency = this.frequency;", + " let noiseSum = 0;", + " let amplitudeSum = 0;", + " let amplitude = 1;", + " for (let i = 0; i < this.octaves; i++) {", + " noiseSum += noiseFunction(x * frequency, y * frequency, z * frequency, w * frequency) * amplitude;", + " amplitudeSum += Math.abs(amplitude);", + " amplitude *= this.persistence;", + " frequency *= this.lacunarity;", + " }", + " return noiseSum / amplitudeSum;", + " }", + "}", + "", + "/*", + "A fast javascript implementation of simplex noise by Jonas Wagner", + "https://github.com/jwagner/simplex-noise.js", + "", + "Based on a speed-improved simplex noise algorithm for 2D, 3D and 4D in Java.", + "Which is based on example code by Stefan Gustavson (stegu@itn.liu.se).", + "With Optimisations by Peter Eastman (peastman@drizzle.stanford.edu).", + "Better rank ordering method by Stefan Gustavson in 2012.", + "", + " Copyright (c) 2021 Jonas Wagner", + "", + " Permission is hereby granted, free of charge, to any person obtaining a copy", + " of this software and associated documentation files (the \"Software\"), to deal", + " in the Software without restriction, including without limitation the rights", + " to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + " copies of the Software, and to permit persons to whom the Software is", + " furnished to do so, subject to the following conditions:", + "", + " The above copyright notice and this permission notice shall be included in all", + " copies or substantial portions of the Software.", + "", + " THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + " IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + " FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + " AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + " LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + " OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", + " SOFTWARE.", + " */", + "", + "const F2 = 0.5 * (Math.sqrt(3.0) - 1.0);", + "const G2 = (3.0 - Math.sqrt(3.0)) / 6.0;", + "const F3 = 1.0 / 3.0;", + "const G3 = 1.0 / 6.0;", + "const F4 = (Math.sqrt(5.0) - 1.0) / 4.0;", + "const G4 = (5.0 - Math.sqrt(5.0)) / 20.0;", + "const grad3 = new Float32Array([1, 1, 0,", + " -1, 1, 0,", + " 1, -1, 0,", + " -1, -1, 0,", + " 1, 0, 1,", + " -1, 0, 1,", + " 1, 0, -1,", + " -1, 0, -1,", + " 0, 1, 1,", + " 0, -1, 1,", + " 0, 1, -1,", + " 0, -1, -1]);", + "const grad4 = new Float32Array([0, 1, 1, 1, 0, 1, 1, -1, 0, 1, -1, 1, 0, 1, -1, -1,", + " 0, -1, 1, 1, 0, -1, 1, -1, 0, -1, -1, 1, 0, -1, -1, -1,", + " 1, 0, 1, 1, 1, 0, 1, -1, 1, 0, -1, 1, 1, 0, -1, -1,", + " -1, 0, 1, 1, -1, 0, 1, -1, -1, 0, -1, 1, -1, 0, -1, -1,", + " 1, 1, 0, 1, 1, 1, 0, -1, 1, -1, 0, 1, 1, -1, 0, -1,", + " -1, 1, 0, 1, -1, 1, 0, -1, -1, -1, 0, 1, -1, -1, 0, -1,", + " 1, 1, 1, 0, 1, 1, -1, 0, 1, -1, 1, 0, 1, -1, -1, 0,", + " -1, 1, 1, 0, -1, 1, -1, 0, -1, -1, 1, 0, -1, -1, -1, 0]);", + "", + "", + "/**", + " * Builds a random permutation table.", + " * This is exported only for (internal) testing purposes.", + " * Do not rely on this export.", + " * @param {() => number} random", + " * @private", + " */", + "function buildPermutationTable(random) {", + " const p = new Uint8Array(256);", + " for (let i = 0; i < 256; i++) {", + " p[i] = i;", + " }", + " for (let i = 0; i < 255; i++) {", + " const r = i + ~~(random() * (256 - i));", + " const aux = p[i];", + " p[i] = p[r];", + " p[r] = aux;", + " }", + " return p;", + "}", + "", + "/*", + "The ALEA PRNG and masher code used by simplex-noise.js", + "is based on code by Johannes Baagøe, modified by Jonas Wagner.", + "See alea.md for the full license.", + "@param {string|number} seed", + "*/", + "function alea(seed) {", + " let s0 = 0;", + " let s1 = 0;", + " let s2 = 0;", + " let c = 1;", + " const mash = masher();", + " s0 = mash(' ');", + " s1 = mash(' ');", + " s2 = mash(' ');", + " s0 -= mash(seed);", + " if (s0 < 0) {", + " s0 += 1;", + " }", + " s1 -= mash(seed);", + " if (s1 < 0) {", + " s1 += 1;", + " }", + " s2 -= mash(seed);", + " if (s2 < 0) {", + " s2 += 1;", + " }", + " return function () {", + " const t = 2091639 * s0 + c * 2.3283064365386963e-10; // 2^-32", + " s0 = s1;", + " s1 = s2;", + " return s2 = t - (c = t | 0);", + " };", + "}", + "", + "function masher() {", + " let n = 0xefc8249d;", + " return function (data) {", + " data = data.toString();", + " for (let i = 0; i < data.length; i++) {", + " n += data.charCodeAt(i);", + " let h = 0.02519603282416938 * n;", + " n = h >>> 0;", + " h -= n;", + " h *= n;", + " n = h >>> 0;", + " h -= n;", + " n += h * 0x100000000; // 2^32", + " }", + " return (n >>> 0) * 2.3283064365386963e-10; // 2^-32", + " };", + "}", + "", + "/** Deterministic simplex noise generator suitable for 2D, 3D and 4D spaces. */", + "class SimplexNoise {", + " /**", + " * Creates a new `SimplexNoise` instance.", + " * This involves some setup. You can save a few cpu cycles by reusing the same instance.", + " * @param {(() => number)|string|number} randomOrSeed A random number generator or a seed (string|number).", + " * Defaults to Math.random (random irreproducible initialization).", + " */", + " constructor(randomOrSeed) {", + " if (randomOrSeed === void 0) { randomOrSeed = Math.random; }", + " const random = typeof randomOrSeed == 'function' ? randomOrSeed : alea(randomOrSeed);", + " this.p = buildPermutationTable(random);", + " this.perm = new Uint8Array(512);", + " this.permMod12 = new Uint8Array(512);", + " for (let i = 0; i < 512; i++) {", + " this.perm[i] = this.p[i & 255];", + " this.permMod12[i] = this.perm[i] % 12;", + " }", + " }", + "", + " /**", + " * Samples the noise field in 2 dimensions", + " * @param {number} x", + " * @param {number} y", + " * @returns a number in the interval [-1, 1]", + " */", + " noise2D(x, y) {", + " const permMod12 = this.permMod12;", + " const perm = this.perm;", + " let n0 = 0; // Noise contributions from the three corners", + " let n1 = 0;", + " let n2 = 0;", + " // Skew the input space to determine which simplex cell we're in", + " const s = (x + y) * F2; // Hairy factor for 2D", + " const i = Math.floor(x + s);", + " const j = Math.floor(y + s);", + " const t = (i + j) * G2;", + " const X0 = i - t; // Unskew the cell origin back to (x,y) space", + " const Y0 = j - t;", + " const x0 = x - X0; // The x,y distances from the cell origin", + " const y0 = y - Y0;", + " // For the 2D case, the simplex shape is an equilateral triangle.", + " // Determine which simplex we are in.", + " let i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords", + " if (x0 > y0) {", + " i1 = 1;", + " j1 = 0;", + " } // lower triangle, XY order: (0,0)->(1,0)->(1,1)", + " else {", + " i1 = 0;", + " j1 = 1;", + " } // upper triangle, YX order: (0,0)->(0,1)->(1,1)", + " // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and", + " // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where", + " // c = (3-sqrt(3))/6", + " const x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords", + " const y1 = y0 - j1 + G2;", + " const x2 = x0 - 1.0 + 2.0 * G2; // Offsets for last corner in (x,y) unskewed coords", + " const y2 = y0 - 1.0 + 2.0 * G2;", + " // Work out the hashed gradient indices of the three simplex corners", + " const ii = i & 255;", + " const jj = j & 255;", + " // Calculate the contribution from the three corners", + " let t0 = 0.5 - x0 * x0 - y0 * y0;", + " if (t0 >= 0) {", + " const gi0 = permMod12[ii + perm[jj]] * 3;", + " t0 *= t0;", + " n0 = t0 * t0 * (grad3[gi0] * x0 + grad3[gi0 + 1] * y0); // (x,y) of grad3 used for 2D gradient", + " }", + " let t1 = 0.5 - x1 * x1 - y1 * y1;", + " if (t1 >= 0) {", + " const gi1 = permMod12[ii + i1 + perm[jj + j1]] * 3;", + " t1 *= t1;", + " n1 = t1 * t1 * (grad3[gi1] * x1 + grad3[gi1 + 1] * y1);", + " }", + " let t2 = 0.5 - x2 * x2 - y2 * y2;", + " if (t2 >= 0) {", + " const gi2 = permMod12[ii + 1 + perm[jj + 1]] * 3;", + " t2 *= t2;", + " n2 = t2 * t2 * (grad3[gi2] * x2 + grad3[gi2 + 1] * y2);", + " }", + " // Add contributions from each corner to get the final noise value.", + " // The result is scaled to return values in the interval [-1,1].", + " return 70.0 * (n0 + n1 + n2);", + " }", + "", + " /**", + " * Samples the noise field in 3 dimensions", + " * @param {number} x", + " * @param {number} y", + " * @param {number} z", + " * @returns a number in the interval [-1, 1]", + " */", + " noise3D(x, y, z) {", + " const permMod12 = this.permMod12;", + " const perm = this.perm;", + " let n0, n1, n2, n3; // Noise contributions from the four corners", + " // Skew the input space to determine which simplex cell we're in", + " const s = (x + y + z) * F3; // Very nice and simple skew factor for 3D", + " const i = Math.floor(x + s);", + " const j = Math.floor(y + s);", + " const k = Math.floor(z + s);", + " const t = (i + j + k) * G3;", + " const X0 = i - t; // Unskew the cell origin back to (x,y,z) space", + " const Y0 = j - t;", + " const Z0 = k - t;", + " const x0 = x - X0; // The x,y,z distances from the cell origin", + " const y0 = y - Y0;", + " const z0 = z - Z0;", + " // For the 3D case, the simplex shape is a slightly irregular tetrahedron.", + " // Determine which simplex we are in.", + " let i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords", + " let i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords", + " if (x0 >= y0) {", + " if (y0 >= z0) {", + " i1 = 1;", + " j1 = 0;", + " k1 = 0;", + " i2 = 1;", + " j2 = 1;", + " k2 = 0;", + " } // X Y Z order", + " else if (x0 >= z0) {", + " i1 = 1;", + " j1 = 0;", + " k1 = 0;", + " i2 = 1;", + " j2 = 0;", + " k2 = 1;", + " } // X Z Y order", + " else {", + " i1 = 0;", + " j1 = 0;", + " k1 = 1;", + " i2 = 1;", + " j2 = 0;", + " k2 = 1;", + " } // Z X Y order", + " }", + " else { // x0 y0)", + " rankx++;", + " else", + " ranky++;", + " if (x0 > z0)", + " rankx++;", + " else", + " rankz++;", + " if (x0 > w0)", + " rankx++;", + " else", + " rankw++;", + " if (y0 > z0)", + " ranky++;", + " else", + " rankz++;", + " if (y0 > w0)", + " ranky++;", + " else", + " rankw++;", + " if (z0 > w0)", + " rankz++;", + " else", + " rankw++;", + " // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order.", + " // Many values of c will never occur, since e.g. x>y>z>w makes x= 3 ? 1 : 0;", + " const j1 = ranky >= 3 ? 1 : 0;", + " const k1 = rankz >= 3 ? 1 : 0;", + " const l1 = rankw >= 3 ? 1 : 0;", + " // The integer offsets for the third simplex corner", + " const i2 = rankx >= 2 ? 1 : 0;", + " const j2 = ranky >= 2 ? 1 : 0;", + " const k2 = rankz >= 2 ? 1 : 0;", + " const l2 = rankw >= 2 ? 1 : 0;", + " // The integer offsets for the fourth simplex corner", + " const i3 = rankx >= 1 ? 1 : 0;", + " const j3 = ranky >= 1 ? 1 : 0;", + " const k3 = rankz >= 1 ? 1 : 0;", + " const l3 = rankw >= 1 ? 1 : 0;", + " // The fifth corner has all coordinate offsets = 1, so no need to compute that.", + " const x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords", + " const y1 = y0 - j1 + G4;", + " const z1 = z0 - k1 + G4;", + " const w1 = w0 - l1 + G4;", + " const x2 = x0 - i2 + 2.0 * G4; // Offsets for third corner in (x,y,z,w) coords", + " const y2 = y0 - j2 + 2.0 * G4;", + " const z2 = z0 - k2 + 2.0 * G4;", + " const w2 = w0 - l2 + 2.0 * G4;", + " const x3 = x0 - i3 + 3.0 * G4; // Offsets for fourth corner in (x,y,z,w) coords", + " const y3 = y0 - j3 + 3.0 * G4;", + " const z3 = z0 - k3 + 3.0 * G4;", + " const w3 = w0 - l3 + 3.0 * G4;", + " const x4 = x0 - 1.0 + 4.0 * G4; // Offsets for last corner in (x,y,z,w) coords", + " const y4 = y0 - 1.0 + 4.0 * G4;", + " const z4 = z0 - 1.0 + 4.0 * G4;", + " const w4 = w0 - 1.0 + 4.0 * G4;", + " // Work out the hashed gradient indices of the five simplex corners", + " const ii = i & 255;", + " const jj = j & 255;", + " const kk = k & 255;", + " const ll = l & 255;", + " // Calculate the contribution from the five corners", + " let t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0;", + " if (t0 < 0)", + " n0 = 0.0;", + " else {", + " const gi0 = (perm[ii + perm[jj + perm[kk + perm[ll]]]] % 32) * 4;", + " t0 *= t0;", + " n0 = t0 * t0 * (grad4[gi0] * x0 + grad4[gi0 + 1] * y0 + grad4[gi0 + 2] * z0 + grad4[gi0 + 3] * w0);", + " }", + " let t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1;", + " if (t1 < 0)", + " n1 = 0.0;", + " else {", + " const gi1 = (perm[ii + i1 + perm[jj + j1 + perm[kk + k1 + perm[ll + l1]]]] % 32) * 4;", + " t1 *= t1;", + " n1 = t1 * t1 * (grad4[gi1] * x1 + grad4[gi1 + 1] * y1 + grad4[gi1 + 2] * z1 + grad4[gi1 + 3] * w1);", + " }", + " let t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2;", + " if (t2 < 0)", + " n2 = 0.0;", + " else {", + " const gi2 = (perm[ii + i2 + perm[jj + j2 + perm[kk + k2 + perm[ll + l2]]]] % 32) * 4;", + " t2 *= t2;", + " n2 = t2 * t2 * (grad4[gi2] * x2 + grad4[gi2 + 1] * y2 + grad4[gi2 + 2] * z2 + grad4[gi2 + 3] * w2);", + " }", + " let t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3;", + " if (t3 < 0)", + " n3 = 0.0;", + " else {", + " const gi3 = (perm[ii + i3 + perm[jj + j3 + perm[kk + k3 + perm[ll + l3]]]] % 32) * 4;", + " t3 *= t3;", + " n3 = t3 * t3 * (grad4[gi3] * x3 + grad4[gi3 + 1] * y3 + grad4[gi3 + 2] * z3 + grad4[gi3 + 3] * w3);", + " }", + " let t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4;", + " if (t4 < 0)", + " n4 = 0.0;", + " else {", + " const gi4 = (perm[ii + 1 + perm[jj + 1 + perm[kk + 1 + perm[ll + 1]]]] % 32) * 4;", + " t4 *= t4;", + " n4 = t4 * t4 * (grad4[gi4] * x4 + grad4[gi4 + 1] * y4 + grad4[gi4 + 2] * z4 + grad4[gi4 + 3] * w4);", + " }", + " // Sum up and scale the result to cover the range [-1,1]", + " return 27.0 * (n0 + n1 + n2 + n3 + n4);", + " };", + "}", + "", + "gdjs._cameraShakeExtension = {", + " noiseManager: new NoiseManager(),", + "};", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Generate a number from 2 dimensional simplex noise.", + "fullName": "2D noise", + "functionType": "Expression", + "name": "Noise2d", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const name = eventsFunctionContext.getArgument(\"Name\");\r", + "const x = eventsFunctionContext.getArgument(\"X\");\r", + "const y = eventsFunctionContext.getArgument(\"Y\");\r", + "\r", + "eventsFunctionContext.returnValue = gdjs._cameraShakeExtension.noiseManager.getGenerator(name).noise(x, y);" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Generator name", + "name": "Name", + "type": "string" + }, + { + "description": "X coordinate", + "name": "X", + "type": "expression" + }, + { + "description": "Y coordinate", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Generate a number from 3 dimensional simplex noise.", + "fullName": "3D noise", + "functionType": "Expression", + "name": "Noise3d", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const name = eventsFunctionContext.getArgument(\"Name\");\r", + "const x = eventsFunctionContext.getArgument(\"X\");\r", + "const y = eventsFunctionContext.getArgument(\"Y\");\r", + "const z = eventsFunctionContext.getArgument(\"Z\");\r", + "\r", + "eventsFunctionContext.returnValue = gdjs._cameraShakeExtension.noiseManager.getGenerator(name).noise(x, y, z);" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Generator name", + "name": "Name", + "type": "string" + }, + { + "description": "X coordinate", + "name": "X", + "type": "expression" + }, + { + "description": "Y coordinate", + "name": "Y", + "type": "expression" + }, + { + "description": "Z coordinate", + "name": "Z", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Generate a number from 4 dimensional simplex noise.", + "fullName": "4D noise", + "functionType": "Expression", + "name": "Noise4d", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const name = eventsFunctionContext.getArgument(\"Name\");\r", + "const x = eventsFunctionContext.getArgument(\"X\");\r", + "const y = eventsFunctionContext.getArgument(\"Y\");\r", + "const z = eventsFunctionContext.getArgument(\"Z\");\r", + "const w = eventsFunctionContext.getArgument(\"W\");\r", + "\r", + "eventsFunctionContext.returnValue = gdjs._cameraShakeExtension.noiseManager.getGenerator(name).noise(x, y, z, w);" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Generator name", + "name": "Name", + "type": "string" + }, + { + "description": "X coordinate", + "name": "X", + "type": "expression" + }, + { + "description": "Y coordinate", + "name": "Y", + "type": "expression" + }, + { + "description": "Z coordinate", + "name": "Z", + "type": "expression" + }, + { + "description": "W coordinate", + "name": "W", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Create a noise generator with default settings (frequency = 1, octaves = 1, persistence = 0.5, lacunarity = 2).", + "fullName": "Create a noise generator", + "functionType": "Action", + "name": "Create", + "private": true, + "sentence": "Create a noise generator named _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const name = eventsFunctionContext.getArgument(\"Name\");\r", + "\r", + "gdjs._cameraShakeExtension.noiseManager.getGenerator(name);" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Generator name", + "name": "Name", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Delete a noise generator and loose its settings.", + "fullName": "Delete a noise generator", + "functionType": "Action", + "name": "Delete", + "private": true, + "sentence": "Delete _PARAM1_ noise generator", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const name = eventsFunctionContext.getArgument(\"Name\");\r", + "\r", + "gdjs._cameraShakeExtension.noiseManager.deleteGenerator(name);" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Generator name", + "name": "Name", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Delete all noise generators and loose their settings.", + "fullName": "Delete all noise generators", + "functionType": "Action", + "name": "DeleteAll", + "private": true, + "sentence": "Delete all noise generators", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "gdjs._cameraShakeExtension.noiseManager.deleteAllGenerators();", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "The seed is a number used to generate the random noise. Setting the same seed will result in the same random noise generation. It's for example useful to generate the same world, by saving this seed value and reusing it later to generate again a world.", + "fullName": "Noise seed", + "functionType": "Action", + "name": "SetSeed", + "private": true, + "sentence": "Change the noise seed to _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "gdjs._cameraShakeExtension.noiseManager.setSeed(eventsFunctionContext.getArgument(\"Seed\"));", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Seed", + "longDescription": "15 digits numbers maximum", + "name": "Seed", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Change the looping period on X used for noise generation. The noise will wrap-around on X.", + "fullName": "Noise looping period on X", + "functionType": "Action", + "name": "SetLoopPeriodX", + "private": true, + "sentence": "Change the looping period on X of _PARAM2_: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const name = eventsFunctionContext.getArgument(\"Name\");\r", + "\r", + "gdjs._cameraShakeExtension.noiseManager.getGenerator(name).xLoopPeriod = eventsFunctionContext.getArgument(\"LoopPeriod\");" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Looping period on X", + "name": "LoopPeriod", + "type": "expression" + }, + { + "description": "Generator name", + "name": "Name", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Change the looping period on Y used for noise generation. The noise will wrap-around on Y.", + "fullName": "Noise looping period on Y", + "functionType": "Action", + "name": "SetLoopPeriodY", + "private": true, + "sentence": "Change the looping period on Y of _PARAM2_: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const name = eventsFunctionContext.getArgument(\"Name\");\r", + "\r", + "gdjs._cameraShakeExtension.noiseManager.getGenerator(name).yLoopPeriod = eventsFunctionContext.getArgument(\"LoopPeriod\");" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Looping period on Y", + "name": "LoopPeriod", + "type": "expression" + }, + { + "description": "Generator name", + "name": "Name", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Change the base frequency used for noise generation. A lower frequency will zoom in the noise.", + "fullName": "Noise base frequency", + "functionType": "Action", + "name": "SetFrequency", + "private": true, + "sentence": "Change the noise frequency of _PARAM2_: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const name = eventsFunctionContext.getArgument(\"Name\");\r", + "\r", + "gdjs._cameraShakeExtension.noiseManager.getGenerator(name).frequency = eventsFunctionContext.getArgument(\"Frequency\");" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Frequency", + "name": "Frequency", + "type": "expression" + }, + { + "description": "Generator name", + "name": "Name", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Change the number of octaves used for noise generation. It can be seen as layers of noise with different zoom.", + "fullName": "Noise octaves", + "functionType": "Action", + "name": "SetOctaves", + "private": true, + "sentence": "Change the number of noise octaves of _PARAM2_: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const name = eventsFunctionContext.getArgument(\"Name\");\r", + "\r", + "gdjs._cameraShakeExtension.noiseManager.getGenerator(name).octaves = eventsFunctionContext.getArgument(\"Octaves\");" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Octaves", + "name": "Octaves", + "type": "expression" + }, + { + "description": "Generator name", + "name": "Name", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Change the persistence used for noise generation. At its default value \"0.5\", it halves the noise amplitude at each octave.", + "fullName": "Noise persistence", + "functionType": "Action", + "name": "SetPersistence", + "private": true, + "sentence": "Change the noise persistence of _PARAM2_: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const name = eventsFunctionContext.getArgument(\"Name\");\r", + "\r", + "gdjs._cameraShakeExtension.noiseManager.getGenerator(name).persistence = eventsFunctionContext.getArgument(\"Persistence\");" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Persistence", + "name": "Persistence", + "type": "expression" + }, + { + "description": "Generator name", + "name": "Name", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Change the lacunarity used for noise generation. At its default value \"2\", it doubles the frequency at each octave.", + "fullName": "Noise lacunarity", + "functionType": "Action", + "name": "SetLacunarity", + "private": true, + "sentence": "Change the noise lacunarity of _PARAM2_: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const name = eventsFunctionContext.getArgument(\"Name\");\r", + "\r", + "gdjs._cameraShakeExtension.noiseManager.getGenerator(name).lacunarity = eventsFunctionContext.getArgument(\"Lacunarity\");" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Lacunarity", + "name": "Lacunarity", + "type": "expression" + }, + { + "description": "Generator name", + "name": "Name", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "The seed used for noise generation.", + "fullName": "Noise seed", + "functionType": "Expression", + "name": "Seed", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "eventsFunctionContext.returnValue = gdjs._cameraShakeExtension.noiseManager.seed;", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [], + "objectGroups": [] + }, + { + "description": "The base frequency used for noise generation.", + "fullName": "Noise base frequency", + "functionType": "Expression", + "name": "Frequency", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const name = eventsFunctionContext.getArgument(\"Name\");\r", + "\r", + "eventsFunctionContext.returnValue = gdjs._cameraShakeExtension.noiseManager.getGenerator(name).frequency;" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Generator name", + "name": "Name", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "The number of octaves used for noise generation.", + "fullName": "Noise octaves number", + "functionType": "Expression", + "name": "Octaves", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const name = eventsFunctionContext.getArgument(\"Name\");\r", + "\r", + "eventsFunctionContext.returnValue = gdjs._cameraShakeExtension.noiseManager.getGenerator(name).octaves;" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Generator name", + "name": "Name", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "The persistence used for noise generation.", + "fullName": "Noise persistence", + "functionType": "Expression", + "name": "Persistence", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const name = eventsFunctionContext.getArgument(\"Name\");\r", + "\r", + "eventsFunctionContext.returnValue = gdjs._cameraShakeExtension.noiseManager.getGenerator(name).persistence;" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Generator name", + "name": "Name", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "The lacunarity used for noise generation.", + "fullName": "Noise lacunarity", + "functionType": "Expression", + "name": "Lacunarity", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const name = eventsFunctionContext.getArgument(\"Name\");\r", + "\r", + "eventsFunctionContext.returnValue = gdjs._cameraShakeExtension.noiseManager.getGenerator(name).lacunarity;" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Generator name", + "name": "Name", + "type": "string" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onSceneLoaded" + }, + { + "functionName": "onScenePostEvents" + }, + { + "functionName": "onScenePreEvents" + }, + { + "functionName": "ShakeCamera" + }, + { + "functionName": "CameraShake" + }, + { + "functionName": "StartShaking" + }, + { + "functionName": "StopShaking" + }, + { + "folderName": "Camera shake configuration", + "children": [ + { + "functionName": "SetLayerShakable" + }, + { + "functionName": "SetLayerTranslationAmplitude" + }, + { + "functionName": "SetLayerRotationAmplitude" + }, + { + "functionName": "SetLayerZoomAmplitude" + }, + { + "functionName": "SetLayerShakingFrequency" + }, + { + "functionName": "SetDefaultTranslationAmplitude" + }, + { + "functionName": "SetDefaultRotationAmplitude" + }, + { + "functionName": "SetDefaultZoomAmplitude" + }, + { + "functionName": "SetDefaultShakingFrequency" + } + ] + }, + { + "functionName": "IsShaking" + }, + { + "functionName": "onFirstSceneLoaded" + }, + { + "functionName": "Noise2d" + }, + { + "functionName": "Noise3d" + }, + { + "functionName": "Noise4d" + }, + { + "functionName": "Create" + }, + { + "functionName": "Delete" + }, + { + "functionName": "DeleteAll" + }, + { + "functionName": "SetSeed" + }, + { + "functionName": "SetLoopPeriodX" + }, + { + "functionName": "SetLoopPeriodY" + }, + { + "functionName": "SetFrequency" + }, + { + "functionName": "SetOctaves" + }, + { + "functionName": "SetPersistence" + }, + { + "functionName": "SetLacunarity" + }, + { + "functionName": "Seed" + }, + { + "functionName": "Frequency" + }, + { + "functionName": "Octaves" + }, + { + "functionName": "Persistence" + }, + { + "functionName": "Lacunarity" + } + ] + }, + "eventsBasedBehaviors": [], + "eventsBasedObjects": [] + }, + { + "author": "", + "category": "User interface", + "dimension": "2D", + "extensionNamespace": "", + "fullName": "Button states and effects", + "gdevelopVersion": ">=5.5.222", + "helpPath": "/objects/button", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLWdlc3R1cmUtdGFwLWJ1dHRvbiIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0xMyA1QzE1LjIxIDUgMTcgNi43OSAxNyA5QzE3IDEwLjUgMTYuMiAxMS43NyAxNSAxMi40NlYxMS4yNEMxNS42MSAxMC42OSAxNiA5Ljg5IDE2IDlDMTYgNy4zNCAxNC42NiA2IDEzIDZTMTAgNy4zNCAxMCA5QzEwIDkuODkgMTAuMzkgMTAuNjkgMTEgMTEuMjRWMTIuNDZDOS44IDExLjc3IDkgMTAuNSA5IDlDOSA2Ljc5IDEwLjc5IDUgMTMgNU0yMCAyMC41QzE5Ljk3IDIxLjMyIDE5LjMyIDIxLjk3IDE4LjUgMjJIMTNDMTIuNjIgMjIgMTIuMjYgMjEuODUgMTIgMjEuNTdMOCAxNy4zN0w4Ljc0IDE2LjZDOC45MyAxNi4zOSA5LjIgMTYuMjggOS41IDE2LjI4SDkuN0wxMiAxOFY5QzEyIDguNDUgMTIuNDUgOCAxMyA4UzE0IDguNDUgMTQgOVYxMy40N0wxNS4yMSAxMy42TDE5LjE1IDE1Ljc5QzE5LjY4IDE2LjAzIDIwIDE2LjU2IDIwIDE3LjE0VjIwLjVNMjAgMkg0QzIuOSAyIDIgMi45IDIgNFYxMkMyIDEzLjExIDIuOSAxNCA0IDE0SDhWMTJMNCAxMkw0IDRIMjBMMjAgMTJIMThWMTRIMjBWMTMuOTZMMjAuMDQgMTRDMjEuMTMgMTQgMjIgMTMuMDkgMjIgMTJWNEMyMiAyLjkgMjEuMTEgMiAyMCAyWiIgLz48L3N2Zz4=", + "name": "ButtonStates", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/753a9a794bd885058159b7509f06f5a8f67f72decfccb9a1b0efee26f41c3c4c_gesture-tap-button.svg", + "shortDescription": "Use any object as a button and change appearance according to user interactions.", + "version": "1.4.1", + "description": [ + "Use the \"Button states\" behavior to track user interactions with an object, including:", + "", + "- Hovered", + "- Pressed", + "- Clicked", + "- Idle", + "", + "Add additional behaviors to make juicy buttons with animated responses to user input:", + "", + "- Size", + "- Color", + "- Animation", + "- Object effects" + ], + "origin": { + "identifier": "ButtonStates", + "name": "gdevelop-extension-store" + }, + "tags": [ + "ui", + "button" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2", + "gqDaZjCfevOOxBYkK6zlhtZnXCg1" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT" + }, + "eventsBasedBehaviors": [ + { + "description": "Use objects as buttons.", + "fullName": "Button states", + "helpPath": "", + "iconUrl": "", + "name": "ButtonFSM", + "objectType": "", + "previewIconUrl": "", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Finite state machine", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "The \"Validated\" state only last one frame." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Validated\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"Idle\"" + ] + } + ] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Check position", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Make sure the cursor position is only checked once per frame." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "MouseIsInside", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldCheckHovering", + "True", + "" + ] + }, + { + "type": { + "value": "CollisionPoint" + }, + "parameters": [ + "Object", + "MouseOnlyCursorX(Object.Layer(), 0)", + "MouseOnlyCursorY(Object.Layer(), 0)" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "MouseIsInside", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Touches are always pressed, so ShouldCheckHovering doesn't matter." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "TouchIsInside", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "!=", + "0" + ] + }, + { + "type": { + "value": "CollisionPoint" + }, + "parameters": [ + "Object", + "TouchX(TouchId, Object.Layer(), 0)", + "TouchY(TouchId, Object.Layer(), 0)" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "TouchIsInside", + "True", + "" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Handle touch start", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Index", + "=", + "0" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "StartedTouchOrMouseCount()", + "conditions": [], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CollisionPoint" + }, + "parameters": [ + "Object", + "TouchX(StartedTouchOrMouseId(Index), Object.Layer(), 0)", + "TouchY(StartedTouchOrMouseId(Index), Object.Layer(), 0)" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "StartedTouchOrMouseId(Index)" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "TouchIsInside", + "True", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Hovered\"" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Idle\"" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedInside\"" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Index", + "+", + "1" + ] + } + ] + } + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Apply position changes", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "MouseIsInside", + "True", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Hovered\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"Idle\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "MouseIsInside", + "True", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Idle\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"Hovered\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "TouchIsInside", + "True", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedInside\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedOutside\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "TouchIsInside", + "True", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedOutside\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedInside\"" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Handle touch end", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasTouchEnded" + }, + "parameters": [ + "", + "TouchId" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "0" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedInside\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"Validated\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedInside\"" + ] + }, + { + "type": { + "inverted": true, + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Validated\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"Idle\"" + ] + } + ] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDeActivate", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::ResetState" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Reset the state of the button.", + "fullName": "Reset state", + "functionType": "Action", + "name": "ResetState", + "private": true, + "sentence": "Reset the button state of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "State", + "=", + "\"Idle\"" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchId", + "=", + "0" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is not used.", + "fullName": "Is idle", + "functionType": "Condition", + "name": "IsIdle", + "sentence": "_PARAM0_ is idle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Idle\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button was just clicked.", + "fullName": "Is clicked", + "functionType": "Condition", + "name": "IsClicked", + "sentence": "_PARAM0_ is clicked", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Validated\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the cursor is hovered over the button.", + "fullName": "Is hovered", + "functionType": "Condition", + "name": "IsHovered", + "sentence": "_PARAM0_ is hovered", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Hovered\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is either hovered or pressed but not hovered.", + "fullName": "Is focused", + "functionType": "Condition", + "name": "IsFocused", + "sentence": "_PARAM0_ is focused", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"Hovered\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedOutside\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is currently being pressed with mouse or touch.", + "fullName": "Is pressed", + "functionType": "Condition", + "name": "IsPressed", + "sentence": "_PARAM0_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedInside\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the button is currently being pressed outside with mouse or touch.", + "fullName": "Is held outside", + "functionType": "Condition", + "name": "IsPressedOutside", + "sentence": "_PARAM0_ is held outside", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "State", + "=", + "\"PressedOutside\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the touch id that is using the button or 0 if none.", + "fullName": "Touch id", + "functionType": "ExpressionAndCondition", + "name": "TouchId", + "sentence": "the touch id", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "TouchId" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "onDeActivate" + }, + { + "functionName": "ResetState" + }, + { + "functionName": "IsIdle" + }, + { + "functionName": "IsClicked" + }, + { + "functionName": "IsHovered" + }, + { + "functionName": "IsFocused" + }, + { + "functionName": "IsPressed" + }, + { + "functionName": "IsPressedOutside" + }, + { + "functionName": "TouchId" + } + ] + }, + "propertyDescriptors": [ + { + "value": "true", + "type": "Boolean", + "label": "", + "description": "Should check hovering", + "hidden": true, + "name": "ShouldCheckHovering" + }, + { + "value": "Idle", + "type": "Choice", + "label": "State", + "choices": [ + { + "label": "Idle", + "value": "Idle" + }, + { + "label": "Hovered", + "value": "Hovered" + }, + { + "label": "PressedInside", + "value": "PressedInside" + }, + { + "label": "PressedOutside", + "value": "PressedOutside" + }, + { + "label": "Validated", + "value": "Validated" + } + ], + "hidden": true, + "name": "State" + }, + { + "value": "0", + "type": "Number", + "label": "Touch id", + "hidden": true, + "name": "TouchId" + }, + { + "value": "", + "type": "Boolean", + "label": "Touch is inside", + "hidden": true, + "name": "TouchIsInside" + }, + { + "value": "", + "type": "Boolean", + "label": "Mouse is inside", + "hidden": true, + "name": "MouseIsInside" + }, + { + "value": "", + "type": "Number", + "label": "", + "hidden": true, + "name": "Index" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ShouldCheckHovering" + }, + { + "propertyName": "State" + }, + { + "propertyName": "TouchId" + }, + { + "propertyName": "TouchIsInside" + }, + { + "propertyName": "MouseIsInside" + }, + { + "propertyName": "Index" + } + ] + } + }, + { + "description": "Enable effects on buttons based on their state.", + "fullName": "Button object effects", + "helpPath": "", + "iconUrl": "", + "name": "ButtonObjectEffects", + "objectType": "", + "previewIconUrl": "", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "IdleEffect", + "yes" + ] + }, + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "FocusedEffect", + "no" + ] + }, + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "PressedEffect", + "no" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffects", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "States", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "IdleEffect", + "no" + ] + }, + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "FocusedEffect", + "no" + ] + }, + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "PressedEffect", + "no" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "IdleEffect", + "!=", + "\"\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonFSM::IsIdle" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "IdleEffect", + "yes" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "FocusedEffect", + "!=", + "\"\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonFSM::IsFocused" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "FocusedEffect", + "yes" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PressedEffect", + "!=", + "\"\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "PressedEffect", + "yes" + ] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffects", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the idle state effect of the object.", + "fullName": "Idle state effect", + "functionType": "ExpressionAndCondition", + "group": "Button object effects configuration", + "name": "IdleEffect", + "sentence": "the idle state effect", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "IdleEffect" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffects", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "IdleEffect", + "group": "Button object effects configuration", + "name": "SetIdleEffect", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "IdleEffect", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffects", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the focused state effect of the object. The state is Focused when the button is hovered or held outside.", + "fullName": "Focused state effect", + "functionType": "ExpressionAndCondition", + "group": "Button object effects configuration", + "name": "FocusedEffect", + "sentence": "the focused state effect", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "FocusedEffect" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffects", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FocusedEffect", + "group": "Button object effects configuration", + "name": "SetFocusedEffect", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "FocusedEffect", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffects", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the pressed state effect of the object.", + "fullName": "Pressed state effect", + "functionType": "ExpressionAndCondition", + "group": "Button object effects configuration", + "name": "PressedEffect", + "sentence": "the pressed state effect", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "PressedEffect" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffects", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "PressedEffect", + "group": "Button object effects configuration", + "name": "SetPressedEffect", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PressedEffect", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffects", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPreEvents" + }, + { + "folderName": "Button object effects configuration", + "children": [ + { + "functionName": "IdleEffect" + }, + { + "functionName": "SetIdleEffect" + }, + { + "functionName": "FocusedEffect" + }, + { + "functionName": "SetFocusedEffect" + }, + { + "functionName": "PressedEffect" + }, + { + "functionName": "SetPressedEffect" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Button states", + "extraInformation": [ + "ButtonStates::ButtonFSM" + ], + "choices": [], + "name": "ButtonFSM" + }, + { + "value": "", + "type": "Behavior", + "label": "Effect capability", + "extraInformation": [ + "EffectCapability::EffectBehavior" + ], + "choices": [], + "name": "Effect" + }, + { + "value": "", + "type": "String", + "label": "Idle state effect", + "group": "Effects", + "name": "IdleEffect" + }, + { + "value": "", + "type": "String", + "label": "Focused state effect", + "description": "The state is Focused when the button is hovered or held outside.", + "group": "Effects", + "name": "FocusedEffect" + }, + { + "value": "", + "type": "String", + "label": "Pressed state effect", + "group": "Effects", + "name": "PressedEffect" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ButtonFSM" + }, + { + "propertyName": "Effect" + }, + { + "folderName": "Effects", + "children": [ + { + "propertyName": "IdleEffect" + }, + { + "propertyName": "FocusedEffect" + }, + { + "propertyName": "PressedEffect" + } + ] + } + ] + } + }, + { + "description": "Change the animation of buttons according to their state.", + "fullName": "Button animation", + "helpPath": "", + "iconUrl": "", + "name": "ButtonAnimationName", + "objectType": "", + "previewIconUrl": "", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "AnimatableCapability::AnimatableBehavior::SetName" + }, + "parameters": [ + "Object", + "Animation", + "=", + "IdleAnimationName" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonAnimationName", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Change based on state", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsIdle" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "AnimatableCapability::AnimatableBehavior::SetName" + }, + "parameters": [ + "Object", + "Animation", + "=", + "IdleAnimationName" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsFocused" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "AnimatableCapability::AnimatableBehavior::SetName" + }, + "parameters": [ + "Object", + "Animation", + "=", + "FocusedAnimationName" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "AnimatableCapability::AnimatableBehavior::SetName" + }, + "parameters": [ + "Object", + "Animation", + "=", + "PressedAnimationName" + ] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonAnimationName", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the idle state animation name of the object.", + "fullName": "Idle state animation name", + "functionType": "ExpressionAndCondition", + "group": "Button animation configuration", + "name": "IdleAnimationName", + "sentence": "the idle state animation name", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "IdleAnimationName" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonAnimationName", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "IdleAnimationName", + "group": "Button animation configuration", + "name": "SetIdleAnimationName", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "IdleAnimationName", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonAnimationName", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the focused state animation name of the object. The state is Focused when the button is hovered or held outside.", + "fullName": "Focused state animation name", + "functionType": "ExpressionAndCondition", + "group": "Button animation configuration", + "name": "FocusedAnimationName", + "sentence": "the focused state animation name", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "FocusedAnimationName" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonAnimationName", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FocusedAnimationName", + "group": "Button animation configuration", + "name": "SetFocusedAnimationName", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "FocusedAnimationName", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonAnimationName", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the pressed state animation name of the object.", + "fullName": "Pressed state animation name", + "functionType": "ExpressionAndCondition", + "group": "Button animation configuration", + "name": "PressedAnimationName", + "sentence": "the pressed state animation name", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "PressedAnimationName" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonAnimationName", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "PressedAnimationName", + "group": "Button animation configuration", + "name": "SetPressedAnimationName", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PressedAnimationName", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonAnimationName", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPreEvents" + }, + { + "folderName": "Button animation configuration", + "children": [ + { + "functionName": "IdleAnimationName" + }, + { + "functionName": "SetIdleAnimationName" + }, + { + "functionName": "FocusedAnimationName" + }, + { + "functionName": "SetFocusedAnimationName" + }, + { + "functionName": "PressedAnimationName" + }, + { + "functionName": "SetPressedAnimationName" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Animatable capability", + "extraInformation": [ + "AnimatableCapability::AnimatableBehavior" + ], + "choices": [], + "name": "Animation" + }, + { + "value": "", + "type": "Behavior", + "label": "Button states", + "extraInformation": [ + "ButtonStates::ButtonFSM" + ], + "choices": [], + "name": "ButtonFSM" + }, + { + "value": "Idle", + "type": "String", + "label": "Idle state animation name", + "group": "Animation", + "name": "IdleAnimationName" + }, + { + "value": "Focused", + "type": "String", + "label": "Focused state animation name", + "description": "The state is Focused when the button is hovered or held outside.", + "group": "Animation", + "name": "FocusedAnimationName" + }, + { + "value": "Pressed", + "type": "String", + "label": "Pressed state animation name", + "group": "Animation", + "name": "PressedAnimationName" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Animation" + }, + { + "propertyName": "ButtonFSM" + }, + { + "folderName": "Animation", + "children": [ + { + "propertyName": "IdleAnimationName" + }, + { + "propertyName": "FocusedAnimationName" + }, + { + "propertyName": "PressedAnimationName" + } + ] + } + ] + } + }, + { + "description": "Smoothly change an effect on buttons according to their state.", + "fullName": "Button object effect tween", + "helpPath": "", + "iconUrl": "", + "name": "ButtonObjectEffectTween", + "objectType": "", + "previewIconUrl": "", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "EffectValue", + "=", + "IdleValue" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "States", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsIdle" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "!=", + "\"Idle\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Idle\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonObjectEffectTween::FadeOut" + }, + "parameters": [ + "Object", + "Behavior", + "IdleValue", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsFocused" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Idle\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Focused\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonObjectEffectTween::FadeIn" + }, + "parameters": [ + "Object", + "Behavior", + "FocusedValue", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Pressed\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Focused\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonObjectEffectTween::FadeOut" + }, + "parameters": [ + "Object", + "Behavior", + "FocusedValue", + "" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "!=", + "\"Pressed\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Pressed\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonObjectEffectTween::FadeIn" + }, + "parameters": [ + "Object", + "Behavior", + "PressedValue", + "" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Tween", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "TweenState", + "=", + "\"FadeIn\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "ButtonStates::ButtonObjectEffectTween::PlayTween" + }, + "parameters": [ + "Object", + "Behavior", + "FadeInDuration", + "FadeInEasing", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "TweenState", + "=", + "\"FadeOut\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "ButtonStates::ButtonObjectEffectTween::PlayTween" + }, + "parameters": [ + "Object", + "Behavior", + "FadeOutDuration", + "FadeOutEasing", + "" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Toggle effect", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldDisableEffectInIdle", + "True", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsIdle" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "TweenState", + "=", + "\"NoTween\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "EffectName", + "no" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "ButtonStates::ButtonFSM::IsIdle" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Object", + "Effect", + "EffectName", + "yes" + ] + } + ] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Time delta", + "functionType": "Expression", + "name": "TimeDelta", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "TimeDelta() * LayerTimeScale(Object.Layer())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Fade in", + "functionType": "Action", + "name": "FadeIn", + "private": true, + "sentence": "_PARAM0_ fade in to _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TweenTime", + "=", + "0" + ] + }, + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "TweenState", + "=", + "\"FadeIn\"" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TweenInitialValue", + "=", + "EffectValue" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TweenTargetedValue", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "fullName": "Fade out", + "functionType": "Action", + "name": "FadeOut", + "private": true, + "sentence": "_PARAM0_ fade out to _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TweenTime", + "=", + "0" + ] + }, + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "TweenState", + "=", + "\"FadeOut\"" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TweenInitialValue", + "=", + "EffectValue" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TweenTargetedValue", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "fullName": "Play tween", + "functionType": "Action", + "name": "PlayTween", + "private": true, + "sentence": "Tween the effect property of object _PARAM0_ over _PARAM2_ seconds with _PARAM3_ easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TweenTime", + "<", + "Duration" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TweenTime", + "+", + "Object.Behavior::TimeDelta()" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "EffectValue", + "=", + "Tween::Ease(Easing, TweenInitialValue, TweenTargetedValue, TweenTime / Duration)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TweenTime", + ">=", + "Duration" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "TweenState", + "=", + "\"NoTween\"" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "EffectValue", + "=", + "TweenTargetedValue" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "EffectCapability::EffectBehavior::SetEffectDoubleParameter" + }, + "parameters": [ + "Object", + "Effect", + "EffectName", + "EffectProperty", + "EffectValue" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "supplementaryInformation": "[]", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "the effect name of the object.", + "fullName": "Effect name", + "functionType": "ExpressionAndCondition", + "group": "Button effect tween configuration", + "name": "EffectName", + "sentence": "the effect name", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "EffectName" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the effect parameter of the object. The effect parameter names can be found in the effects tab with the \"Show parameter names\" action of the drop down menu.", + "fullName": "Effect parameter", + "functionType": "ExpressionAndCondition", + "group": "Button effect tween configuration", + "name": "EffectProperty", + "sentence": "the effect parameter", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "EffectProperty" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Change the effect parameter of the object. The effect parameter names can be found in the effects tab with the \"Show parameter names\" action of the drop down menu.", + "fullName": "Effect parameter", + "functionType": "Action", + "getterName": "EffectName", + "group": "Button effect tween configuration", + "name": "SetEffectProperty", + "sentence": "Change the tweened effect of _PARAM0_ to _PARAM2_ with parameter _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "EffectName", + "=", + "NewEffectName" + ] + }, + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "EffectProperty", + "=", + "NewPropertyName" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + }, + { + "description": "Effect name", + "name": "NewEffectName", + "type": "string" + }, + { + "description": "Parameter name", + "name": "NewPropertyName", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "the idle effect parameter value of the object.", + "fullName": "Idle effect parameter value", + "functionType": "ExpressionAndCondition", + "group": "Button effect tween configuration", + "name": "IdleValue", + "sentence": "the idle effect parameter value", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "IdleValue" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "IdleValue", + "group": "Button effect tween configuration", + "name": "SetIdleValue", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "IdleValue", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the focused effect parameter value of the object. The state is Focused when the button is hovered or held outside.", + "fullName": "Focused effect parameter value", + "functionType": "ExpressionAndCondition", + "group": "Button effect tween configuration", + "name": "FocusedValue", + "sentence": "the focused effect parameter value", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FocusedValue" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FocusedValue", + "group": "Button effect tween configuration", + "name": "SetFocusedValue", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FocusedValue", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the pressed effect parameter value of the object.", + "fullName": "Pressed effect parameter value", + "functionType": "ExpressionAndCondition", + "group": "Button effect tween configuration", + "name": "PressedValue", + "sentence": "the pressed effect parameter value", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "PressedValue" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "PressedValue", + "group": "Button effect tween configuration", + "name": "SetPressedValue", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "PressedValue", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-in easing of the object.", + "fullName": "Fade-in easing", + "functionType": "ExpressionAndCondition", + "group": "Button effect tween configuration", + "name": "FadeInEasing", + "sentence": "the fade-in easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "FadeInEasing" + ] + } + ] + } + ], + "expressionType": { + "supplementaryInformation": "[\"linear\",\"easeInQuad\",\"easeOutQuad\",\"easeInOutQuad\",\"easeInCubic\",\"easeOutCubic\",\"easeInOutCubic\",\"easeInQuart\",\"easeOutQuart\",\"easeInOutQuart\",\"easeInQuint\",\"easeOutQuint\",\"easeInOutQuint\",\"easeInOutSine\",\"easeInExpo\",\"easeOutExpo\",\"easeInOutExpo\",\"easeInCirc\",\"easeOutCirc\",\"easeInOutCirc\",\"easeOutBounce\",\"easeInBack\",\"easeOutBack\",\"easeInOutBack\",\"elastic\",\"swingFromTo\",\"swingFrom\",\"swingTo\",\"bounce\",\"bouncePast\",\"easeFromTo\",\"easeFrom\",\"easeTo\"]", + "type": "stringWithSelector" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeInEasing", + "group": "Button effect tween configuration", + "name": "SetFadeInEasing", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "FadeInEasing", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-out easing of the object.", + "fullName": "Fade-out easing", + "functionType": "ExpressionAndCondition", + "group": "Button effect tween configuration", + "name": "FadeOutEasing", + "sentence": "the fade-out easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "FadeOutEasing" + ] + } + ] + } + ], + "expressionType": { + "supplementaryInformation": "[\"linear\",\"easeInQuad\",\"easeOutQuad\",\"easeInOutQuad\",\"easeInCubic\",\"easeOutCubic\",\"easeInOutCubic\",\"easeInQuart\",\"easeOutQuart\",\"easeInOutQuart\",\"easeInQuint\",\"easeOutQuint\",\"easeInOutQuint\",\"easeInOutSine\",\"easeInExpo\",\"easeOutExpo\",\"easeInOutExpo\",\"easeInCirc\",\"easeOutCirc\",\"easeInOutCirc\",\"easeOutBounce\",\"easeInBack\",\"easeOutBack\",\"easeInOutBack\",\"elastic\",\"swingFromTo\",\"swingFrom\",\"swingTo\",\"bounce\",\"bouncePast\",\"easeFromTo\",\"easeFrom\",\"easeTo\"]", + "type": "stringWithSelector" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeOutEasing", + "group": "Button effect tween configuration", + "name": "SetFadeOutEasing", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "FadeOutEasing", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-in duration of the object.", + "fullName": "Fade-in duration", + "functionType": "ExpressionAndCondition", + "group": "Button effect tween configuration", + "name": "FadeInDuration", + "sentence": "the fade-in duration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FadeInDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeInDuration", + "group": "Button effect tween configuration", + "name": "SetFadeInDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FadeInDuration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-out duration of the object.", + "fullName": "Fade-out duration", + "functionType": "ExpressionAndCondition", + "group": "Button effect tween configuration", + "name": "FadeOutDuration", + "sentence": "the fade-out duration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FadeOutDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeOutDuration", + "group": "Button effect tween configuration", + "name": "SetFadeOutDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FadeOutDuration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonObjectEffectTween", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "TimeDelta" + }, + { + "functionName": "FadeIn" + }, + { + "functionName": "FadeOut" + }, + { + "functionName": "PlayTween" + }, + { + "folderName": "Button effect tween configuration", + "children": [ + { + "functionName": "EffectName" + }, + { + "functionName": "EffectProperty" + }, + { + "functionName": "SetEffectProperty" + }, + { + "functionName": "IdleValue" + }, + { + "functionName": "SetIdleValue" + }, + { + "functionName": "FocusedValue" + }, + { + "functionName": "SetFocusedValue" + }, + { + "functionName": "PressedValue" + }, + { + "functionName": "SetPressedValue" + }, + { + "functionName": "FadeInEasing" + }, + { + "functionName": "SetFadeInEasing" + }, + { + "functionName": "FadeOutEasing" + }, + { + "functionName": "SetFadeOutEasing" + }, + { + "functionName": "FadeInDuration" + }, + { + "functionName": "SetFadeInDuration" + }, + { + "functionName": "FadeOutDuration" + }, + { + "functionName": "SetFadeOutDuration" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Button states", + "extraInformation": [ + "ButtonStates::ButtonFSM" + ], + "choices": [], + "name": "ButtonFSM" + }, + { + "value": "", + "type": "Behavior", + "label": "Effect capability", + "extraInformation": [ + "EffectCapability::EffectBehavior" + ], + "choices": [], + "name": "Effect" + }, + { + "value": "Effect", + "type": "String", + "label": "Effect name", + "group": "Effect", + "name": "EffectName" + }, + { + "value": "", + "type": "String", + "label": "Effect parameter", + "description": "The effect parameter names can be found in the effects tab with the \"Show parameter names\" action of the drop down menu.", + "group": "Effect", + "name": "EffectProperty" + }, + { + "value": "0", + "type": "Number", + "label": "Idle effect parameter value", + "group": "Value", + "name": "IdleValue" + }, + { + "value": "0", + "type": "Number", + "label": "Focused effect parameter value", + "description": "The state is Focused when the button is hovered or held outside.", + "group": "Value", + "name": "FocusedValue" + }, + { + "value": "0", + "type": "Number", + "label": "Pressed effect parameter value", + "group": "Value", + "name": "PressedValue" + }, + { + "value": "easeInOutQuad", + "type": "Choice", + "label": "Fade-in easing", + "group": "Speed", + "choices": [ + { + "label": "linear", + "value": "linear" + }, + { + "label": "easeInQuad", + "value": "easeInQuad" + }, + { + "label": "easeOutQuad", + "value": "easeOutQuad" + }, + { + "label": "easeInOutQuad", + "value": "easeInOutQuad" + }, + { + "label": "easeInCubic", + "value": "easeInCubic" + }, + { + "label": "easeOutCubic", + "value": "easeOutCubic" + }, + { + "label": "easeInOutCubic", + "value": "easeInOutCubic" + }, + { + "label": "easeInQuart", + "value": "easeInQuart" + }, + { + "label": "easeOutQuart", + "value": "easeOutQuart" + }, + { + "label": "easeInOutQuart", + "value": "easeInOutQuart" + }, + { + "label": "easeInQuint", + "value": "easeInQuint" + }, + { + "label": "easeOutQuint", + "value": "easeOutQuint" + }, + { + "label": "easeInOutQuint", + "value": "easeInOutQuint" + }, + { + "label": "easeInOutSine", + "value": "easeInOutSine" + }, + { + "label": "easeInExpo", + "value": "easeInExpo" + }, + { + "label": "easeOutExpo", + "value": "easeOutExpo" + }, + { + "label": "easeInOutExpo", + "value": "easeInOutExpo" + }, + { + "label": "easeInCirc", + "value": "easeInCirc" + }, + { + "label": "easeOutCirc", + "value": "easeOutCirc" + }, + { + "label": "easeInOutCirc", + "value": "easeInOutCirc" + }, + { + "label": "easeOutBounce", + "value": "easeOutBounce" + }, + { + "label": "easeInBack", + "value": "easeInBack" + }, + { + "label": "easeOutBack", + "value": "easeOutBack" + }, + { + "label": "easeInOutBack", + "value": "easeInOutBack" + }, + { + "label": "elastic", + "value": "elastic" + }, + { + "label": "swingFromTo", + "value": "swingFromTo" + }, + { + "label": "swingFrom", + "value": "swingFrom" + }, + { + "label": "swingTo", + "value": "swingTo" + }, + { + "label": "bounce", + "value": "bounce" + }, + { + "label": "bouncePast", + "value": "bouncePast" + }, + { + "label": "easeFromTo", + "value": "easeFromTo" + }, + { + "label": "easeFrom", + "value": "easeFrom" + }, + { + "label": "easeTo", + "value": "easeTo" + } + ], + "name": "FadeInEasing" + }, + { + "value": "easeInOutQuad", + "type": "Choice", + "label": "Fade-out easing", + "group": "Speed", + "choices": [ + { + "label": "linear", + "value": "linear" + }, + { + "label": "easeInQuad", + "value": "easeInQuad" + }, + { + "label": "easeOutQuad", + "value": "easeOutQuad" + }, + { + "label": "easeInOutQuad", + "value": "easeInOutQuad" + }, + { + "label": "easeInCubic", + "value": "easeInCubic" + }, + { + "label": "easeOutCubic", + "value": "easeOutCubic" + }, + { + "label": "easeInOutCubic", + "value": "easeInOutCubic" + }, + { + "label": "easeInQuart", + "value": "easeInQuart" + }, + { + "label": "easeOutQuart", + "value": "easeOutQuart" + }, + { + "label": "easeInOutQuart", + "value": "easeInOutQuart" + }, + { + "label": "easeInQuint", + "value": "easeInQuint" + }, + { + "label": "easeOutQuint", + "value": "easeOutQuint" + }, + { + "label": "easeInOutQuint", + "value": "easeInOutQuint" + }, + { + "label": "easeInOutSine", + "value": "easeInOutSine" + }, + { + "label": "easeInExpo", + "value": "easeInExpo" + }, + { + "label": "easeOutExpo", + "value": "easeOutExpo" + }, + { + "label": "easeInOutExpo", + "value": "easeInOutExpo" + }, + { + "label": "easeInCirc", + "value": "easeInCirc" + }, + { + "label": "easeOutCirc", + "value": "easeOutCirc" + }, + { + "label": "easeInOutCirc", + "value": "easeInOutCirc" + }, + { + "label": "easeOutBounce", + "value": "easeOutBounce" + }, + { + "label": "easeInBack", + "value": "easeInBack" + }, + { + "label": "easeOutBack", + "value": "easeOutBack" + }, + { + "label": "easeInOutBack", + "value": "easeInOutBack" + }, + { + "label": "elastic", + "value": "elastic" + }, + { + "label": "swingFromTo", + "value": "swingFromTo" + }, + { + "label": "swingFrom", + "value": "swingFrom" + }, + { + "label": "swingTo", + "value": "swingTo" + }, + { + "label": "bounce", + "value": "bounce" + }, + { + "label": "bouncePast", + "value": "bouncePast" + }, + { + "label": "easeFromTo", + "value": "easeFromTo" + }, + { + "label": "easeFrom", + "value": "easeFrom" + }, + { + "label": "easeTo", + "value": "easeTo" + } + ], + "name": "FadeOutEasing" + }, + { + "value": "0.125", + "type": "Number", + "unit": "Second", + "label": "Fade-in duration", + "group": "Speed", + "name": "FadeInDuration" + }, + { + "value": "0.5", + "type": "Number", + "unit": "Second", + "label": "Fade-out duration", + "group": "Speed", + "name": "FadeOutDuration" + }, + { + "value": "", + "type": "Boolean", + "label": "Disable the effect in idle state", + "advanced": true, + "name": "ShouldDisableEffectInIdle" + }, + { + "value": "Idle", + "type": "Choice", + "label": "", + "choices": [ + { + "label": "Idle", + "value": "Idle" + }, + { + "label": "Focused", + "value": "Focused" + }, + { + "label": "Pressed", + "value": "Pressed" + } + ], + "hidden": true, + "name": "PreviousState" + }, + { + "value": "0", + "type": "Number", + "label": "", + "hidden": true, + "name": "TweenInitialValue" + }, + { + "value": "0", + "type": "Number", + "label": "", + "hidden": true, + "name": "TweenTargetedValue" + }, + { + "value": "0", + "type": "Number", + "label": "", + "hidden": true, + "name": "TweenTime" + }, + { + "value": "NoTween", + "type": "Choice", + "label": "", + "choices": [ + { + "label": "NoTween", + "value": "NoTween" + }, + { + "label": "FadeIn", + "value": "FadeIn" + }, + { + "label": "FadeOut", + "value": "FadeOut" + } + ], + "hidden": true, + "name": "TweenState" + }, + { + "value": "0", + "type": "Number", + "label": "", + "hidden": true, + "name": "EffectValue" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ButtonFSM" + }, + { + "propertyName": "Effect" + }, + { + "folderName": "Effect", + "children": [ + { + "propertyName": "EffectName" + }, + { + "propertyName": "EffectProperty" + } + ] + }, + { + "folderName": "Value", + "children": [ + { + "propertyName": "IdleValue" + }, + { + "propertyName": "FocusedValue" + }, + { + "propertyName": "PressedValue" + } + ] + }, + { + "folderName": "Speed", + "children": [ + { + "propertyName": "FadeInEasing" + }, + { + "propertyName": "FadeOutEasing" + }, + { + "propertyName": "FadeInDuration" + }, + { + "propertyName": "FadeOutDuration" + } + ] + }, + { + "propertyName": "ShouldDisableEffectInIdle" + }, + { + "propertyName": "PreviousState" + }, + { + "propertyName": "TweenInitialValue" + }, + { + "propertyName": "TweenTargetedValue" + }, + { + "propertyName": "TweenTime" + }, + { + "propertyName": "TweenState" + }, + { + "propertyName": "EffectValue" + } + ] + } + }, + { + "description": "Smoothly resize buttons according to their state.", + "fullName": "Button scale tween", + "helpPath": "", + "iconUrl": "", + "name": "ButtonScaleTween", + "objectType": "", + "previewIconUrl": "", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ScalableCapability::ScalableBehavior::SetValue" + }, + "parameters": [ + "Object", + "Scale", + "=", + "IdleScale" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Change based on state", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsIdle" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "!=", + "\"Idle\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Idle\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonScaleTween::FadeOut" + }, + "parameters": [ + "Object", + "Behavior", + "IdleScale", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsFocused" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Idle\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Focused\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonScaleTween::FadeIn" + }, + "parameters": [ + "Object", + "Behavior", + "FocusedScale", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Pressed\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Focused\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonScaleTween::FadeOut" + }, + "parameters": [ + "Object", + "Behavior", + "FocusedScale", + "" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "!=", + "\"Pressed\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Pressed\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonScaleTween::FadeIn" + }, + "parameters": [ + "Object", + "Behavior", + "PressedScale", + "" + ] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Fade in", + "functionType": "Action", + "name": "FadeIn", + "private": true, + "sentence": "_PARAM0_ fade in to _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween::AddObjectScaleTween" + }, + "parameters": [ + "Object", + "Tween", + "\"__ButtonScaleTween.Fade\"", + "Value", + "Value", + "FadeInEasing", + "1000 * FadeInDuration", + "no", + "yes" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "fullName": "Fade out", + "functionType": "Action", + "name": "FadeOut", + "private": true, + "sentence": "_PARAM0_ fade out to _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween::AddObjectScaleTween" + }, + "parameters": [ + "Object", + "Tween", + "\"__ButtonScaleTween.Fade\"", + "Value", + "Value", + "FadeOutEasing", + "1000 * FadeOutDuration", + "no", + "yes" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the idle state size scale of the object.", + "fullName": "Idle state size scale", + "functionType": "ExpressionAndCondition", + "group": "Button scale tween configuration", + "name": "IdleScale", + "sentence": "the idle state size scale", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "IdleScale" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "IdleScale", + "group": "Button scale tween configuration", + "name": "SetIdleScale", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "IdleScale", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the focused state size scale of the object. The state is Focused when the button is hovered or held outside.", + "fullName": "Focused state size scale", + "functionType": "ExpressionAndCondition", + "group": "Button scale tween configuration", + "name": "FocusedScale", + "sentence": "the focused state size scale", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FocusedScale" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FocusedScale", + "group": "Button scale tween configuration", + "name": "SetFocusedScale", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FocusedScale", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the pressed state size scale of the object.", + "fullName": "Pressed state size scale", + "functionType": "ExpressionAndCondition", + "group": "Button scale tween configuration", + "name": "PressedScale", + "sentence": "the pressed state size scale", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "PressedScale" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "PressedScale", + "group": "Button scale tween configuration", + "name": "SetPressedScale", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "PressedScale", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-in duration of the object.", + "fullName": "Fade-in duration", + "functionType": "ExpressionAndCondition", + "group": "Button scale tween configuration", + "name": "FadeInDuration", + "sentence": "the fade-in duration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FadeInDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeInDuration", + "group": "Button scale tween configuration", + "name": "SetFadeInDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FadeInDuration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-out duration of the object.", + "fullName": "Fade-out duration", + "functionType": "ExpressionAndCondition", + "group": "Button scale tween configuration", + "name": "FadeOutDuration", + "sentence": "the fade-out duration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FadeOutDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeOutDuration", + "group": "Button scale tween configuration", + "name": "SetFadeOutDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FadeOutDuration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-in easing of the object.", + "fullName": "Fade-in easing", + "functionType": "ExpressionAndCondition", + "group": "Button scale tween configuration", + "name": "FadeInEasing", + "sentence": "the fade-in easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "FadeInEasing" + ] + } + ] + } + ], + "expressionType": { + "supplementaryInformation": "[\"linear\",\"easeInQuad\",\"easeOutQuad\",\"easeInOutQuad\",\"easeInCubic\",\"easeOutCubic\",\"easeInOutCubic\",\"easeInQuart\",\"easeOutQuart\",\"easeInOutQuart\",\"easeInQuint\",\"easeOutQuint\",\"easeInOutQuint\",\"easeInOutSine\",\"easeInExpo\",\"easeOutExpo\",\"easeInOutExpo\",\"easeInCirc\",\"easeOutCirc\",\"easeInOutCirc\",\"easeOutBounce\",\"easeInBack\",\"easeOutBack\",\"easeInOutBack\",\"elastic\",\"swingFromTo\",\"swingFrom\",\"swingTo\",\"bounce\",\"bouncePast\",\"easeFromTo\",\"easeFrom\",\"easeTo\"]", + "type": "stringWithSelector" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeInEasing", + "group": "Button scale tween configuration", + "name": "SetFadeInEasing", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "FadeInEasing", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-out easing of the object.", + "fullName": "Fade-out easing", + "functionType": "ExpressionAndCondition", + "group": "Button scale tween configuration", + "name": "FadeOutEasing", + "sentence": "the fade-out easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "FadeOutEasing" + ] + } + ] + } + ], + "expressionType": { + "supplementaryInformation": "[\"linear\",\"easeInQuad\",\"easeOutQuad\",\"easeInOutQuad\",\"easeInCubic\",\"easeOutCubic\",\"easeInOutCubic\",\"easeInQuart\",\"easeOutQuart\",\"easeInOutQuart\",\"easeInQuint\",\"easeOutQuint\",\"easeInOutQuint\",\"easeInOutSine\",\"easeInExpo\",\"easeOutExpo\",\"easeInOutExpo\",\"easeInCirc\",\"easeOutCirc\",\"easeInOutCirc\",\"easeOutBounce\",\"easeInBack\",\"easeOutBack\",\"easeInOutBack\",\"elastic\",\"swingFromTo\",\"swingFrom\",\"swingTo\",\"bounce\",\"bouncePast\",\"easeFromTo\",\"easeFrom\",\"easeTo\"]", + "type": "stringWithSelector" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeOutEasing", + "group": "Button scale tween configuration", + "name": "SetFadeOutEasing", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "FadeOutEasing", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonScaleTween", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "FadeIn" + }, + { + "functionName": "FadeOut" + }, + { + "folderName": "Button scale tween configuration", + "children": [ + { + "functionName": "IdleScale" + }, + { + "functionName": "SetIdleScale" + }, + { + "functionName": "FocusedScale" + }, + { + "functionName": "SetFocusedScale" + }, + { + "functionName": "PressedScale" + }, + { + "functionName": "SetPressedScale" + }, + { + "functionName": "FadeInDuration" + }, + { + "functionName": "SetFadeInDuration" + }, + { + "functionName": "FadeOutDuration" + }, + { + "functionName": "SetFadeOutDuration" + }, + { + "functionName": "FadeInEasing" + }, + { + "functionName": "SetFadeInEasing" + }, + { + "functionName": "FadeOutEasing" + }, + { + "functionName": "SetFadeOutEasing" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Scalable capability", + "extraInformation": [ + "ScalableCapability::ScalableBehavior" + ], + "choices": [], + "name": "Scale" + }, + { + "value": "", + "type": "Behavior", + "label": "Button states behavior (required)", + "extraInformation": [ + "ButtonStates::ButtonFSM" + ], + "choices": [], + "name": "ButtonFSM" + }, + { + "value": "", + "type": "Behavior", + "label": "Tween behavior (required)", + "extraInformation": [ + "Tween::TweenBehavior" + ], + "choices": [], + "name": "Tween" + }, + { + "value": "1", + "type": "Number", + "unit": "Dimensionless", + "label": "Idle state size scale", + "group": "Size", + "name": "IdleScale" + }, + { + "value": "1", + "type": "Number", + "unit": "Dimensionless", + "label": "Focused state size scale", + "description": "The state is Focused when the button is hovered or held outside.", + "group": "Size", + "name": "FocusedScale" + }, + { + "value": "0.1", + "type": "Number", + "unit": "Second", + "label": "Fade-in duration", + "group": "Speed", + "name": "FadeInDuration" + }, + { + "value": "0.2", + "type": "Number", + "unit": "Second", + "label": "Fade-out duration", + "group": "Speed", + "name": "FadeOutDuration" + }, + { + "value": "0.95", + "type": "Number", + "unit": "Dimensionless", + "label": "Pressed state size scale", + "group": "Size", + "name": "PressedScale" + }, + { + "value": "easeInOutQuad", + "type": "Choice", + "label": "Fade-in easing", + "group": "Speed", + "choices": [ + { + "label": "linear", + "value": "linear" + }, + { + "label": "easeInQuad", + "value": "easeInQuad" + }, + { + "label": "easeOutQuad", + "value": "easeOutQuad" + }, + { + "label": "easeInOutQuad", + "value": "easeInOutQuad" + }, + { + "label": "easeInCubic", + "value": "easeInCubic" + }, + { + "label": "easeOutCubic", + "value": "easeOutCubic" + }, + { + "label": "easeInOutCubic", + "value": "easeInOutCubic" + }, + { + "label": "easeInQuart", + "value": "easeInQuart" + }, + { + "label": "easeOutQuart", + "value": "easeOutQuart" + }, + { + "label": "easeInOutQuart", + "value": "easeInOutQuart" + }, + { + "label": "easeInQuint", + "value": "easeInQuint" + }, + { + "label": "easeOutQuint", + "value": "easeOutQuint" + }, + { + "label": "easeInOutQuint", + "value": "easeInOutQuint" + }, + { + "label": "easeInOutSine", + "value": "easeInOutSine" + }, + { + "label": "easeInExpo", + "value": "easeInExpo" + }, + { + "label": "easeOutExpo", + "value": "easeOutExpo" + }, + { + "label": "easeInOutExpo", + "value": "easeInOutExpo" + }, + { + "label": "easeInCirc", + "value": "easeInCirc" + }, + { + "label": "easeOutCirc", + "value": "easeOutCirc" + }, + { + "label": "easeInOutCirc", + "value": "easeInOutCirc" + }, + { + "label": "easeOutBounce", + "value": "easeOutBounce" + }, + { + "label": "easeInBack", + "value": "easeInBack" + }, + { + "label": "easeOutBack", + "value": "easeOutBack" + }, + { + "label": "easeInOutBack", + "value": "easeInOutBack" + }, + { + "label": "elastic", + "value": "elastic" + }, + { + "label": "swingFromTo", + "value": "swingFromTo" + }, + { + "label": "swingFrom", + "value": "swingFrom" + }, + { + "label": "swingTo", + "value": "swingTo" + }, + { + "label": "bounce", + "value": "bounce" + }, + { + "label": "bouncePast", + "value": "bouncePast" + }, + { + "label": "easeFromTo", + "value": "easeFromTo" + }, + { + "label": "easeFrom", + "value": "easeFrom" + }, + { + "label": "easeTo", + "value": "easeTo" + } + ], + "name": "FadeInEasing" + }, + { + "value": "easeInOutQuad", + "type": "Choice", + "label": "Fade-out easing", + "group": "Speed", + "choices": [ + { + "label": "linear", + "value": "linear" + }, + { + "label": "easeInQuad", + "value": "easeInQuad" + }, + { + "label": "easeOutQuad", + "value": "easeOutQuad" + }, + { + "label": "easeInOutQuad", + "value": "easeInOutQuad" + }, + { + "label": "easeInCubic", + "value": "easeInCubic" + }, + { + "label": "easeOutCubic", + "value": "easeOutCubic" + }, + { + "label": "easeInOutCubic", + "value": "easeInOutCubic" + }, + { + "label": "easeInQuart", + "value": "easeInQuart" + }, + { + "label": "easeOutQuart", + "value": "easeOutQuart" + }, + { + "label": "easeInOutQuart", + "value": "easeInOutQuart" + }, + { + "label": "easeInQuint", + "value": "easeInQuint" + }, + { + "label": "easeOutQuint", + "value": "easeOutQuint" + }, + { + "label": "easeInOutQuint", + "value": "easeInOutQuint" + }, + { + "label": "easeInOutSine", + "value": "easeInOutSine" + }, + { + "label": "easeInExpo", + "value": "easeInExpo" + }, + { + "label": "easeOutExpo", + "value": "easeOutExpo" + }, + { + "label": "easeInOutExpo", + "value": "easeInOutExpo" + }, + { + "label": "easeInCirc", + "value": "easeInCirc" + }, + { + "label": "easeOutCirc", + "value": "easeOutCirc" + }, + { + "label": "easeInOutCirc", + "value": "easeInOutCirc" + }, + { + "label": "easeOutBounce", + "value": "easeOutBounce" + }, + { + "label": "easeInBack", + "value": "easeInBack" + }, + { + "label": "easeOutBack", + "value": "easeOutBack" + }, + { + "label": "easeInOutBack", + "value": "easeInOutBack" + }, + { + "label": "elastic", + "value": "elastic" + }, + { + "label": "swingFromTo", + "value": "swingFromTo" + }, + { + "label": "swingFrom", + "value": "swingFrom" + }, + { + "label": "swingTo", + "value": "swingTo" + }, + { + "label": "bounce", + "value": "bounce" + }, + { + "label": "bouncePast", + "value": "bouncePast" + }, + { + "label": "easeFromTo", + "value": "easeFromTo" + }, + { + "label": "easeFrom", + "value": "easeFrom" + }, + { + "label": "easeTo", + "value": "easeTo" + } + ], + "name": "FadeOutEasing" + }, + { + "value": "Idle", + "type": "Choice", + "label": "", + "choices": [ + { + "label": "Idle", + "value": "Idle" + }, + { + "label": "Focused", + "value": "Focused" + }, + { + "label": "Pressed", + "value": "Pressed" + } + ], + "hidden": true, + "name": "PreviousState" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Scale" + }, + { + "propertyName": "ButtonFSM" + }, + { + "propertyName": "Tween" + }, + { + "folderName": "Size", + "children": [ + { + "propertyName": "IdleScale" + }, + { + "propertyName": "FocusedScale" + }, + { + "propertyName": "PressedScale" + } + ] + }, + { + "folderName": "Speed", + "children": [ + { + "propertyName": "FadeInDuration" + }, + { + "propertyName": "FadeOutDuration" + }, + { + "propertyName": "FadeInEasing" + }, + { + "propertyName": "FadeOutEasing" + } + ] + }, + { + "propertyName": "PreviousState" + } + ] + } + }, + { + "description": "Smoothly change the color tint of buttons according to their state.", + "fullName": "Button color tint tween", + "helpPath": "", + "iconUrl": "", + "name": "ButtonColorTintTween", + "objectType": "Sprite", + "previewIconUrl": "", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ChangeColor" + }, + "parameters": [ + "Object", + "IdleColorTint" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "States", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsIdle" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "!=", + "\"Idle\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Idle\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonColorTintTween::FadeOut" + }, + "parameters": [ + "Object", + "Behavior", + "IdleColorTint", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsFocused" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Idle\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Focused\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonColorTintTween::FadeIn" + }, + "parameters": [ + "Object", + "Behavior", + "FocusedColorTint", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Pressed\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Focused\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonColorTintTween::FadeOut" + }, + "parameters": [ + "Object", + "Behavior", + "FocusedColorTint", + "" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "Object", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "PreviousState", + "!=", + "\"Pressed\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PreviousState", + "=", + "\"Pressed\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonColorTintTween::FadeIn" + }, + "parameters": [ + "Object", + "Behavior", + "PressedColorTint", + "" + ] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Fade in", + "functionType": "Action", + "name": "FadeIn", + "private": true, + "sentence": "_PARAM0_ fade in to _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween::AddObjectColorTween" + }, + "parameters": [ + "Object", + "Tween", + "\"__ButtonColorTintTween.Fade\"", + "Value", + "FadeInEasing", + "1000 * FadeInDuration", + "no", + "yes" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + }, + { + "description": "Value", + "name": "Value", + "type": "color" + } + ], + "objectGroups": [] + }, + { + "fullName": "Fade out", + "functionType": "Action", + "name": "FadeOut", + "private": true, + "sentence": "_PARAM0_ fade out to _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween::AddObjectColorTween" + }, + "parameters": [ + "Object", + "Tween", + "\"__ButtonColorTintTween.Fade\"", + "Value", + "FadeOutEasing", + "1000 * FadeOutDuration", + "no", + "yes" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + }, + { + "description": "Value", + "name": "Value", + "type": "color" + } + ], + "objectGroups": [] + }, + { + "description": "the idle state color tint of the object.", + "fullName": "Idle state color tint", + "functionType": "ExpressionAndCondition", + "group": "Button color tint tween", + "name": "IdleColorTint", + "sentence": "the idle state color tint", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "IdleColorTint" + ] + } + ] + } + ], + "expressionType": { + "type": "color" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "IdleColorTint", + "group": "Button color tint tween", + "name": "SetIdleColorTint", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "IdleColorTint", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the focused state color tint of the object. The state is Focused when the button is hovered or held outside.", + "fullName": "Focused state color tint", + "functionType": "ExpressionAndCondition", + "group": "Button color tint tween", + "name": "FocusedColorTint", + "sentence": "the focused state color tint", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "FocusedColorTint" + ] + } + ] + } + ], + "expressionType": { + "type": "color" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FocusedColorTint", + "group": "Button color tint tween", + "name": "SetFocusedColorTint", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "FocusedColorTint", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the pressed state color tint of the object.", + "fullName": "Pressed state color tint", + "functionType": "ExpressionAndCondition", + "group": "Button color tint tween", + "name": "PressedColorTint", + "sentence": "the pressed state color tint", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "PressedColorTint" + ] + } + ] + } + ], + "expressionType": { + "type": "color" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "PressedColorTint", + "group": "Button color tint tween", + "name": "SetPressedColorTint", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "PressedColorTint", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-in duration of the object.", + "fullName": "Fade-in duration", + "functionType": "ExpressionAndCondition", + "group": "Button color tint tween", + "name": "FadeInDuration", + "sentence": "the fade-in duration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FadeInDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeInDuration", + "group": "Button color tint tween", + "name": "SetFadeInDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FadeInDuration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-out duration of the object.", + "fullName": "Fade-out duration", + "functionType": "ExpressionAndCondition", + "group": "Button color tint tween", + "name": "FadeOutDuration", + "sentence": "the fade-out duration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "FadeOutDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeOutDuration", + "group": "Button color tint tween", + "name": "SetFadeOutDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FadeOutDuration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-in easing of the object.", + "fullName": "Fade-in easing", + "functionType": "ExpressionAndCondition", + "group": "Button color tint tween", + "name": "FadeInEasing", + "sentence": "the fade-in easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "FadeInEasing" + ] + } + ] + } + ], + "expressionType": { + "supplementaryInformation": "[\"linear\",\"easeInQuad\",\"easeOutQuad\",\"easeInOutQuad\",\"easeInCubic\",\"easeOutCubic\",\"easeInOutCubic\",\"easeInQuart\",\"easeOutQuart\",\"easeInOutQuart\",\"easeInQuint\",\"easeOutQuint\",\"easeInOutQuint\",\"easeInOutSine\",\"easeInExpo\",\"easeOutExpo\",\"easeInOutExpo\",\"easeInCirc\",\"easeOutCirc\",\"easeInOutCirc\",\"easeOutBounce\",\"easeInBack\",\"easeOutBack\",\"easeInOutBack\",\"elastic\",\"swingFromTo\",\"swingFrom\",\"swingTo\",\"bounce\",\"bouncePast\",\"easeFromTo\",\"easeFrom\",\"easeTo\"]", + "type": "stringWithSelector" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeInEasing", + "group": "Button color tint tween", + "name": "SetFadeInEasing", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "FadeInEasing", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the fade-out easing of the object.", + "fullName": "Fade-out easing", + "functionType": "ExpressionAndCondition", + "group": "Button color tint tween", + "name": "FadeOutEasing", + "sentence": "the fade-out easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "FadeOutEasing" + ] + } + ] + } + ], + "expressionType": { + "supplementaryInformation": "[\"linear\",\"easeInQuad\",\"easeOutQuad\",\"easeInOutQuad\",\"easeInCubic\",\"easeOutCubic\",\"easeInOutCubic\",\"easeInQuart\",\"easeOutQuart\",\"easeInOutQuart\",\"easeInQuint\",\"easeOutQuint\",\"easeInOutQuint\",\"easeInOutSine\",\"easeInExpo\",\"easeOutExpo\",\"easeInOutExpo\",\"easeInCirc\",\"easeOutCirc\",\"easeInOutCirc\",\"easeOutBounce\",\"easeInBack\",\"easeOutBack\",\"easeInOutBack\",\"elastic\",\"swingFromTo\",\"swingFrom\",\"swingTo\",\"bounce\",\"bouncePast\",\"easeFromTo\",\"easeFrom\",\"easeTo\"]", + "type": "stringWithSelector" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "FadeOutEasing", + "group": "Button color tint tween", + "name": "SetFadeOutEasing", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "FadeOutEasing", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ButtonStates::ButtonColorTintTween", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "FadeIn" + }, + { + "functionName": "FadeOut" + }, + { + "folderName": "Button color tint tween", + "children": [ + { + "functionName": "IdleColorTint" + }, + { + "functionName": "SetIdleColorTint" + }, + { + "functionName": "FocusedColorTint" + }, + { + "functionName": "SetFocusedColorTint" + }, + { + "functionName": "PressedColorTint" + }, + { + "functionName": "SetPressedColorTint" + }, + { + "functionName": "FadeInDuration" + }, + { + "functionName": "SetFadeInDuration" + }, + { + "functionName": "FadeOutDuration" + }, + { + "functionName": "SetFadeOutDuration" + }, + { + "functionName": "FadeInEasing" + }, + { + "functionName": "SetFadeInEasing" + }, + { + "functionName": "FadeOutEasing" + }, + { + "functionName": "SetFadeOutEasing" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Button states", + "extraInformation": [ + "ButtonStates::ButtonFSM" + ], + "choices": [], + "name": "ButtonFSM" + }, + { + "value": "", + "type": "Behavior", + "label": "Tween", + "extraInformation": [ + "Tween::TweenBehavior" + ], + "choices": [], + "name": "Tween" + }, + { + "value": "255;255;255", + "type": "Color", + "label": "Idle state color tint", + "group": "Color", + "name": "IdleColorTint" + }, + { + "value": "192;192;192", + "type": "Color", + "label": "Focused state color tint", + "description": "The state is Focused when the button is hovered or held outside.", + "group": "Color", + "name": "FocusedColorTint" + }, + { + "value": "64;64;64", + "type": "Color", + "label": "Pressed state color tint", + "group": "Color", + "name": "PressedColorTint" + }, + { + "value": "0.1", + "type": "Number", + "unit": "Second", + "label": "Fade-in duration", + "group": "Speed", + "name": "FadeInDuration" + }, + { + "value": "0.2", + "type": "Number", + "unit": "Second", + "label": "Fade-out duration", + "group": "Speed", + "name": "FadeOutDuration" + }, + { + "value": "easeInOutQuad", + "type": "Choice", + "label": "Fade-in easing", + "group": "Speed", + "choices": [ + { + "label": "linear", + "value": "linear" + }, + { + "label": "easeInQuad", + "value": "easeInQuad" + }, + { + "label": "easeOutQuad", + "value": "easeOutQuad" + }, + { + "label": "easeInOutQuad", + "value": "easeInOutQuad" + }, + { + "label": "easeInCubic", + "value": "easeInCubic" + }, + { + "label": "easeOutCubic", + "value": "easeOutCubic" + }, + { + "label": "easeInOutCubic", + "value": "easeInOutCubic" + }, + { + "label": "easeInQuart", + "value": "easeInQuart" + }, + { + "label": "easeOutQuart", + "value": "easeOutQuart" + }, + { + "label": "easeInOutQuart", + "value": "easeInOutQuart" + }, + { + "label": "easeInQuint", + "value": "easeInQuint" + }, + { + "label": "easeOutQuint", + "value": "easeOutQuint" + }, + { + "label": "easeInOutQuint", + "value": "easeInOutQuint" + }, + { + "label": "easeInOutSine", + "value": "easeInOutSine" + }, + { + "label": "easeInExpo", + "value": "easeInExpo" + }, + { + "label": "easeOutExpo", + "value": "easeOutExpo" + }, + { + "label": "easeInOutExpo", + "value": "easeInOutExpo" + }, + { + "label": "easeInCirc", + "value": "easeInCirc" + }, + { + "label": "easeOutCirc", + "value": "easeOutCirc" + }, + { + "label": "easeInOutCirc", + "value": "easeInOutCirc" + }, + { + "label": "easeOutBounce", + "value": "easeOutBounce" + }, + { + "label": "easeInBack", + "value": "easeInBack" + }, + { + "label": "easeOutBack", + "value": "easeOutBack" + }, + { + "label": "easeInOutBack", + "value": "easeInOutBack" + }, + { + "label": "elastic", + "value": "elastic" + }, + { + "label": "swingFromTo", + "value": "swingFromTo" + }, + { + "label": "swingFrom", + "value": "swingFrom" + }, + { + "label": "swingTo", + "value": "swingTo" + }, + { + "label": "bounce", + "value": "bounce" + }, + { + "label": "bouncePast", + "value": "bouncePast" + }, + { + "label": "easeFromTo", + "value": "easeFromTo" + }, + { + "label": "easeFrom", + "value": "easeFrom" + }, + { + "label": "easeTo", + "value": "easeTo" + } + ], + "name": "FadeInEasing" + }, + { + "value": "easeInOutQuad", + "type": "Choice", + "label": "Fade-out easing", + "group": "Speed", + "choices": [ + { + "label": "linear", + "value": "linear" + }, + { + "label": "easeInQuad", + "value": "easeInQuad" + }, + { + "label": "easeOutQuad", + "value": "easeOutQuad" + }, + { + "label": "easeInOutQuad", + "value": "easeInOutQuad" + }, + { + "label": "easeInCubic", + "value": "easeInCubic" + }, + { + "label": "easeOutCubic", + "value": "easeOutCubic" + }, + { + "label": "easeInOutCubic", + "value": "easeInOutCubic" + }, + { + "label": "easeInQuart", + "value": "easeInQuart" + }, + { + "label": "easeOutQuart", + "value": "easeOutQuart" + }, + { + "label": "easeInOutQuart", + "value": "easeInOutQuart" + }, + { + "label": "easeInQuint", + "value": "easeInQuint" + }, + { + "label": "easeOutQuint", + "value": "easeOutQuint" + }, + { + "label": "easeInOutQuint", + "value": "easeInOutQuint" + }, + { + "label": "easeInOutSine", + "value": "easeInOutSine" + }, + { + "label": "easeInExpo", + "value": "easeInExpo" + }, + { + "label": "easeOutExpo", + "value": "easeOutExpo" + }, + { + "label": "easeInOutExpo", + "value": "easeInOutExpo" + }, + { + "label": "easeInCirc", + "value": "easeInCirc" + }, + { + "label": "easeOutCirc", + "value": "easeOutCirc" + }, + { + "label": "easeInOutCirc", + "value": "easeInOutCirc" + }, + { + "label": "easeOutBounce", + "value": "easeOutBounce" + }, + { + "label": "easeInBack", + "value": "easeInBack" + }, + { + "label": "easeOutBack", + "value": "easeOutBack" + }, + { + "label": "easeInOutBack", + "value": "easeInOutBack" + }, + { + "label": "elastic", + "value": "elastic" + }, + { + "label": "swingFromTo", + "value": "swingFromTo" + }, + { + "label": "swingFrom", + "value": "swingFrom" + }, + { + "label": "swingTo", + "value": "swingTo" + }, + { + "label": "bounce", + "value": "bounce" + }, + { + "label": "bouncePast", + "value": "bouncePast" + }, + { + "label": "easeFromTo", + "value": "easeFromTo" + }, + { + "label": "easeFrom", + "value": "easeFrom" + }, + { + "label": "easeTo", + "value": "easeTo" + } + ], + "name": "FadeOutEasing" + }, + { + "value": "Idle", + "type": "Choice", + "label": "", + "choices": [ + { + "label": "Idle", + "value": "Idle" + }, + { + "label": "Focused", + "value": "Focused" + }, + { + "label": "Pressed", + "value": "Pressed" + } + ], + "hidden": true, + "name": "PreviousState" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "ButtonFSM" + }, + { + "propertyName": "Tween" + }, + { + "folderName": "Color", + "children": [ + { + "propertyName": "IdleColorTint" + }, + { + "propertyName": "FocusedColorTint" + }, + { + "propertyName": "PressedColorTint" + } + ] + }, + { + "folderName": "Speed", + "children": [ + { + "propertyName": "FadeInDuration" + }, + { + "propertyName": "FadeOutDuration" + }, + { + "propertyName": "FadeInEasing" + }, + { + "propertyName": "FadeOutEasing" + } + ] + }, + { + "propertyName": "PreviousState" + } + ] + } + } + ], + "eventsBasedObjects": [] + }, + { + "author": "", + "category": "General", + "dimension": "3D", + "extensionNamespace": "", + "fullName": "3D terrain", + "gdevelopVersion": ">=5.5.222", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXRlcnJhaW4iIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTQsNkwxMC4yNSwxMUwxMy4xLDE0LjhMMTEuNSwxNkM5LjgxLDEzLjc1IDcsMTAgNywxMEwxLDE4SDIzTDE0LDZaIiAvPjwvc3ZnPg==", + "name": "Terrain3D", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/10e0a26c0d500830dfe23b94138beb0ef61607ba62bca05a5aa5c849777f1b06_terrain.svg", + "shortDescription": "A ground with hills and valleys that can be sculpted and painted in the editor, with collisions.", + "version": "0.1.0", + "description": [ + "A 3D terrain object for open worlds, adventure or racing games: hills, mountains, islands or valleys, with painted layers of grass, dirt, rock or sand.", + "", + "- **Sculpt and paint in the scene editor**: select a terrain to show the tools (raise, lower, smooth, flatten and paint 4 layers).", + "- **Start from a relief** (hills, mountains, island) or **from a heightmap image**.", + "- **Steep slopes automatically show rock** (or any layer).", + "- **Natural borders between layers**: stones and tufts of a texture cover the gaps of the other where layers meet (height blending), and the repetition of textures is made less visible (texture variation).", + "- **Physics**: add the **3D physics** behavior to the terrain. Characters and objects collide with the ground exactly. The terrain is always static.", + "- **Pathfinding**: add the **floor/obstacle for pathfinding (navmesh based)** behavior to the terrain, so that characters with the navmesh pathfinding behavior walk on its hills and around its cliffs (in recent GDevelop versions).", + "- **Shape it with the Edits property**: a list of raise, flatten, smooth and paint strokes written in JSON, shown in the scene editor. It's readable and can be changed by hand or by an AI agent, like the relief, the heightmap and the other properties. Actions only change the terrain during the game.", + "- **Change it during the game** with actions (raise, lower, flatten, smooth, paint, along a line for paths and rivers) and read the ground height, slope or layer with expressions.", + "- **Grass**: add a **terrain grass** object over the terrain. Thousands of blades sway in the wind, grow where a layer (like grass) is painted and where the ground is not too steep, and are pushed aside by characters. Sculpt or paint the terrain and the grass follows.", + "- **Forests, rocks and bushes**: add a **scattered models** object over the terrain. It places hundreds or thousands of copies of a 3D model (trees, rocks, bushes, flowers...) on the ground and draws them all at once, which is much faster than an object for each. Copies grow where a layer is painted, away from steep slopes, and follow the ground when it's sculpted. Add or erase them with the brushes of the scene editor or with the Edits property, give trees trunk collisions, and cut them down with actions. Ready-made forests, rocks and bushes are in the asset store.", + "- **Tunnels and caves**: make holes in the ground (and fill them again) with the brushes, the Edits property or actions, and put a 3D model of the tunnel or the cave (with a mesh collision shape) under the ground. Nothing is drawn in holes, and characters, cars and rays go through them.", + "- **Made for large worlds**: the terrain is split in chunks that are hidden when off-screen and simplified when far away, and collisions are updated only where the ground changes." + ], + "origin": { + "identifier": "Terrain3D", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d", + "terrain", + "ground", + "heightmap", + "landscape", + "open world", + "grass", + "foliage", + "forest", + "trees", + "tunnel", + "cave" + ], + "authorIds": [], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [ + { + "fullName": "Define helper classes", + "functionType": "Action", + "name": "DefineHelperClasses", + "sentence": "Define helper classes JavaScript code", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (gdjs.__terrain3DExtension) {", + " return;", + "}", + "", + "// Tested by the gameplay tests of the \"3D island terrain\" example (GDevelop-examples repository).", + "", + "const groundsByScene = new WeakMap();", + "/**", + " * The grounds of a scene (the terrains), followed by the terrain grass.", + " *", + " * A ground has: `getVersion()` (changing when the ground changes),", + " * `containsPoint(x, y)`, `getHeightAt(x, y)` (Z position of the ground),", + " * `getSlopeAt(x, y)` (in degrees), `getLayerWeightAt(x, y, layer)` (how", + " * much a painted layer, from 1 to 4, is there, from 0 to 1) and optionally", + " * `getTintAt(x, y)` (the painted tint, as red, green and blue from 0 to 255).", + " * @param {gdjs.RuntimeScene} runtimeScene", + " * @returns {Set}", + " */", + "const getGrounds = (runtimeScene) => {", + " let grounds = groundsByScene.get(runtimeScene);", + " if (!grounds) {", + " grounds = new Set();", + " groundsByScene.set(runtimeScene, grounds);", + " }", + " return grounds;", + "};", + "", + "/**", + " * @param {number} seed", + " * @returns {() => number} Random numbers between 0 (included) and 1 (excluded), the same for the same seed.", + " */", + "const createRandom = (seed) => {", + " let state = seed >>> 0 || 1;", + " return () => {", + " state ^= state << 13;", + " state ^= state >>> 17;", + " state ^= state << 5;", + " return (state >>> 0) / 4294967296;", + " };", + "};", + "", + "/**", + " * Find the highest ground at a position: grounds can overlap (like an island over a sea floor).", + " * @param {Iterable} grounds", + " * @param {{ground: object | null, z: number}} result Set to the ground (null if there is none) and its Z position.", + " */", + "const findHighestGround = (grounds, x, y, result) => {", + " result.ground = null;", + " for (const ground of grounds) {", + " if (!ground.containsPoint(x, y)) continue;", + " const z = ground.getHeightAt(x, y);", + " if (!result.ground || z > result.z) {", + " result.ground = ground;", + " result.z = z;", + " }", + " }", + " return result;", + "};", + "", + "// The grounds or the objects placed on them can change every frame (while", + "// sculpting or moving them in the editor): things are placed again at most this often.", + "const PLACEMENT_DELAY = 0.3;", + "", + "/**", + " * Tells when things placed on the grounds of a scene (grass blades, scattered", + " * models) must be placed again: after the grounds or the given properties", + " * changed, at most every `PLACEMENT_DELAY` seconds.", + " */", + "class GroundPlacement {", + " /** @param {gdjs.RuntimeScene} runtimeScene */", + " constructor(runtimeScene) {", + " this.grounds = getGrounds(runtimeScene);", + " this.placedFrom = '';", + " this.timeSincePlacement = 0;", + " }", + "", + " /**", + " * @param {number} elapsedSeconds", + " * @param {string} propertiesKey Changes when the things must be placed again.", + " */", + " shouldPlaceAgain(elapsedSeconds, propertiesKey) {", + " this.timeSincePlacement += elapsedSeconds;", + " if (this.placedFrom && this.timeSincePlacement < PLACEMENT_DELAY) return false;", + " let placementKey = propertiesKey;", + " for (const ground of this.grounds) placementKey += '|' + ground.getVersion();", + " if (placementKey === this.placedFrom) return false;", + " this.placedFrom = placementKey;", + " this.timeSincePlacement = 0;", + " return true;", + " }", + "", + " placeAgain() {", + " this.placedFrom = '';", + " }", + "}", + "", + "/**", + " * Replace parts of the shader code of a Three.js material, with an error if", + " * a part is missing (for example after an update of Three.js).", + " * @param {string} shaderCode", + " * @param {Array<[string, string]>} replacements The searched code and its replacement.", + " * @param {string} materialName", + " */", + "const patchShaderCode = (shaderCode, replacements, materialName) => {", + " for (const [searchedCode, newCode] of replacements) {", + " if (!shaderCode.includes(searchedCode)) {", + " console.error(`${materialName}: \"${searchedCode}\" was not found in the shader, which won't look as expected.`);", + " }", + " shaderCode = shaderCode.replace(searchedCode, newCode);", + " }", + " return shaderCode;", + "};", + "", + "// Size of the object inner area. Instances are scaled from it, so these only", + "// define the default size of a new terrain.", + "const AREA_SIZE = 4096;", + "const AREA_DEPTH = 600;", + "", + "// Terrains have at most 8 x 8 chunks (so at most 64 draw calls), of at least", + "// 32 x 32 cells.", + "const MIN_CHUNK_CELLS = 32;", + "const MAX_CHUNKS_PER_SIDE = 8;", + "/** @param {number} resolution */", + "const getChunkCells = (resolution) => Math.max(MIN_CHUNK_CELLS, resolution / MAX_CHUNKS_PER_SIDE);", + "// Grid steps of each level of detail, and the distance (in chunk sizes)", + "// from which each one is used.", + "const LOD_STEPS = [1, 2, 4];", + "const LOD_DISTANCES = [0, 3, 6];", + "", + "const LAYER_COUNT = 4;", + "const RESOLUTIONS = [64, 128, 256, 512, 1024];", + "const DEFAULT_RESOLUTION = 256;", + "// Each undo step is a copy of the data: fewer are kept for big terrains.", + "const UNDO_STEPS_SAMPLES = 20 * 257 * 257;", + "", + "const clamp01 = (value) => (value < 0 ? 0 : value > 1 ? 1 : value);", + "const clampInteger = (value, min, max) =>", + " value < min ? min : value > max ? max : Math.floor(value);", + "", + "/**", + " * A rectangle of grid samples, inclusive. Empty when `minI > maxI`.", + " */", + "class SampleRectangle {", + " constructor() {", + " this.clear();", + " }", + "", + " clear() {", + " this.minI = Number.MAX_SAFE_INTEGER;", + " this.minJ = Number.MAX_SAFE_INTEGER;", + " this.maxI = -1;", + " this.maxJ = -1;", + " }", + "", + " isEmpty() {", + " return this.minI > this.maxI;", + " }", + "", + " /** @param {SampleRectangle} other */", + " add(other) {", + " if (other.isEmpty()) return;", + " this.minI = Math.min(this.minI, other.minI);", + " this.minJ = Math.min(this.minJ, other.minJ);", + " this.maxI = Math.max(this.maxI, other.maxI);", + " this.maxJ = Math.max(this.maxJ, other.maxJ);", + " }", + "", + " set(minI, minJ, maxI, maxJ) {", + " this.minI = minI;", + " this.minJ = minJ;", + " this.maxI = maxI;", + " this.maxJ = maxJ;", + " return this;", + " }", + "}", + "", + "/**", + " * The heights (between 0 and 1) and the painted layers weights of a square", + " * grid of `size * size` samples.", + " */", + "class TerrainData {", + " /** @param {number} resolution The number of cells on each side. */", + " constructor(resolution) {", + " this.resolution = resolution;", + " this.size = resolution + 1;", + " this.heights = new Float32Array(this.size * this.size);", + " /** 4 weights (0-255) per sample, one for each layer. */", + " this.splat = new Uint8Array(this.size * this.size * LAYER_COUNT);", + " this.fillLayer(0);", + " /**", + " * 4 bytes per sample: a tint multiplying the layers (red, green and", + " * blue, white for none) and the wetness (0 for dry).", + " */", + " this.colors = new Uint8Array(this.size * this.size * 4);", + " this.clearColors();", + " }", + "", + " clearColors() {", + " this.colors.fill(255);", + " for (let index = 3; index < this.colors.length; index += 4) {", + " this.colors[index] = 0;", + " }", + " }", + "", + " /** @param {number} layer */", + " fillLayer(layer) {", + " this.splat.fill(0);", + " for (let index = layer; index < this.splat.length; index += LAYER_COUNT) {", + " this.splat[index] = 255;", + " }", + " }", + "", + " getHeight(i, j) {", + " const last = this.resolution;", + " const clampedI = i < 0 ? 0 : i > last ? last : i;", + " const clampedJ = j < 0 ? 0 : j > last ? last : j;", + " return this.heights[clampedJ * this.size + clampedI];", + " }", + "", + " /**", + " * @param {number} gridX", + " * @param {number} gridY", + " * @returns {number} The height interpolated on the triangles that are drawn.", + " */", + " sampleHeight(gridX, gridY) {", + " const x = Math.min(Math.max(gridX, 0), this.resolution);", + " const y = Math.min(Math.max(gridY, 0), this.resolution);", + " const i = Math.min(Math.floor(x), this.resolution - 1);", + " const j = Math.min(Math.floor(y), this.resolution - 1);", + " const fx = x - i;", + " const fy = y - j;", + " const topRight = this.getHeight(i + 1, j);", + " const bottomLeft = this.getHeight(i, j + 1);", + " if (fx + fy <= 1) {", + " const topLeft = this.getHeight(i, j);", + " return topLeft + fx * (topRight - topLeft) + fy * (bottomLeft - topLeft);", + " }", + " const bottomRight = this.getHeight(i + 1, j + 1);", + " return (", + " bottomRight +", + " (1 - fx) * (bottomLeft - bottomRight) +", + " (1 - fy) * (topRight - bottomRight)", + " );", + " }", + "", + " /** @returns {number} The weight (0-1) of a layer, interpolated between samples. */", + " sampleLayerWeight(gridX, gridY, layer) {", + " const x = Math.min(Math.max(gridX, 0), this.resolution);", + " const y = Math.min(Math.max(gridY, 0), this.resolution);", + " const i = Math.min(Math.floor(x), this.resolution - 1);", + " const j = Math.min(Math.floor(y), this.resolution - 1);", + " const fx = x - i;", + " const fy = y - j;", + " const weight = (i, j) => this.splat[(j * this.size + i) * LAYER_COUNT + layer];", + " const top = weight(i, j) + fx * (weight(i + 1, j) - weight(i, j));", + " const bottom = weight(i, j + 1) + fx * (weight(i + 1, j + 1) - weight(i, j + 1));", + " return (top + fy * (bottom - top)) / 255;", + " }", + "", + " /**", + " * @param {number} channel 0, 1 and 2 for the tint, 3 for the wetness.", + " * @returns {number} The channel (0-1), interpolated between samples.", + " */", + " sampleColor(gridX, gridY, channel) {", + " const x = Math.min(Math.max(gridX, 0), this.resolution);", + " const y = Math.min(Math.max(gridY, 0), this.resolution);", + " const i = Math.min(Math.floor(x), this.resolution - 1);", + " const j = Math.min(Math.floor(y), this.resolution - 1);", + " const fx = x - i;", + " const fy = y - j;", + " const value = (i, j) => this.colors[(j * this.size + i) * 4 + channel];", + " const top = value(i, j) + fx * (value(i + 1, j) - value(i, j));", + " const bottom = value(i, j + 1) + fx * (value(i + 1, j + 1) - value(i, j + 1));", + " return (top + fy * (bottom - top)) / 255;", + " }", + "", + " /**", + " * @param {number} index The index of a sample.", + " * @returns {boolean} true if no layer is painted on the sample: it's a hole.", + " */", + " isHole(index) {", + " const offset = index * LAYER_COUNT;", + " return (this.splat[offset] | this.splat[offset + 1] | this.splat[offset + 2] | this.splat[offset + 3]) === 0;", + " }", + "", + " /**", + " * @returns {number} The sum of the layer weights (0-1), interpolated between samples: there is a", + " * hole where it's under 0.5, like what is drawn.", + " */", + " sampleSolidity(gridX, gridY) {", + " let solidity = 0;", + " for (let layer = 0; layer < LAYER_COUNT; layer++) solidity += this.sampleLayerWeight(gridX, gridY, layer);", + " return solidity;", + " }", + "", + " /** @returns {number} The index (from 0) of the layer painted the most. */", + " getDominantLayer(gridX, gridY) {", + " const i = clampInteger(Math.round(gridX), 0, this.resolution);", + " const j = clampInteger(Math.round(gridY), 0, this.resolution);", + " const offset = (j * this.size + i) * LAYER_COUNT;", + " let dominantLayer = 0;", + " for (let layer = 1; layer < LAYER_COUNT; layer++) {", + " if (this.splat[offset + layer] > this.splat[offset + dominantLayer]) {", + " dominantLayer = layer;", + " }", + " }", + " return dominantLayer;", + " }", + "", + " /** @param {TerrainData} other */", + " copyFrom(other) {", + " this.heights.set(other.heights);", + " this.splat.set(other.splat);", + " this.colors.set(other.colors);", + " }", + "", + " /**", + " * @param {number} resolution", + " * @returns {TerrainData} A copy of this data with another resolution.", + " */", + " resampled(resolution) {", + " const data = new TerrainData(resolution);", + " const ratio = this.resolution / resolution;", + " for (let j = 0; j < data.size; j++) {", + " for (let i = 0; i < data.size; i++) {", + " const index = j * data.size + i;", + " data.heights[index] = this.sampleHeight(i * ratio, j * ratio);", + " const sourceOffset =", + " (Math.round(j * ratio) * this.size + Math.round(i * ratio)) * LAYER_COUNT;", + " for (let layer = 0; layer < LAYER_COUNT; layer++) {", + " data.splat[index * LAYER_COUNT + layer] = this.splat[sourceOffset + layer];", + " }", + " for (let channel = 0; channel < 4; channel++) {", + " data.colors[index * 4 + channel] = this.colors[sourceOffset + channel];", + " }", + " }", + " }", + " return data;", + " }", + "}", + "", + "/**", + " * Brushes change the samples around a segment, with an effect going smoothly", + " * from full on the segment to none at `radius`. A point is a segment of", + " * length 0. Positions are in scene units relative to the terrain origin.", + " */", + "const brushes = {", + " /**", + " * @param {TerrainData} data", + " * @param {{cellWidth: number, cellHeight: number, ax: number, ay: number, bx: number, by: number, radius: number}} stroke", + " * @param {(sampleIndex: number, weight: number, t: number) => void} apply Called with the weight of", + " * the brush and the position (from 0 to 1) of the nearest point of the segment.", + " * @param {SampleRectangle} changedSamples", + " */", + " forEachSample(data, stroke, apply, changedSamples) {", + " const { cellWidth, cellHeight, ax, ay, bx, by, radius } = stroke;", + " const last = data.resolution;", + " const minI = clampInteger(Math.floor((Math.min(ax, bx) - radius) / cellWidth), 0, last);", + " const maxI = clampInteger(Math.ceil((Math.max(ax, bx) + radius) / cellWidth), 0, last);", + " const minJ = clampInteger(Math.floor((Math.min(ay, by) - radius) / cellHeight), 0, last);", + " const maxJ = clampInteger(Math.ceil((Math.max(ay, by) + radius) / cellHeight), 0, last);", + " changedSamples.set(minI, minJ, maxI, maxJ);", + " if (radius <= 0) return;", + "", + " const abX = bx - ax;", + " const abY = by - ay;", + " const abLengthSquared = abX * abX + abY * abY;", + " const radiusSquared = radius * radius;", + " for (let j = minJ; j <= maxJ; j++) {", + " const y = j * cellHeight;", + " for (let i = minI; i <= maxI; i++) {", + " const x = i * cellWidth;", + " const t =", + " abLengthSquared > 0", + " ? clamp01(((x - ax) * abX + (y - ay) * abY) / abLengthSquared)", + " : 0;", + " const dx = x - (ax + t * abX);", + " const dy = y - (ay + t * abY);", + " const distanceSquared = dx * dx + dy * dy;", + " if (distanceSquared >= radiusSquared) continue;", + " const falloff = 1 - distanceSquared / radiusSquared;", + " apply(j * data.size + i, falloff * falloff, t);", + " }", + " }", + " },", + "", + " raise(data, stroke, amount, changedSamples) {", + " const { heights } = data;", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index, weight) => {", + " heights[index] = clamp01(heights[index] + amount * weight);", + " },", + " changedSamples", + " );", + " },", + "", + " /**", + " * @param {number} targetHeight", + " * @param {number} [endTargetHeight] The target at the end of the segment, for a slope (like a road", + " * going up). The same target as at its start by default.", + " */", + " flatten(data, stroke, targetHeight, strength, changedSamples, endTargetHeight = targetHeight) {", + " const { heights } = data;", + " const startTarget = clamp01(targetHeight);", + " const endTarget = clamp01(endTargetHeight);", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index, weight, t) => {", + " const target = startTarget + (endTarget - startTarget) * t;", + " heights[index] += (target - heights[index]) * clamp01(weight * strength);", + " },", + " changedSamples", + " );", + " },", + "", + " smooth(data, stroke, strength, changedSamples) {", + " const { heights, size } = data;", + " const changes = [];", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index, weight) => {", + " const i = index % size;", + " const j = (index - i) / size;", + " let sum = 0;", + " for (let dj = -1; dj <= 1; dj++) {", + " for (let di = -1; di <= 1; di++) {", + " sum += data.getHeight(i + di, j + dj);", + " }", + " }", + " changes.push(index, heights[index] + (sum / 9 - heights[index]) * clamp01(weight * strength));", + " },", + " changedSamples", + " );", + " // Written after all averages are computed, so that the result", + " // doesn't depend on the order in which samples are visited.", + " for (let index = 0; index < changes.length; index += 2) {", + " heights[changes[index]] = changes[index + 1];", + " }", + " },", + "", + " paint(data, stroke, layer, strength, changedSamples) {", + " const { splat } = data;", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index, weight) => {", + " const amount = clamp01(weight * strength);", + " const offset = index * LAYER_COUNT;", + " for (let otherLayer = 0; otherLayer < LAYER_COUNT; otherLayer++) {", + " const target = otherLayer === layer ? 255 : 0;", + " const value = splat[offset + otherLayer];", + " // Rounded towards the target, so that light strokes still paint.", + " const change = (target - value) * amount;", + " splat[offset + otherLayer] = value + (change > 0 ? Math.ceil(change) : Math.floor(change));", + " }", + " },", + " changedSamples", + " );", + " },", + "", + " /** Remove all the layers, making a hole (`fill` or painting a layer fills it again). */", + " hole(data, stroke, changedSamples) {", + " const { splat } = data;", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index) => {", + " splat.fill(0, index * LAYER_COUNT, (index + 1) * LAYER_COUNT);", + " },", + " changedSamples", + " );", + " },", + "", + " /**", + " * Fill the holes in the radius with the layers of the ground around them:", + " * each pass fills the holes next to the ground with the average of their", + " * neighbors, from the edges of the holes inward.", + " */", + " fill(data, stroke, changedSamples) {", + " const { splat, size, resolution } = data;", + " /** @type {number[]} */", + " let holes = [];", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index) => {", + " if (data.isHole(index)) holes.push(index);", + " },", + " changedSamples", + " );", + " const filledWeights = new Float32Array(holes.length * LAYER_COUNT);", + " while (holes.length > 0) {", + " /** @type {number[]} */", + " const remainingHoles = [];", + " /** @type {number[]} */", + " const filledHoles = [];", + " for (const index of holes) {", + " const i = index % size;", + " const j = (index - i) / size;", + " const weights = filledWeights.subarray(filledHoles.length * LAYER_COUNT, (filledHoles.length + 1) * LAYER_COUNT);", + " weights.fill(0);", + " for (let neighborJ = Math.max(j - 1, 0); neighborJ <= Math.min(j + 1, resolution); neighborJ++) {", + " for (let neighborI = Math.max(i - 1, 0); neighborI <= Math.min(i + 1, resolution); neighborI++) {", + " const neighbor = neighborJ * size + neighborI;", + " if (data.isHole(neighbor)) continue;", + " for (let layer = 0; layer < LAYER_COUNT; layer++) weights[layer] += splat[neighbor * LAYER_COUNT + layer];", + " }", + " }", + " if (weights[0] + weights[1] + weights[2] + weights[3] > 0) filledHoles.push(index);", + " else remainingHoles.push(index);", + " }", + " if (filledHoles.length === 0) {", + " // No ground around (a hole bigger than the brush): the first layer is used.", + " for (const index of remainingHoles) splat[index * LAYER_COUNT] = 255;", + " break;", + " }", + " // Filled after the pass, so that the holes are filled from the edges inward.", + " filledHoles.forEach((index, filledIndex) => {", + " const weights = filledWeights.subarray(filledIndex * LAYER_COUNT, (filledIndex + 1) * LAYER_COUNT);", + " const total = weights[0] + weights[1] + weights[2] + weights[3];", + " for (let layer = 0; layer < LAYER_COUNT; layer++) {", + " splat[index * LAYER_COUNT + layer] = Math.round((weights[layer] / total) * 255);", + " }", + " });", + " holes = remainingHoles;", + " }", + " },", + "", + " /** @param {number[]} color The red, green and blue (0-255) of the tint: white removes it. */", + " tint(data, stroke, color, strength, changedSamples) {", + " brushes.paintColors(data, stroke, 0, color, strength, changedSamples);", + " },", + "", + " /** @param {number} wetness From 0 (dry) to 1. */", + " wet(data, stroke, wetness, strength, changedSamples) {", + " brushes.paintColors(data, stroke, 3, [Math.round(clamp01(wetness) * 255)], strength, changedSamples);", + " },", + "", + " /**", + " * Bring channels of the colors (see `TerrainData.colors`) towards targets.", + " * @param {number} firstChannel", + " * @param {number[]} targets From 0 to 255, for the channels from the first one.", + " */", + " paintColors(data, stroke, firstChannel, targets, strength, changedSamples) {", + " const { colors } = data;", + " brushes.forEachSample(", + " data,", + " stroke,", + " (index, weight) => {", + " const amount = clamp01(weight * strength);", + " for (let channel = 0; channel < targets.length; channel++) {", + " const offset = index * 4 + firstChannel + channel;", + " // Rounded towards the target, so that light strokes still paint.", + " const change = (targets[channel] - colors[offset]) * amount;", + " colors[offset] += change > 0 ? Math.ceil(change) : Math.floor(change);", + " }", + " },", + " changedSamples", + " );", + " },", + "};", + "", + "const relief = (() => {", + " const hash = (x, y, seed) => {", + " let h = Math.imul(x, 374761393) + Math.imul(y, 668265263) + Math.imul(seed, 982451653);", + " h = Math.imul(h ^ (h >>> 13), 1274126177);", + " return ((h ^ (h >>> 16)) >>> 0) / 4294967296;", + " };", + " const valueNoise = (x, y, seed) => {", + " const x0 = Math.floor(x);", + " const y0 = Math.floor(y);", + " const fx = x - x0;", + " const fy = y - y0;", + " const sx = fx * fx * (3 - 2 * fx);", + " const sy = fy * fy * (3 - 2 * fy);", + " const top = hash(x0, y0, seed) + sx * (hash(x0 + 1, y0, seed) - hash(x0, y0, seed));", + " const bottom =", + " hash(x0, y0 + 1, seed) + sx * (hash(x0 + 1, y0 + 1, seed) - hash(x0, y0 + 1, seed));", + " return top + sy * (bottom - top);", + " };", + " /** Fractal noise between 0 and 1. */", + " const fractalNoise = (x, y, seed) => {", + " let value = 0;", + " let amplitude = 0.5;", + " let frequency = 1;", + " for (let octave = 0; octave < 5; octave++) {", + " value += amplitude * valueNoise(x * frequency, y * frequency, seed + octave);", + " amplitude *= 0.5;", + " frequency *= 2;", + " }", + " return value / 0.96875;", + " };", + " const smoothstep = (edge0, edge1, x) => {", + " const t = clamp01((x - edge0) / (edge1 - edge0));", + " return t * t * (3 - 2 * t);", + " };", + " const reliefHeights = {", + " Flat: () => 0,", + " Hills: (u, v, seed) => 0.05 + 0.5 * Math.pow(fractalNoise(u * 3, v * 3, seed), 2),", + " Mountains: (u, v, seed) => {", + " const ridge = 1 - Math.abs(2 * fractalNoise(u * 2.5, v * 2.5, seed) - 1);", + " return 0.9 * Math.pow(ridge, 2.5);", + " },", + " Island: (u, v, seed) => {", + " const distanceToCenter = 2 * Math.hypot(u - 0.5, v - 0.5);", + " const coast = 0.2 * (fractalNoise(u * 4, v * 4, seed + 7) - 0.5);", + " const land = smoothstep(0.95, 0.55, distanceToCenter + coast);", + " return land * (0.12 + 0.6 * Math.pow(fractalNoise(u * 3, v * 3, seed), 1.5));", + " },", + " };", + " const beachLayer = 3;", + "", + " return {", + " /**", + " * @param {TerrainData} data", + " * @param {string} reliefName Flat, Hills, Mountains or Island.", + " * @param {number} seed", + " */", + " generate(data, reliefName, seed) {", + " const getHeight = reliefHeights[reliefName] || reliefHeights.Flat;", + " const integerSeed = Math.floor(seed) | 0;", + " data.fillLayer(0);", + " for (let j = 0; j < data.size; j++) {", + " for (let i = 0; i < data.size; i++) {", + " const index = j * data.size + i;", + " const height = clamp01(", + " getHeight(i / data.resolution, j / data.resolution, integerSeed)", + " );", + " data.heights[index] = height;", + " if (reliefName === 'Island') {", + " const sand = Math.round(255 * smoothstep(0.16, 0.1, height));", + " const offset = index * LAYER_COUNT;", + " data.splat[offset] = 255 - sand;", + " data.splat[offset + beachLayer] = sand;", + " }", + " }", + " }", + " },", + " };", + "})();", + "", + "/**", + " * Edits written in the Edits property: a JSON list of brush strokes, like", + " * `[{\"tool\": \"raise\", \"x\": 0.5, \"y\": 0.5, \"radius\": 0.2, \"height\": 0.3}]`,", + " * applied on the relief or the heightmap. Positions and radius are fractions", + " * of the terrain size, heights and Z positions fractions of its depth: they", + " * are the same for all instances, whatever their size. They are made to be", + " * read and written by people and by AI agents.", + " */", + "const edits = {", + " /**", + " * @param {TerrainData} data", + " * @param {string} text", + " */", + " apply(data, text) {", + " if (!text.trim()) return;", + " let editList;", + " try {", + " editList = JSON.parse(text);", + " } catch (error) {", + " console.warn('The edits of a terrain are not valid JSON: ' + error.message);", + " return;", + " }", + " if (!Array.isArray(editList)) {", + " console.warn('The edits of a terrain must be a JSON list (like [{\"tool\": \"raise\", ...}]).');", + " return;", + " }", + " const numberOr = (value, defaultValue) => (typeof value === 'number' && isFinite(value) ? value : defaultValue);", + " const changedSamples = new SampleRectangle();", + " for (const edit of editList) {", + " if (!edit || typeof edit !== 'object') continue;", + " const x = numberOr(edit.x, 0.5);", + " const y = numberOr(edit.y, 0.5);", + " const stroke = {", + " cellWidth: 1 / data.resolution,", + " cellHeight: 1 / data.resolution,", + " ax: x,", + " ay: y,", + " bx: numberOr(edit.toX, x),", + " by: numberOr(edit.toY, y),", + " radius: numberOr(edit.radius, 0.1),", + " };", + " const strength = clamp01(numberOr(edit.strength, 1));", + " if (edit.tool === 'raise') {", + " brushes.raise(data, stroke, numberOr(edit.height, 0.1), changedSamples);", + " } else if (edit.tool === 'flatten') {", + " const z = numberOr(edit.z, 0);", + " brushes.flatten(data, stroke, z, strength, changedSamples, numberOr(edit.toZ, z));", + " } else if (edit.tool === 'smooth') {", + " // Smoothing the same fraction of the terrain at any resolution.", + " const passes = Math.max(Math.round(((1 + 9 * strength) * data.resolution) / 256), 1);", + " for (let pass = 0; pass < passes; pass++) brushes.smooth(data, stroke, 1, changedSamples);", + " } else if (edit.tool === 'paint') {", + " const layer = clampInteger(numberOr(edit.layer, 1) - 1, 0, LAYER_COUNT - 1);", + " brushes.paint(data, stroke, layer, strength, changedSamples);", + " } else if (edit.tool === 'tint') {", + " const color = gdjs.rgbOrHexToRGBColor(typeof edit.color === 'string' ? edit.color : '255;255;255');", + " brushes.tint(data, stroke, color, strength, changedSamples);", + " } else if (edit.tool === 'wetness') {", + " brushes.wet(data, stroke, numberOr(edit.amount, 1), strength, changedSamples);", + " } else if (edit.tool === 'hole') {", + " brushes.hole(data, stroke, changedSamples);", + " } else if (edit.tool === 'fill') {", + " brushes.fill(data, stroke, changedSamples);", + " } else {", + " console.warn('Unknown tool in the edits of a terrain: \"' + edit.tool + '\" (use raise, flatten, smooth, paint, tint, wetness, hole or fill).');", + " }", + " }", + " },", + "};", + "", + "/**", + " * @param {gdjs.RuntimeGame} game", + " * @param {string} imageResourceName", + " * @returns {CanvasImageSource | null} The image, or null if it's not loaded (yet).", + " */", + "const getLoadedImage = (game, imageResourceName) => {", + " const imageManager = game.getImageManager();", + " if (typeof imageManager.getImageSource === 'function') return imageManager.getImageSource(imageResourceName);", + " // Older GDevelop versions: a 192 x 192 placeholder is given for images that are not loaded.", + " const texture = imageManager.getPIXITexture(imageResourceName);", + " return texture && texture !== imageManager.getInvalidPIXITexture() ? texture.baseTexture.getDrawableSource() : null;", + "};", + "", + "/**", + " * Loads heights from an image: black is the lowest, white the highest.", + " * Heights have 16 bits of precision when the red channel holds the most", + " * significant byte and the green one the least significant (a gray image", + " * works as expected too).", + " * @param {TerrainData} data", + " * @param {gdjs.RuntimeGame} game", + " * @param {string} imageResourceName", + " * @returns {boolean} true if the image could be read.", + " */", + "const loadHeightsFromImage = (data, game, imageResourceName) => {", + " const source = getLoadedImage(game, imageResourceName);", + " if (!source) return false;", + "", + " const canvas = document.createElement('canvas');", + " canvas.width = source.width;", + " canvas.height = source.height;", + " const context = canvas.getContext('2d', { willReadFrequently: true });", + " if (!context) return false;", + " context.drawImage(source, 0, 0);", + " const pixels = context.getImageData(0, 0, source.width, source.height).data;", + " // Pixels are decoded before being interpolated, to keep the 16 bits.", + " const pixelHeight = (x, y) => {", + " const offset = (y * source.width + x) * 4;", + " return (256 * pixels[offset] + pixels[offset + 1]) / 65535;", + " };", + " for (let j = 0; j < data.size; j++) {", + " const y = (j / data.resolution) * (source.height - 1);", + " const y0 = Math.floor(y);", + " const y1 = Math.min(y0 + 1, source.height - 1);", + " for (let i = 0; i < data.size; i++) {", + " const x = (i / data.resolution) * (source.width - 1);", + " const x0 = Math.floor(x);", + " const x1 = Math.min(x0 + 1, source.width - 1);", + " const top = pixelHeight(x0, y0) + (x - x0) * (pixelHeight(x1, y0) - pixelHeight(x0, y0));", + " const bottom = pixelHeight(x0, y1) + (x - x0) * (pixelHeight(x1, y1) - pixelHeight(x0, y1));", + " data.heights[j * data.size + i] = top + (y - y0) * (bottom - top);", + " }", + " }", + " return true;", + "};", + "", + "/**", + " * Stores the data in a string. Only the differences with the base data (from", + " * the relief or the heightmap) are stored: heights on 16 bits, layer weights", + " * on 8 bits, then (since format 2) the tint and wetness on 8 bits. Each value is predicted from its neighbors and the", + " * prediction errors are written as variable length integers, with runs of", + " * zeros collapsed. Untouched or smoothly changed areas take almost no space.", + " */", + "const codec = (() => {", + " const formatVersion = '2';", + " // Format 1 has no tint and wetness.", + " const readableFormatVersions = ['1', '2'];", + "", + " class ByteWriter {", + " constructor() {", + " this.bytes = new Uint8Array(1024);", + " this.length = 0;", + " }", + " writeVarUint(value) {", + " if (this.length + 5 > this.bytes.length) {", + " const bytes = new Uint8Array(this.bytes.length * 2);", + " bytes.set(this.bytes);", + " this.bytes = bytes;", + " }", + " while (value >= 0x80) {", + " this.bytes[this.length++] = (value & 0x7f) | 0x80;", + " value >>>= 7;", + " }", + " this.bytes[this.length++] = value;", + " }", + " }", + "", + " class ByteReader {", + " /** @param {Uint8Array} bytes */", + " constructor(bytes) {", + " this.bytes = bytes;", + " this.position = 0;", + " }", + " readVarUint() {", + " let value = 0;", + " let shift = 0;", + " let byte;", + " do {", + " if (this.position >= this.bytes.length) throw new Error('Truncated terrain data.');", + " byte = this.bytes[this.position++];", + " value += (byte & 0x7f) * Math.pow(2, shift);", + " shift += 7;", + " } while (byte & 0x80);", + " return value;", + " }", + " }", + "", + " /** Predicts a value from the left, top and top-left ones. */", + " const predict = (values, size, index) => {", + " const i = index % size;", + " if (index < size) return i === 0 ? 0 : values[index - 1];", + " if (i === 0) return values[index - size];", + " return values[index - 1] + values[index - size] - values[index - size - 1];", + " };", + "", + " /**", + " * @param {ByteWriter} writer", + " * @param {Int32Array} values A grid of `size * size` values.", + " */", + " const writeGrid = (writer, values, size) => {", + " let zeroRun = 0;", + " for (let index = 0; index < values.length; index++) {", + " const error = values[index] - predict(values, size, index);", + " if (error === 0) {", + " zeroRun++;", + " continue;", + " }", + " if (zeroRun > 0) {", + " writer.writeVarUint(0);", + " writer.writeVarUint(zeroRun);", + " zeroRun = 0;", + " }", + " // Zigzag: small negative and positive numbers get small codes.", + " writer.writeVarUint(error > 0 ? error * 2 : -error * 2 - 1);", + " }", + " if (zeroRun > 0) {", + " writer.writeVarUint(0);", + " writer.writeVarUint(zeroRun);", + " }", + " };", + "", + " /** @param {ByteReader} reader */", + " const readGrid = (reader, size) => {", + " const values = new Int32Array(size * size);", + " let zeroRun = 0;", + " for (let index = 0; index < values.length; index++) {", + " let error = 0;", + " if (zeroRun > 0) {", + " zeroRun--;", + " } else {", + " const code = reader.readVarUint();", + " if (code === 0) {", + " zeroRun = reader.readVarUint() - 1;", + " } else {", + " error = code % 2 === 0 ? code / 2 : -(code + 1) / 2;", + " }", + " }", + " values[index] = predict(values, size, index) + error;", + " }", + " return values;", + " };", + "", + " const toBase64 = (bytes, length) => {", + " let binary = '';", + " for (let offset = 0; offset < length; offset += 0x8000) {", + " binary += String.fromCharCode.apply(null, bytes.subarray(offset, Math.min(offset + 0x8000, length)));", + " }", + " return btoa(binary);", + " };", + "", + " const fromBase64 = (base64) => {", + " const binary = atob(base64);", + " const bytes = new Uint8Array(binary.length);", + " for (let index = 0; index < binary.length; index++) {", + " bytes[index] = binary.charCodeAt(index);", + " }", + " return bytes;", + " };", + "", + " return {", + " /**", + " * @param {TerrainData} data", + " * @param {TerrainData} baseData The data without changes, with the same resolution.", + " */", + " encode(data, baseData) {", + " const writer = new ByteWriter();", + " const values = new Int32Array(data.heights.length);", + " for (let index = 0; index < values.length; index++) {", + " values[index] = Math.round((data.heights[index] - baseData.heights[index]) * 65535);", + " }", + " writeGrid(writer, values, data.size);", + " for (let layer = 0; layer < LAYER_COUNT; layer++) {", + " for (let index = 0; index < values.length; index++) {", + " const offset = index * LAYER_COUNT + layer;", + " values[index] = data.splat[offset] - baseData.splat[offset];", + " }", + " writeGrid(writer, values, data.size);", + " }", + " // Not painted, these grids are zeros: a few bytes each.", + " for (let channel = 0; channel < 4; channel++) {", + " for (let index = 0; index < values.length; index++) {", + " const offset = index * 4 + channel;", + " values[index] = data.colors[offset] - baseData.colors[offset];", + " }", + " writeGrid(writer, values, data.size);", + " }", + " return formatVersion + ';' + data.resolution + ';' + toBase64(writer.bytes, writer.length);", + " },", + "", + " /**", + " * Store grids of integers in a string, like the terrain data. Used by other objects (the scattered models).", + " * @param {Int32Array[]} grids Grids of `size * size` values.", + " * @param {number} size", + " */", + " encodeGrids(grids, size) {", + " const writer = new ByteWriter();", + " for (const grid of grids) writeGrid(writer, grid, size);", + " return toBase64(writer.bytes, writer.length);", + " },", + "", + " /**", + " * @param {string} base64 A string from `encodeGrids`.", + " * @param {number} size", + " * @param {number} count The number of grids.", + " * @returns {Int32Array[]} The grids (an error is thrown if the string is not valid).", + " */", + " decodeGrids(base64, size, count) {", + " const reader = new ByteReader(fromBase64(base64));", + " return Array.from({ length: count }, () => readGrid(reader, size));", + " },", + "", + " /**", + " * @param {string} text", + " * @param {(resolution: number) => TerrainData} createBaseData", + " * @returns {TerrainData | null} null if the text is empty or not valid.", + " */", + " decode(text, createBaseData) {", + " if (!text) return null;", + " const parts = text.split(';');", + " const resolution = parseInt(parts[1], 10);", + " if (readableFormatVersions.indexOf(parts[0]) === -1 || RESOLUTIONS.indexOf(resolution) === -1) {", + " return null;", + " }", + " try {", + " const reader = new ByteReader(fromBase64(parts[2]));", + " const data = createBaseData(resolution);", + " const heightChanges = readGrid(reader, data.size);", + " for (let index = 0; index < heightChanges.length; index++) {", + " data.heights[index] = clamp01(data.heights[index] + heightChanges[index] / 65535);", + " }", + " for (let layer = 0; layer < LAYER_COUNT; layer++) {", + " const weightChanges = readGrid(reader, data.size);", + " for (let index = 0; index < weightChanges.length; index++) {", + " const offset = index * LAYER_COUNT + layer;", + " // The base may have changed since the data was saved.", + " data.splat[offset] = Math.min(Math.max(data.splat[offset] + weightChanges[index], 0), 255);", + " }", + " }", + " if (parts[0] !== '1') {", + " for (let channel = 0; channel < 4; channel++) {", + " const colorChanges = readGrid(reader, data.size);", + " for (let index = 0; index < colorChanges.length; index++) {", + " const offset = index * 4 + channel;", + " data.colors[offset] = Math.min(Math.max(data.colors[offset] + colorChanges[index], 0), 255);", + " }", + " }", + " }", + " return data;", + " } catch (error) {", + " console.error('Terrain data could not be read:', error);", + " return null;", + " }", + " },", + " };", + "})();", + "", + "const lodIndices = new Map();", + "/**", + " * The triangles of a chunk, shared by all chunks of the same size. Chunk", + " * borders have a skirt going down, hiding the gaps between chunks drawn", + " * with different levels of detail.", + " * @param {number} chunkCells", + " * @param {number} step", + " */", + "const getLodIndex = (chunkCells, step) => {", + " const key = chunkCells + ';' + step;", + " const existingIndex = lodIndices.get(key);", + " if (existingIndex) return existingIndex;", + "", + " const sideVertices = chunkCells + 1;", + " const gridVertices = sideVertices * sideVertices;", + " const indices = [];", + " const grid = (i, j) => j * sideVertices + i;", + " for (let j = 0; j < chunkCells; j += step) {", + " for (let i = 0; i < chunkCells; i += step) {", + " const topLeft = grid(i, j);", + " const topRight = grid(i + step, j);", + " const bottomLeft = grid(i, j + step);", + " const bottomRight = grid(i + step, j + step);", + " indices.push(topLeft, topRight, bottomLeft, topRight, bottomRight, bottomLeft);", + " }", + " }", + " const borders = [", + " (k) => grid(k, 0),", + " (k) => grid(k, chunkCells),", + " (k) => grid(0, k),", + " (k) => grid(chunkCells, k),", + " ];", + " borders.forEach((borderVertex, border) => {", + " const skirtVertex = (k) => gridVertices + border * sideVertices + k;", + " for (let k = 0; k < chunkCells; k += step) {", + " const a = borderVertex(k);", + " const b = borderVertex(k + step);", + " const skirtA = skirtVertex(k);", + " const skirtB = skirtVertex(k + step);", + " // Both faces, as a skirt can be seen from either side.", + " indices.push(a, b, skirtB, a, skirtB, skirtA, a, skirtB, b, a, skirtA, skirtB);", + " }", + " });", + " const index = new THREE.BufferAttribute(new Uint16Array(indices), 1);", + " lodIndices.set(key, index);", + " return index;", + "};", + "", + "const vertexShaderDeclarations = `", + "varying vec2 vTerrainLocalXY;", + "varying float vTerrainLocalZ;", + "uniform float terrainWorldScale;", + "varying vec2 vTerrainWorldXY;", + "varying float vTerrainUp;", + "`;", + "const vertexShaderCode = `", + "vTerrainLocalXY = position.xy;", + "vTerrainLocalZ = position.z;", + "vTerrainWorldXY = (modelMatrix * vec4(transformed, 1.0)).xy * terrainWorldScale;", + "vTerrainUp = normalize(transpose(inverse(mat3(modelMatrix))) * objectNormal).z;", + "`;", + "const fragmentShaderDeclarations = `", + "uniform sampler2D terrainSplatMap;", + "uniform vec2 terrainSplatUvScaleAndOffset;", + "uniform vec3 terrainLayerColors[4];", + "uniform sampler2D terrainLayerMap0;", + "uniform sampler2D terrainLayerMap1;", + "uniform sampler2D terrainLayerMap2;", + "uniform sampler2D terrainLayerMap3;", + "uniform sampler2D terrainLayerNormalMap0;", + "uniform sampler2D terrainLayerNormalMap1;", + "uniform sampler2D terrainLayerNormalMap2;", + "uniform sampler2D terrainLayerNormalMap3;", + "uniform float terrainHasNormalMaps;", + "uniform float terrainNormalMapStrength;", + "uniform float terrainTextureSize;", + "uniform float terrainHeightBlending;", + "uniform float terrainTextureVariation;", + "uniform vec4 terrainCliffLayer;", + "uniform vec2 terrainCliffCosines;", + "uniform vec4 terrainBrush;", + "uniform sampler2D terrainColorMap;", + "uniform vec3 terrainWetShore;", + "varying vec2 vTerrainLocalXY;", + "varying float vTerrainLocalZ;", + "varying vec2 vTerrainWorldXY;", + "varying float vTerrainUp;", + "", + "float terrainHash(vec2 p) {", + " return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);", + "}", + "// The rotation of the copies of textures used to make repetition less visible.", + "const mat2 terrainCopyRotation = mat2(0.8, -0.6, 0.6, 0.8);", + "/** A bump of a normal map (from -1 to 1), mixed with its rotated copy in patches. */", + "vec3 terrainSampleNormal(sampler2D normalMap, vec2 uv, vec2 uv2, float patches) {", + " vec3 bump = texture2D(normalMap, uv).xyz * 2.0 - 1.0;", + " if (patches <= 0.0) return bump;", + " vec3 copyBump = texture2D(normalMap, uv2).xyz * 2.0 - 1.0;", + " // The bumps of the copy are rotated back with the inverse rotation.", + " copyBump.xy = transpose(terrainCopyRotation) * copyBump.xy;", + " return mix(bump, copyBump, patches);", + "}", + "float terrainNoise(vec2 p) {", + " vec2 i = floor(p);", + " vec2 f = fract(p);", + " vec2 u = f * f * (3.0 - 2.0 * f);", + " return mix(", + " mix(terrainHash(i), terrainHash(i + vec2(1.0, 0.0)), u.x),", + " mix(terrainHash(i + vec2(0.0, 1.0)), terrainHash(i + vec2(1.0, 1.0)), u.x),", + " u.y", + " );", + "}", + "`;", + "// Holes are samples without any painted layer: the interpolated sum of the", + "// weights gives smooth edges. Only compiled for terrains having holes, as", + "// discard makes drawing slower on some GPUs.", + "const holeShaderCode = `", + "#ifdef TERRAIN_HOLES", + "if (dot(texture2D(terrainSplatMap, vTerrainLocalXY * terrainSplatUvScaleAndOffset.x + terrainSplatUvScaleAndOffset.y), vec4(1.0)) < 0.5) discard;", + "#endif", + "`;", + "// For the shadows: the depth of holes is not drawn.", + "const holeDepthShaderDeclarations = `", + "uniform sampler2D terrainSplatMap;", + "uniform vec2 terrainSplatUvScaleAndOffset;", + "varying vec2 vTerrainLocalXY;", + "`;", + "", + "const fragmentShaderCode = holeShaderCode + `", + "// Borders between layers are made irregular and sharper, to look painted.", + "vec2 terrainNoisePosition = vTerrainWorldXY / 24.0;", + "vec4 terrainJitter = vec4(", + " terrainNoise(terrainNoisePosition),", + " terrainNoise(terrainNoisePosition + 19.1),", + " terrainNoise(terrainNoisePosition + 37.7),", + " terrainNoise(terrainNoisePosition + 53.3)", + ");", + "// Layer weights are in a texture, so that painted details stay the same", + "// whatever the level of detail of the ground.", + "vec2 terrainSplatUv = vTerrainLocalXY * terrainSplatUvScaleAndOffset.x + terrainSplatUvScaleAndOffset.y;", + "vec4 terrainWeights = texture2D(terrainSplatMap, terrainSplatUv) * (0.7 + 0.6 * terrainJitter);", + "terrainWeights = terrainWeights * terrainWeights;", + "terrainWeights /= max(dot(terrainWeights, vec4(1.0)), 0.00001);", + "float terrainCliff = 1.0 - smoothstep(terrainCliffCosines.x, terrainCliffCosines.y, vTerrainUp);", + "terrainWeights = mix(terrainWeights, terrainCliffLayer, terrainCliff * dot(terrainCliffLayer, vec4(1.0)));", + "", + "vec2 terrainUv = vTerrainWorldXY / terrainTextureSize;", + "vec3 terrainTexture0 = texture2D(terrainLayerMap0, terrainUv).rgb;", + "vec3 terrainTexture1 = texture2D(terrainLayerMap1, terrainUv).rgb;", + "vec3 terrainTexture2 = texture2D(terrainLayerMap2, terrainUv).rgb;", + "vec3 terrainTexture3 = texture2D(terrainLayerMap3, terrainUv).rgb;", + "", + "// Less visible repetition: in irregular patches, textures are replaced by a", + "// rotated and shifted copy of themselves, and colors slowly vary.", + "float terrainPatches = 0.0;", + "vec2 terrainUv2 = terrainUv;", + "float terrainColorVariation = 1.0;", + "if (terrainTextureVariation > 0.0) {", + " // Up to half of the ground is covered by the copies, each patch showing", + " // only one of them (mixing them would make them blurry).", + " float terrainPatchThreshold = 1.0 - 0.5 * terrainTextureVariation;", + " terrainPatches = smoothstep(terrainPatchThreshold - 0.05, terrainPatchThreshold + 0.05, terrainNoise(terrainUv * 0.23 + 7.3));", + " terrainUv2 = terrainCopyRotation * terrainUv + vec2(0.31, 0.73);", + " terrainTexture0 = mix(terrainTexture0, texture2D(terrainLayerMap0, terrainUv2).rgb, terrainPatches);", + " terrainTexture1 = mix(terrainTexture1, texture2D(terrainLayerMap1, terrainUv2).rgb, terrainPatches);", + " terrainTexture2 = mix(terrainTexture2, texture2D(terrainLayerMap2, terrainUv2).rgb, terrainPatches);", + " terrainTexture3 = mix(terrainTexture3, texture2D(terrainLayerMap3, terrainUv2).rgb, terrainPatches);", + " terrainColorVariation += terrainTextureVariation * 0.3 *", + " (terrainNoise(terrainUv * 0.11) + 0.5 * terrainNoise(terrainUv * 0.37 + 3.1) - 0.75);", + "}", + "", + "// Height-based blending: the brightness of a texture, compared to its", + "// average, is used as its height. Where layers meet, the high parts (stones,", + "// tufts) cover the low parts (sand, gaps) instead of fading into each other.", + "if (terrainHeightBlending > 0.0) {", + " const vec3 terrainLuminance = vec3(0.299, 0.587, 0.114);", + " vec4 terrainHeights = vec4(", + " dot(terrainTexture0 - textureLod(terrainLayerMap0, vec2(0.5), 16.0).rgb, terrainLuminance),", + " dot(terrainTexture1 - textureLod(terrainLayerMap1, vec2(0.5), 16.0).rgb, terrainLuminance),", + " dot(terrainTexture2 - textureLod(terrainLayerMap2, vec2(0.5), 16.0).rgb, terrainLuminance),", + " dot(terrainTexture3 - textureLod(terrainLayerMap3, vec2(0.5), 16.0).rgb, terrainLuminance)", + " );", + " vec4 terrainHeightWeights = terrainWeights + terrainHeights;", + " float terrainTop = max(max(terrainHeightWeights.x, terrainHeightWeights.y), max(terrainHeightWeights.z, terrainHeightWeights.w));", + " // Only painted layers (and the cliff layer) can show.", + " vec4 terrainHeightBlend = max(terrainHeightWeights - terrainTop + 0.1, 0.0) * step(0.004, terrainWeights);", + " terrainHeightBlend /= max(dot(terrainHeightBlend, vec4(1.0)), 0.00001);", + " terrainWeights = mix(terrainWeights, terrainHeightBlend, terrainHeightBlending);", + "}", + "", + "vec3 terrainColor = terrainColorVariation * (", + " terrainWeights.x * terrainLayerColors[0] * terrainTexture0 +", + " terrainWeights.y * terrainLayerColors[1] * terrainTexture1 +", + " terrainWeights.z * terrainLayerColors[2] * terrainTexture2 +", + " terrainWeights.w * terrainLayerColors[3] * terrainTexture3);", + "", + "// The painted tint multiplies all layers. Wet ground is darker (and", + "// shinier, see the roughness).", + "vec4 terrainTintAndWetness = texture2D(terrainColorMap, terrainSplatUv);", + "float terrainWetness = max(", + " terrainTintAndWetness.a,", + " terrainWetShore.z * (1.0 - smoothstep(terrainWetShore.x, terrainWetShore.x + terrainWetShore.y, vTerrainLocalZ))", + ");", + "terrainColor *= terrainTintAndWetness.rgb * (1.0 - 0.4 * terrainWetness);", + "diffuseColor.rgb *= terrainColor;", + "", + "if (terrainBrush.w > 0.5) {", + " float brushDistance = distance(vTerrainWorldXY, terrainBrush.xy);", + " float ringWidth = max(terrainBrush.z * 0.04, 1.5);", + " float ring = 1.0 - smoothstep(0.0, ringWidth, abs(brushDistance - terrainBrush.z));", + " float disc = brushDistance < terrainBrush.z ? 0.12 : 0.0;", + " diffuseColor.rgb = mix(diffuseColor.rgb, vec3(1.0), max(ring * 0.85, disc));", + "}", + "`;", + "", + "const fragmentShaderRoughnessCode = `", + "roughnessFactor = mix(roughnessFactor, 0.3, terrainWetness);", + "`;", + "", + "const fragmentShaderNormalCode = `", + "if (terrainHasNormalMaps > 0.5) {", + " vec3 terrainMapNormal =", + " terrainWeights.x * terrainSampleNormal(terrainLayerNormalMap0, terrainUv, terrainUv2, terrainPatches) +", + " terrainWeights.y * terrainSampleNormal(terrainLayerNormalMap1, terrainUv, terrainUv2, terrainPatches) +", + " terrainWeights.z * terrainSampleNormal(terrainLayerNormalMap2, terrainUv, terrainUv2, terrainPatches) +", + " terrainWeights.w * terrainSampleNormal(terrainLayerNormalMap3, terrainUv, terrainUv2, terrainPatches);", + " terrainMapNormal.xy *= terrainNormalMapStrength;", + " // The X and Y of textures follow the X and Y of the 3D world, projected on", + " // the ground (the camera space of the normal is used).", + " vec3 terrainTangent = (viewMatrix * vec4(1.0, 0.0, 0.0, 0.0)).xyz;", + " terrainTangent -= normal * dot(normal, terrainTangent);", + " // A ground facing the X axis (a rotated terrain) uses the Y axis instead.", + " if (dot(terrainTangent, terrainTangent) < 0.000001) {", + " terrainTangent = (viewMatrix * vec4(0.0, 1.0, 0.0, 0.0)).xyz;", + " terrainTangent -= normal * dot(normal, terrainTangent);", + " }", + " terrainTangent = normalize(terrainTangent);", + " vec3 terrainBitangent = cross(normal, terrainTangent);", + " normal = normalize(mat3(terrainTangent, terrainBitangent, normal) * terrainMapNormal);", + "}", + "`;", + "", + "let whiteTexture = null;", + "const getWhiteTexture = () => {", + " if (!whiteTexture) {", + " whiteTexture = new THREE.DataTexture(new Uint8Array([255, 255, 255, 255]), 1, 1);", + " whiteTexture.needsUpdate = true;", + " }", + " return whiteTexture;", + "};", + "", + "let flatNormalTexture = null;", + "const getFlatNormalTexture = () => {", + " if (!flatNormalTexture) {", + " flatNormalTexture = new THREE.DataTexture(new Uint8Array([128, 128, 255, 255]), 1, 1);", + " flatNormalTexture.needsUpdate = true;", + " }", + " return flatNormalTexture;", + "};", + "", + "const layerTextures = new WeakMap();", + "const layerNormalMaps = new WeakMap();", + "/**", + " * Textures of the image manager have no mipmaps: they would shimmer when", + " * repeated in the distance.", + " * @param {gdjs.RuntimeGame} game", + " * @param {string} resourceName", + " * @param {boolean} isNormalMap Normal maps are directions, not colors.", + " */", + "const getLayerTexture = (game, resourceName, isNormalMap) => {", + " if (!resourceName) return isNormalMap ? getFlatNormalTexture() : getWhiteTexture();", + " const imageTexture = game.getImageManager().getThreeTexture(resourceName);", + " const textures = isNormalMap ? layerNormalMaps : layerTextures;", + " let texture = textures.get(imageTexture);", + " if (!texture) {", + " texture = imageTexture.clone();", + " if (isNormalMap) texture.colorSpace = THREE.NoColorSpace;", + " texture.wrapS = THREE.RepeatWrapping;", + " texture.wrapT = THREE.RepeatWrapping;", + " texture.minFilter = THREE.LinearMipmapLinearFilter;", + " texture.generateMipmaps = true;", + " texture.anisotropy = 4;", + " texture.needsUpdate = true;", + " textures.set(imageTexture, texture);", + " }", + " return texture;", + "};", + "", + "/**", + " * A texture of 4 bytes per sample of a grid of `size * size` samples (layer", + " * weights, tint and wetness...), interpolated between samples.", + " * @param {Uint8Array} bytes", + " * @param {number} size", + " */", + "const createGridTexture = (bytes, size) => {", + " const texture = new THREE.DataTexture(bytes, size, size);", + " texture.magFilter = THREE.LinearFilter;", + " texture.minFilter = THREE.LinearFilter;", + " texture.userData.isUploaded = false;", + " texture.onUpdate = () => {", + " texture.userData.isUploaded = true;", + " };", + " return texture;", + "};", + "", + "/**", + " * Send the rows of the changed samples to the GPU, or the whole texture", + " * before its first upload (and in older GDevelop versions).", + " * @param {THREE.DataTexture} texture", + " * @param {SampleRectangle} samples", + " */", + "const uploadGridTextureRows = (texture, samples) => {", + " if (texture.userData.isUploaded && typeof texture.addUpdateRange === 'function') {", + " const size = texture.image.width;", + " for (let j = samples.minJ; j <= samples.maxJ; j++) {", + " texture.addUpdateRange((j * size + samples.minI) * 4, (samples.maxI - samples.minI + 1) * 4);", + " }", + " }", + " texture.needsUpdate = true;", + "};", + "", + "class TerrainRenderer {", + " /** @param {Terrain} terrain */", + " constructor(terrain) {", + " this.terrain = terrain;", + " this.group = new THREE.Group();", + " terrain.object.get3DRendererObject().add(this.group);", + " /** @type {Array<{mesh: THREE.Mesh, geometry: THREE.BufferGeometry, firstI: number, firstJ: number, center: THREE.Vector3, lod: number}>} */", + " this.chunks = [];", + " this.chunksPerSide = 0;", + " this.chunkCells = 0;", + " this.chunkSideVertices = 0;", + " this.chunkGridVertices = 0;", + " this.chunkVertices = 0;", + " /** @type {THREE.DataTexture | null} */", + " this.splatTexture = null;", + " /** @type {THREE.DataTexture | null} */", + " this.colorTexture = null;", + " this.uniforms = {", + " terrainSplatMap: { value: getWhiteTexture() },", + " terrainSplatUvScaleAndOffset: { value: new THREE.Vector2() },", + " terrainLayerColors: { value: [new THREE.Color(), new THREE.Color(), new THREE.Color(), new THREE.Color()] },", + " terrainLayerMap0: { value: getWhiteTexture() },", + " terrainLayerMap1: { value: getWhiteTexture() },", + " terrainLayerMap2: { value: getWhiteTexture() },", + " terrainLayerMap3: { value: getWhiteTexture() },", + " terrainLayerNormalMap0: { value: getFlatNormalTexture() },", + " terrainLayerNormalMap1: { value: getFlatNormalTexture() },", + " terrainLayerNormalMap2: { value: getFlatNormalTexture() },", + " terrainLayerNormalMap3: { value: getFlatNormalTexture() },", + " terrainHasNormalMaps: { value: 0 },", + " terrainNormalMapStrength: { value: 1 },", + " terrainTextureSize: { value: 256 },", + " terrainHeightBlending: { value: 0 },", + " terrainTextureVariation: { value: 0 },", + " terrainCliffLayer: { value: new THREE.Vector4() },", + " terrainCliffCosines: { value: new THREE.Vector2() },", + " terrainBrush: { value: new THREE.Vector4() },", + " terrainColorMap: { value: getWhiteTexture() },", + " // Z (in the terrain group) under which the ground is wet, fade height, and 1 if it's used.", + " terrainWetShore: { value: new THREE.Vector3() },", + " // Scene units by 3D world unit.", + " terrainWorldScale: { value: 1 },", + " };", + " this.material = new THREE.MeshStandardMaterial({ roughness: 1, metalness: 0 });", + " this.material.onBeforeCompile = (shader) => {", + " Object.assign(shader.uniforms, this.uniforms);", + " shader.vertexShader = patchShaderCode(shader.vertexShader, [", + " ['#include ', '#include \\n' + vertexShaderDeclarations],", + " ['#include ', '#include \\n' + vertexShaderCode],", + " ], 'Terrain3D');", + " shader.fragmentShader = patchShaderCode(shader.fragmentShader, [", + " ['#include ', '#include \\n' + fragmentShaderDeclarations],", + " ['#include ', fragmentShaderCode],", + " ['#include ', '#include \\n' + fragmentShaderRoughnessCode],", + " ['#include ', '#include \\n' + fragmentShaderNormalCode],", + " ], 'Terrain3D');", + " };", + " this.material.customProgramCacheKey = () => (this.hasHoles ? 'Terrain3DWithHoles' : 'Terrain3D');", + " this.hasHoles = false;", + " // Without them, holes would cast shadows.", + " this.depthMaterial = this._createHoleDepthMaterial(new THREE.MeshDepthMaterial());", + " this.distanceMaterial = this._createHoleDepthMaterial(new THREE.MeshDistanceMaterial());", + " this._cameraPosition = new THREE.Vector3();", + " this._chunkPosition = new THREE.Vector3();", + " this._scale = new THREE.Vector3();", + " }", + "", + " /**", + " * @param {THREE.MeshDepthMaterial | THREE.MeshDistanceMaterial} material", + " * @returns The material, drawing nothing in holes.", + " */", + " _createHoleDepthMaterial(material) {", + " material.defines = { TERRAIN_HOLES: '' };", + " material.onBeforeCompile = (shader) => {", + " Object.assign(shader.uniforms, this.uniforms);", + " shader.vertexShader = patchShaderCode(shader.vertexShader, [", + " ['#include ', '#include \\nvarying vec2 vTerrainLocalXY;'],", + " ['#include ', '#include \\nvTerrainLocalXY = position.xy;'],", + " ], 'Terrain3D shadows');", + " shader.fragmentShader = patchShaderCode(shader.fragmentShader, [", + " ['#include ', '#include \\n' + holeDepthShaderDeclarations],", + " ['#include ', holeShaderCode + '#include '],", + " ], 'Terrain3D shadows');", + " };", + " material.customProgramCacheKey = () => 'Terrain3DHoles';", + " return material;", + " }", + "", + " /**", + " * Use the shaders drawing holes, or the faster ones without them.", + " * @param {boolean} hasHoles", + " */", + " setHasHoles(hasHoles) {", + " if (hasHoles === this.hasHoles) return;", + " this.hasHoles = hasHoles;", + " // The material has its own defines (STANDARD).", + " if (hasHoles) this.material.defines.TERRAIN_HOLES = '';", + " else delete this.material.defines.TERRAIN_HOLES;", + " this.material.needsUpdate = true;", + " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", + " }", + "", + " /** @param {THREE.Mesh} mesh */", + " _setChunkShadowMaterials(mesh) {", + " mesh.customDepthMaterial = this.hasHoles ? this.depthMaterial : undefined;", + " mesh.customDistanceMaterial = this.hasHoles ? this.distanceMaterial : undefined;", + " }", + "", + " /** Recreate all the chunks, for example after the resolution changed. */", + " rebuild() {", + " this.disposeChunks();", + " const { data } = this.terrain;", + " this.chunkCells = getChunkCells(data.resolution);", + " this.chunkSideVertices = this.chunkCells + 1;", + " this.chunkGridVertices = this.chunkSideVertices * this.chunkSideVertices;", + " this.chunkVertices = this.chunkGridVertices + 4 * this.chunkSideVertices;", + " this.chunksPerSide = data.resolution / this.chunkCells;", + " const cellSize = AREA_SIZE / data.resolution;", + " // Sample centers are at the texel centers.", + " this.splatTexture = createGridTexture(data.splat, data.size);", + " this.uniforms.terrainSplatMap.value = this.splatTexture;", + " this.colorTexture = createGridTexture(data.colors, data.size);", + " // Tints are colors chosen like the layer colors.", + " this.colorTexture.colorSpace = THREE.SRGBColorSpace;", + " this.uniforms.terrainColorMap.value = this.colorTexture;", + " this.uniforms.terrainSplatUvScaleAndOffset.value.set(1 / (cellSize * data.size), 0.5 / data.size);", + " for (let chunkJ = 0; chunkJ < this.chunksPerSide; chunkJ++) {", + " for (let chunkI = 0; chunkI < this.chunksPerSide; chunkI++) {", + " const firstI = chunkI * this.chunkCells;", + " const firstJ = chunkJ * this.chunkCells;", + " const positions = new Float32Array(this.chunkVertices * 3);", + " for (let j = 0; j < this.chunkSideVertices; j++) {", + " for (let i = 0; i < this.chunkSideVertices; i++) {", + " const vertex = j * this.chunkSideVertices + i;", + " positions[vertex * 3] = (firstI + i) * cellSize;", + " positions[vertex * 3 + 1] = (firstJ + j) * cellSize;", + " }", + " }", + " // Skirt vertices are below border vertices.", + " this._forEachSkirtVertex((skirtVertex, borderVertex) => {", + " positions[skirtVertex * 3] = positions[borderVertex * 3];", + " positions[skirtVertex * 3 + 1] = positions[borderVertex * 3 + 1];", + " });", + " const geometry = new THREE.BufferGeometry();", + " geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));", + " geometry.setAttribute('normal', new THREE.BufferAttribute(new Float32Array(this.chunkVertices * 3), 3));", + " geometry.setIndex(getLodIndex(this.chunkCells, LOD_STEPS[0]));", + " geometry.boundingBox = new THREE.Box3();", + " geometry.boundingSphere = new THREE.Sphere();", + " const mesh = new THREE.Mesh(geometry, this.material);", + " mesh.matrixAutoUpdate = false;", + " // Rays hit the ground at its full resolution, whatever the level of", + " // detail drawn, and go through holes.", + " const chunkCells = this.chunkCells;", + " const terrainData = () => this.terrain.data;", + " const localPoint = new THREE.Vector3();", + " mesh.raycast = function (raycaster, intersects) {", + " const drawnIndex = geometry.index;", + " const firstIntersection = intersects.length;", + " geometry.setIndex(getLodIndex(chunkCells, LOD_STEPS[0]));", + " THREE.Mesh.prototype.raycast.call(this, raycaster, intersects);", + " geometry.setIndex(drawnIndex);", + " const data = terrainData();", + " const cellSize = AREA_SIZE / data.resolution;", + " for (let index = intersects.length - 1; index >= firstIntersection; index--) {", + " this.worldToLocal(localPoint.copy(intersects[index].point));", + " if (data.sampleSolidity(localPoint.x / cellSize, localPoint.y / cellSize) < 0.5) {", + " intersects.splice(index, 1);", + " }", + " }", + " };", + " this._setChunkShadowMaterials(mesh);", + " this.group.add(mesh);", + " this.chunks.push({", + " mesh,", + " geometry,", + " firstI,", + " firstJ,", + " center: new THREE.Vector3(", + " (firstI + this.chunkCells / 2) * cellSize,", + " (firstJ + this.chunkCells / 2) * cellSize,", + " 0", + " ),", + " lod: 0,", + " });", + " }", + " }", + " this.updateSamples(new SampleRectangle().set(0, 0, data.resolution, data.resolution), true);", + " this.updateMaterial();", + " }", + "", + " _forEachSkirtVertex(callback) {", + " for (let k = 0; k < this.chunkSideVertices; k++) {", + " callback(this.chunkGridVertices + k, k);", + " callback(this.chunkGridVertices + this.chunkSideVertices + k, this.chunkCells * this.chunkSideVertices + k);", + " callback(this.chunkGridVertices + 2 * this.chunkSideVertices + k, k * this.chunkSideVertices);", + " callback(", + " this.chunkGridVertices + 3 * this.chunkSideVertices + k,", + " k * this.chunkSideVertices + this.chunkCells", + " );", + " }", + " }", + "", + " /**", + " * Skirts go down only to the lowest point a coarser neighbor chunk can", + " * draw along the border, so that they can't be seen through holes.", + " * @param {Float32Array} positions", + " */", + " _updateSkirtHeights(positions) {", + " const coarsestStep = LOD_STEPS[LOD_STEPS.length - 1];", + " const borderVertices = [", + " (k) => k,", + " (k) => this.chunkCells * this.chunkSideVertices + k,", + " (k) => k * this.chunkSideVertices,", + " (k) => k * this.chunkSideVertices + this.chunkCells,", + " ];", + " borderVertices.forEach((borderVertex, border) => {", + " for (let k = 0; k < this.chunkSideVertices; k++) {", + " let minZ = Infinity;", + " const from = Math.max(k - coarsestStep, 0);", + " const to = Math.min(k + coarsestStep, this.chunkCells);", + " for (let other = from; other <= to; other++) {", + " minZ = Math.min(minZ, positions[borderVertex(other) * 3 + 2]);", + " }", + " const skirtVertex = this.chunkGridVertices + border * this.chunkSideVertices + k;", + " positions[skirtVertex * 3 + 2] = minZ;", + " }", + " });", + " }", + "", + " /**", + " * Update the vertices of the chunks sharing the given samples.", + " * @param {SampleRectangle} samples", + " * @param {boolean} hasPaintChanged", + " */", + " updateSamples(samples, hasPaintChanged) {", + " if (samples.isEmpty()) return;", + " const { data } = this.terrain;", + " const cellSize = AREA_SIZE / data.resolution;", + " const slopeFactor = AREA_DEPTH / cellSize;", + " // Normals of samples next to changed ones change too.", + " const minI = Math.max(samples.minI - 1, 0);", + " const minJ = Math.max(samples.minJ - 1, 0);", + " const maxI = Math.min(samples.maxI + 1, data.resolution);", + " const maxJ = Math.min(samples.maxJ + 1, data.resolution);", + " const firstChunkI = Math.max(Math.ceil(minI / this.chunkCells) - 1, 0);", + " const firstChunkJ = Math.max(Math.ceil(minJ / this.chunkCells) - 1, 0);", + " const lastChunkI = Math.min(Math.floor(maxI / this.chunkCells), this.chunksPerSide - 1);", + " const lastChunkJ = Math.min(Math.floor(maxJ / this.chunkCells), this.chunksPerSide - 1);", + "", + " for (let chunkJ = firstChunkJ; chunkJ <= lastChunkJ; chunkJ++) {", + " for (let chunkI = firstChunkI; chunkI <= lastChunkI; chunkI++) {", + " const chunk = this.chunks[chunkJ * this.chunksPerSide + chunkI];", + " const positions = chunk.geometry.attributes.position.array;", + " const normals = chunk.geometry.attributes.normal.array;", + " const fromI = Math.max(minI - chunk.firstI, 0);", + " const toI = Math.min(maxI - chunk.firstI, this.chunkCells);", + " const fromJ = Math.max(minJ - chunk.firstJ, 0);", + " const toJ = Math.min(maxJ - chunk.firstJ, this.chunkCells);", + " for (let j = fromJ; j <= toJ; j++) {", + " const gridJ = chunk.firstJ + j;", + " for (let i = fromI; i <= toI; i++) {", + " const gridI = chunk.firstI + i;", + " const vertex = j * this.chunkSideVertices + i;", + " const sample = gridJ * data.size + gridI;", + " positions[vertex * 3 + 2] = data.heights[sample] * AREA_DEPTH;", + "", + " const leftI = Math.max(gridI - 1, 0);", + " const rightI = Math.min(gridI + 1, data.resolution);", + " const upJ = Math.max(gridJ - 1, 0);", + " const downJ = Math.min(gridJ + 1, data.resolution);", + " const nx =", + " ((data.getHeight(leftI, gridJ) - data.getHeight(rightI, gridJ)) * slopeFactor) /", + " (rightI - leftI);", + " const ny =", + " ((data.getHeight(gridI, upJ) - data.getHeight(gridI, downJ)) * slopeFactor) /", + " (downJ - upJ);", + " const inverseLength = 1 / Math.sqrt(nx * nx + ny * ny + 1);", + " normals[vertex * 3] = nx * inverseLength;", + " normals[vertex * 3 + 1] = ny * inverseLength;", + " normals[vertex * 3 + 2] = inverseLength;", + " }", + " }", + " this._forEachSkirtVertex((skirtVertex, borderVertex) => {", + " for (let axis = 0; axis < 3; axis++) {", + " normals[skirtVertex * 3 + axis] = normals[borderVertex * 3 + axis];", + " }", + " });", + " this._updateSkirtHeights(positions);", + " let maxZ = 0;", + " for (let vertex = 0; vertex < this.chunkGridVertices; vertex++) {", + " maxZ = Math.max(maxZ, positions[vertex * 3 + 2]);", + " }", + " const chunkSize = this.chunkCells * cellSize;", + " chunk.geometry.boundingBox.min.set(chunk.firstI * cellSize, chunk.firstJ * cellSize, 0);", + " chunk.geometry.boundingBox.max.set(", + " chunk.firstI * cellSize + chunkSize,", + " chunk.firstJ * cellSize + chunkSize,", + " maxZ", + " );", + " chunk.geometry.boundingBox.getBoundingSphere(chunk.geometry.boundingSphere);", + " chunk.center.z = maxZ / 2;", + " chunk.geometry.attributes.position.needsUpdate = true;", + " chunk.geometry.attributes.normal.needsUpdate = true;", + " }", + " }", + " if (hasPaintChanged) {", + " // The data may have been replaced by one with the same resolution.", + " this.splatTexture.image.data = data.splat;", + " uploadGridTextureRows(this.splatTexture, samples);", + " this.colorTexture.image.data = data.colors;", + " uploadGridTextureRows(this.colorTexture, samples);", + " }", + " }", + "", + " updateMaterial() {", + " const { object } = this.terrain;", + " const game = object.getRuntimeScene().getGame();", + " const colors = [object._getLayer1Color(), object._getLayer2Color(), object._getLayer3Color(), object._getLayer4Color()];", + " const textures = [", + " object._getLayer1Texture(),", + " object._getLayer2Texture(),", + " object._getLayer3Texture(),", + " object._getLayer4Texture(),", + " ];", + " const normalMaps = [", + " object._getLayer1NormalMap(),", + " object._getLayer2NormalMap(),", + " object._getLayer3NormalMap(),", + " object._getLayer4NormalMap(),", + " ];", + " for (let layer = 0; layer < LAYER_COUNT; layer++) {", + " this.uniforms.terrainLayerColors.value[layer].set(gdjs.rgbOrHexStringToNumber(colors[layer]));", + " this.uniforms['terrainLayerMap' + layer].value = getLayerTexture(game, textures[layer], false);", + " this.uniforms['terrainLayerNormalMap' + layer].value = getLayerTexture(game, normalMaps[layer], true);", + " }", + " this.uniforms.terrainHasNormalMaps.value = normalMaps.some((normalMap) => !!normalMap) ? 1 : 0;", + " this.uniforms.terrainNormalMapStrength.value = Math.max(object._getNormalMapStrength(), 0);", + " this.uniforms.terrainTextureSize.value = Math.max(object._getTextureSize(), 1);", + " this.uniforms.terrainHeightBlending.value = clamp01(object._getHeightBlending());", + " this.uniforms.terrainTextureVariation.value = clamp01(object._getTextureVariation());", + "", + " const cliffLayer = parseInt(object._getCliffLayer(), 10);", + " const cliffAngle = gdjs.toRad(object._getCliffAngle());", + " const cliffBlendAngle = gdjs.toRad(4);", + " this.uniforms.terrainCliffLayer.value.set(", + " cliffLayer === 1 ? 1 : 0,", + " cliffLayer === 2 ? 1 : 0,", + " cliffLayer === 3 ? 1 : 0,", + " cliffLayer === 4 ? 1 : 0", + " );", + " this.uniforms.terrainCliffCosines.value.set(", + " Math.cos(cliffAngle + cliffBlendAngle),", + " Math.cos(cliffAngle - cliffBlendAngle)", + " );", + "", + " const castShadow = object._getCastShadow();", + " const receiveShadow = object._getReceiveShadow();", + " for (const chunk of this.chunks) {", + " chunk.mesh.castShadow = castShadow;", + " chunk.mesh.receiveShadow = receiveShadow;", + " }", + " }", + "", + " /**", + " * Show the brush circle of the editor.", + " * @param {THREE.Vector3 | null} localPosition", + " * @param {number} radius In scene units.", + " */", + " setBrush(localPosition, radius) {", + " const brush = this.uniforms.terrainBrush.value;", + " if (!localPosition) {", + " brush.w = 0;", + " return;", + " }", + " this.group.updateWorldMatrix(true, false);", + " const worldPosition = this._chunkPosition.copy(localPosition).applyMatrix4(this.group.matrixWorld);", + " const worldScale = this.uniforms.terrainWorldScale.value;", + " brush.set(worldPosition.x * worldScale, worldPosition.y * worldScale, radius, 1);", + " }", + "", + " /** @param {THREE.Camera | null} camera */", + " updateLevelsOfDetail(camera) {", + " if (!camera || !this.group.visible) return;", + " const matrixWorld = this.group.matrixWorld;", + " this._cameraPosition.setFromMatrixPosition(camera.matrixWorld);", + " this._scale.setFromMatrixScale(matrixWorld);", + " const cellSize = AREA_SIZE / this.terrain.data.resolution;", + " const chunkWorldSize = this.chunkCells * cellSize * Math.max(this._scale.x, this._scale.y);", + " for (const chunk of this.chunks) {", + " const distance =", + " this._chunkPosition.copy(chunk.center).applyMatrix4(matrixWorld).distanceTo(this._cameraPosition) /", + " chunkWorldSize;", + " let lod = LOD_DISTANCES.length - 1;", + " while (lod > 0 && distance < LOD_DISTANCES[lod]) lod--;", + " if (lod !== chunk.lod) {", + " chunk.lod = lod;", + " chunk.geometry.setIndex(getLodIndex(this.chunkCells, LOD_STEPS[lod]));", + " }", + " }", + " }", + "", + " disposeChunks() {", + " for (const chunk of this.chunks) {", + " chunk.mesh.removeFromParent();", + " // The index is shared by all chunks: it must stay on the GPU.", + " chunk.geometry.setIndex(null);", + " chunk.geometry.dispose();", + " }", + " this.chunks.length = 0;", + " if (this.splatTexture) this.splatTexture.dispose();", + " this.splatTexture = null;", + " if (this.colorTexture) this.colorTexture.dispose();", + " this.colorTexture = null;", + " }", + "", + " dispose() {", + " this.disposeChunks();", + " this.material.dispose();", + " this.depthMaterial.dispose();", + " this.distanceMaterial.dispose();", + " this.group.removeFromParent();", + " }", + "}", + "", + "/**", + " * Makes a Physics3D behavior use a height field shape following the terrain.", + " * Changes of heights are applied to the shape without recreating it.", + " * @implements {gdjs.Physics3DRuntimeBehavior.BodyUpdater}", + " */", + "class TerrainBodyUpdater {", + " /**", + " * @param {Terrain} terrain", + " * @param {gdjs.Physics3DRuntimeBehavior} physics", + " */", + " constructor(terrain, physics) {", + " this.terrain = terrain;", + " this.physics = physics;", + " this.defaultBodyUpdater = physics.bodyUpdater;", + " this.changedSamples = new SampleRectangle();", + " /** @type {Jolt.HeightFieldShape | null} */", + " this.heightField = null;", + " this.heightScale = 1;", + " this.heightsBufferPointer = 0;", + " this.heightsBufferLength = 0;", + " this.bodyX = 0;", + " this.bodyY = 0;", + " this.bodyZ = 0;", + " this.bodyHeight = 0;", + " physics.bodyType = 'Static';", + " physics.bodyUpdater = this;", + " }", + "", + " createShape() {", + " const { data, object } = this.terrain;", + " const worldInvScale = this.physics._sharedData.worldInvScale;", + " const { size } = data;", + " this.heightScale = object.getDepth() * worldInvScale;", + "", + " // Changes of heights (SetHeights) break collisions unless the sample", + " // count is a power of 2: extra samples have no collision.", + " const sampleCount = Math.pow(2, Math.ceil(Math.log2(size)));", + " const settings = new Jolt.HeightFieldShapeSettings();", + " settings.mScale.Set(", + " (object.getWidth() / data.resolution) * worldInvScale,", + " 1,", + " (object.getHeight() / data.resolution) * worldInvScale", + " );", + " settings.mSampleCount = sampleCount;", + " settings.mBlockSize = 4;", + " // Allows later changes of heights on the whole depth of the object.", + " settings.mMinHeightValue = 0;", + " settings.mMaxHeightValue = this.heightScale;", + " settings.mHeightSamples.resize(sampleCount * sampleCount);", + " const samples = new Float32Array(", + " Jolt.HEAPF32.buffer,", + " Jolt.getPointer(settings.mHeightSamples.data()),", + " sampleCount * sampleCount", + " );", + " const noCollision = Jolt.HeightFieldShapeConstantValues.prototype.cNoCollisionValue;", + " samples.fill(noCollision);", + " // Height fields go along X and Z: rows are flipped so that, once", + " // rotated to have heights along Z, rows go along Y.", + " for (let j = 0; j < size; j++) {", + " const row = (size - 1 - j) * sampleCount;", + " for (let i = 0; i < size; i++) {", + " samples[row + i] = this._getSampleHeight(j * size + i, noCollision);", + " }", + " }", + " const shape = settings.Create().Get();", + " Jolt.destroy(settings);", + " this.heightField = Jolt.castObject(shape, Jolt.HeightFieldShape);", + " this.changedSamples.clear();", + " return shape;", + " }", + "", + " /** @returns {number} The height of a sample in the shape, or no collision in a hole. */", + " _getSampleHeight(index, noCollision) {", + " const height = this.terrain.surfaceHeights[index];", + " return Number.isNaN(height) ? noCollision : height * this.heightScale;", + " }", + "", + " _getBodyPosition() {", + " const { object } = this.terrain;", + " const worldInvScale = this.physics._sharedData.worldInvScale;", + " this.bodyX = object.getX();", + " this.bodyY = object.getY();", + " this.bodyZ = object.getZ();", + " // The origin of the height field is at the bottom of the terrain (see createShape).", + " this.bodyHeight = object.getHeight();", + " return this.physics._sharedData.getRVec3(", + " object.getX() * worldInvScale,", + " (object.getY() + object.getHeight()) * worldInvScale,", + " object.getZ() * worldInvScale", + " );", + " }", + "", + " _getBodyRotation() {", + " // A quarter turn around X: heights go along Z.", + " return this.physics._sharedData.getQuat(Math.SQRT1_2, 0, 0, Math.SQRT1_2);", + " }", + "", + " createAndAddBody() {", + " const { physics } = this;", + " const { bodyInterface } = physics._sharedData;", + " const shape = this.createShape();", + " const settings = new Jolt.BodyCreationSettings(", + " shape,", + " this._getBodyPosition(),", + " this._getBodyRotation(),", + " Jolt.EMotionType_Static,", + " physics.getBodyLayer()", + " );", + " settings.mFriction = physics.friction;", + " settings.mRestitution = physics.restitution;", + " const body = bodyInterface.CreateBody(settings);", + " Jolt.destroy(settings);", + " bodyInterface.AddBody(body.GetID(), Jolt.EActivation_DontActivate);", + " return body;", + " }", + "", + " updateObjectFromBody() {", + " // The terrain is static: the body never moves it.", + " }", + "", + " updateBodyFromObject() {", + " const { physics } = this;", + " const body = physics._body;", + " if (!body) return;", + " const { object } = this.terrain;", + " const { bodyInterface } = physics._sharedData;", + " if (", + " object.getX() !== this.bodyX ||", + " object.getY() !== this.bodyY ||", + " object.getZ() !== this.bodyZ ||", + " object.getHeight() !== this.bodyHeight", + " ) {", + " bodyInterface.SetPositionAndRotation(", + " body.GetID(),", + " this._getBodyPosition(),", + " this._getBodyRotation(),", + " Jolt.EActivation_DontActivate", + " );", + " }", + " if (!this.changedSamples.isEmpty()) {", + " this._applyChangedHeights(body);", + " }", + " }", + "", + " _applyChangedHeights(body) {", + " const { data } = this.terrain;", + " const { size } = data;", + " const { minI, minJ, maxI, maxJ } = this.changedSamples;", + " // Jolt only changes blocks of samples. Rows are flipped and extra", + " // samples have no collision (see createShape).", + " const blockSize = this.heightField.GetBlockSize();", + " const sampleCount = this.heightField.GetSampleCount();", + " const alignDown = (value) => Math.floor(value / blockSize) * blockSize;", + " const alignUp = (value) => Math.min(Math.ceil(value / blockSize) * blockSize, sampleCount);", + " const firstX = alignDown(minI);", + " const firstRow = alignDown(size - 1 - maxJ);", + " const width = alignUp(maxI + 1) - firstX;", + " const height = alignUp(size - minJ) - firstRow;", + " if (width * height > this.heightsBufferLength) {", + " if (this.heightsBufferPointer) Jolt._webidl_free(this.heightsBufferPointer);", + " this.heightsBufferLength = width * height;", + " this.heightsBufferPointer = Jolt._webidl_malloc(this.heightsBufferLength * 4);", + " }", + " const heights = new Float32Array(Jolt.HEAPF32.buffer, this.heightsBufferPointer, width * height);", + " const noCollision = Jolt.HeightFieldShapeConstantValues.prototype.cNoCollisionValue;", + " for (let row = 0; row < height; row++) {", + " const j = size - 1 - (firstRow + row);", + " for (let column = 0; column < width; column++) {", + " const i = firstX + column;", + " heights[row * width + column] =", + " i < size && j >= 0 ? this._getSampleHeight(j * size + i, noCollision) : noCollision;", + " }", + " }", + " const sharedData = this.physics._sharedData;", + " this.heightField.SetHeights(", + " firstX,", + " firstRow,", + " width,", + " height,", + " this.heightsBufferPointer,", + " width,", + " sharedData.jolt.GetTempAllocator()", + " );", + " sharedData.bodyInterface.NotifyShapeChanged(", + " body.GetID(),", + " sharedData.getVec3(0, 0, 0),", + " false,", + " Jolt.EActivation_DontActivate", + " );", + " this._wakeUpBodiesOnChangedSamples();", + " this.changedSamples.clear();", + " }", + "", + " /** Bodies sleeping on the changed area would otherwise float or sink. */", + " _wakeUpBodiesOnChangedSamples() {", + " const { object, data } = this.terrain;", + " const worldInvScale = this.physics._sharedData.worldInvScale;", + " const cellWidth = object.getWidth() / data.resolution;", + " const cellHeight = object.getHeight() / data.resolution;", + " const { minI, minJ, maxI, maxJ } = this.changedSamples;", + " const min = new Jolt.Vec3(", + " (object.getX() + (minI - 1) * cellWidth) * worldInvScale,", + " (object.getY() + (minJ - 1) * cellHeight) * worldInvScale,", + " (object.getZ() - 1) * worldInvScale", + " );", + " const max = new Jolt.Vec3(", + " (object.getX() + (maxI + 1) * cellWidth) * worldInvScale,", + " (object.getY() + (maxJ + 1) * cellHeight) * worldInvScale,", + " (object.getZ() + object.getDepth() + 1) * worldInvScale", + " );", + " const box = new Jolt.AABox(min, max);", + " const broadPhaseLayerFilter = new Jolt.BroadPhaseLayerFilter();", + " const objectLayerFilter = new Jolt.ObjectLayerFilter();", + " this.physics._sharedData.bodyInterface.ActivateBodiesInAABox(box, broadPhaseLayerFilter, objectLayerFilter);", + " Jolt.destroy(min);", + " Jolt.destroy(max);", + " Jolt.destroy(box);", + " Jolt.destroy(broadPhaseLayerFilter);", + " Jolt.destroy(objectLayerFilter);", + " }", + "", + " recreateShape() {", + " const body = this.physics._body;", + " if (!body) return;", + " this.physics._sharedData.bodyInterface.SetShape(", + " body.GetID(),", + " this.createShape(),", + " false,", + " Jolt.EActivation_DontActivate", + " );", + " const { resolution } = this.terrain.data;", + " this.changedSamples.set(0, 0, resolution, resolution);", + " this._wakeUpBodiesOnChangedSamples();", + " this.changedSamples.clear();", + " }", + "", + " destroyBody() {", + " this.defaultBodyUpdater.destroyBody();", + " this.heightField = null;", + " }", + "", + " dispose() {", + " if (this.heightsBufferPointer) Jolt._webidl_free(this.heightsBufferPointer);", + " this.heightsBufferPointer = 0;", + " this.heightsBufferLength = 0;", + " }", + "}", + "", + "/** @param {TerrainData} data */", + "const copyTerrainData = (data) => {", + " const copy = new TerrainData(data.resolution);", + " copy.copyFrom(data);", + " return copy;", + "};", + "", + "// Generating a relief or reading an image is long for big terrains, and done", + "// for every instance and every hot-reload: the results are kept.", + "const baseDataCache = new Map();", + "const MAX_CACHED_BASE_DATA = 8;", + "// Changes of the shape of a terrain kept for consumers of its surface that", + "// missed some versions: older changes update the whole surface.", + "const MAX_SURFACE_CHANGES = 64;", + "// Unique among all terrains: a terrain replaced by another one never has its version.", + "let lastGroundVersion = 0;", + "", + "/**", + " * The terrain of a Terrain3D object: its data, how it's drawn and its", + " * surface (for collisions and navigation meshes). Positions are in the scene.", + " */", + "class Terrain {", + " /** @param {gdjs.CustomRuntimeObject3D} object */", + " constructor(object) {", + " this.object = object;", + " /** @type {TerrainData} */", + " this.data = new TerrainData(DEFAULT_RESOLUTION);", + " /** The data before any sculpting or painting, from the relief or heightmap. */", + " this.baseData = this.data;", + " this.renderer = new TerrainRenderer(this);", + " /** @type {TerrainBodyUpdater[]} */", + " this.bodyUpdaters = [];", + " this.changedSamples = new SampleRectangle();", + " this.hasPaintChanged = false;", + " /** Increased each time the ground changes, for objects following it (like grass). */", + " this.version = 0;", + " /** Increased each time the shape of the ground changes (not its paint or position). */", + " this.surfaceVersion = 0;", + " /**", + " * The last changes of the shape of the ground, for `getChangedArea`.", + " * @type {Array<{version: number, samples: SampleRectangle}>}", + " */", + " this.surfaceChanges = [];", + " /** The changes since this version are all in `surfaceChanges`. */", + " this.surfaceChangesStartVersion = 0;", + " this._stroke = { cellWidth: 1, cellHeight: 1, ax: 0, ay: 0, bx: 0, by: 0, radius: 0 };", + " this._strokeChangedSamples = new SampleRectangle();", + " /** The properties the data was loaded from, to reload it only if they change. */", + " this.loadedFrom = '';", + " /** The heightmap image being loaded: the relief is used until it is. */", + " this.loadingHeightmapImage = '';", + " this.isDisposed = false;", + " /** The last value of the sculpt data property read or saved. */", + " this.sculptData = '';", + " /**", + " * Values saved by the editor tools: the editor sends them back with a", + " * hot-reload, possibly after newer ones were saved.", + " * @type {string[]}", + " */", + " this.savedSculptData = [];", + " /** The heights of the surface: the heights of the data, and NaN in holes. */", + " this.surfaceHeights = new Float32Array(0);", + " this.loadFromProperties();", + " this._transform = this._getTransform();", + " getGrounds(object.getRuntimeScene()).add(this);", + " /**", + " * The shape of the ground, for the physics engine and navigation meshes.", + " * @type {gdjs.Surface}", + " */", + " this.surface = {", + " getVersion: () => this.surfaceVersion,", + " getChangedArea: (sinceVersion) => this.getChangedArea(sinceVersion),", + " getTriangles: () => null,", + " getHeightField: () => ({ columns: this.data.size, rows: this.data.size, heights: this.surfaceHeights }),", + " };", + " if (typeof object.setSurface === 'function') {", + " object.setSurface(this.surface);", + " } else if (gdjs.Physics3DRuntimeBehavior) {", + " // Older GDevelop versions don't use surfaces.", + " for (const behavior of object._behaviors) {", + " if (behavior instanceof gdjs.Physics3DRuntimeBehavior) {", + " this.bodyUpdaters.push(new TerrainBodyUpdater(this, behavior));", + " }", + " }", + " }", + " }", + "", + " _getSourceProperties() {", + " const { object } = this;", + " return [", + " object._getResolution(),", + " object._getRelief(),", + " object._getSeed(),", + " object._getHeightmapImage(),", + " object._getEdits(),", + " ].join('|');", + " }", + "", + " loadFromProperties() {", + " const { object } = this;", + " const resolution = parseInt(object._getResolution(), 10);", + " const validResolution = RESOLUTIONS.indexOf(resolution) !== -1 ? resolution : DEFAULT_RESOLUTION;", + " this.sculptData = object._getSculptData();", + " this.loadedFrom = this._getSourceProperties();", + "", + " this.baseData = this._getBaseData(validResolution);", + " let data = codec.decode(this.sculptData, (resolution) => copyTerrainData(this._getBaseData(resolution)));", + " if (data && data.resolution !== validResolution) {", + " data = data.resampled(validResolution);", + " }", + " this.setData(data || copyTerrainData(this.baseData));", + " }", + "", + " /**", + " * @param {number} resolution", + " * @returns {TerrainData} The data from the relief or the heightmap, shared: it must not be changed.", + " */", + " _getBaseData(resolution) {", + " const { object } = this;", + " const key = [resolution, object._getRelief(), object._getSeed(), object._getHeightmapImage(), object._getEdits()].join('|');", + " const cachedData = baseDataCache.get(key);", + " if (cachedData) return cachedData;", + "", + " const data = new TerrainData(resolution);", + " const heightmapImage = object._getHeightmapImage();", + " const isHeightmapLoaded =", + " !!heightmapImage && loadHeightsFromImage(data, object.getRuntimeScene().getGame(), heightmapImage);", + " if (!isHeightmapLoaded) {", + " relief.generate(data, object._getRelief(), object._getSeed());", + " if (heightmapImage) this._reloadOnceLoaded(heightmapImage);", + " }", + " edits.apply(data, object._getEdits());", + " // Not kept if the image is missing: it may be loaded later.", + " if (isHeightmapLoaded || !heightmapImage) baseDataCache.set(key, data);", + " if (baseDataCache.size > MAX_CACHED_BASE_DATA) {", + " baseDataCache.delete(baseDataCache.keys().next().value);", + " }", + " return data;", + " }", + "", + " /** @param {TerrainData} data */", + " setData(data) {", + " this.version = ++lastGroundVersion;", + " this.surfaceVersion = this.version;", + " this.surfaceChanges.length = 0;", + " this.surfaceChangesStartVersion = this.version;", + " const hasSameResolution = data.resolution === this.data.resolution && this.renderer.chunks.length > 0;", + " this.data = data;", + " const allSamples = new SampleRectangle().set(0, 0, data.resolution, data.resolution);", + " this.surfaceHeights = new Float32Array(data.heights.length);", + " this.renderer.setHasHoles(false);", + " this._updateSurfaceHeights(allSamples);", + " if (hasSameResolution) {", + " this.renderer.updateSamples(allSamples, true);", + " } else {", + " this.renderer.rebuild();", + " }", + " for (const bodyUpdater of this.bodyUpdaters) {", + " bodyUpdater.recreateShape();", + " }", + " }", + "", + " onHotReloading() {", + " const sculptData = this.object._getSculptData();", + " const savedIndex = this.savedSculptData.indexOf(sculptData);", + " if (savedIndex !== -1 && this._getSourceProperties() === this.loadedFrom) {", + " // The terrain already has this data, or newer changes.", + " this.savedSculptData.splice(0, savedIndex + 1);", + " } else if (sculptData !== this.sculptData || this._getSourceProperties() !== this.loadedFrom) {", + " this.savedSculptData.length = 0;", + " this.loadFromProperties();", + " }", + " this.renderer.updateMaterial();", + " }", + "", + " /** @returns {string} The data to save in the sculpt data property. */", + " save() {", + " this.sculptData = codec.encode(this.data, this.baseData);", + " this.savedSculptData.push(this.sculptData);", + " return this.sculptData;", + " }", + "", + " getCellWidth() {", + " return this.object.getWidth() / this.data.resolution;", + " }", + "", + " getCellHeight() {", + " return this.object.getHeight() / this.data.resolution;", + " }", + "", + " _toGridX(x) {", + " return (x - this.object.getX()) / this.getCellWidth();", + " }", + "", + " _toGridY(y) {", + " return (y - this.object.getY()) / this.getCellHeight();", + " }", + "", + " /** @returns {number} The Z position of the ground. */", + " getHeightAt(x, y) {", + " return this.object.getZ() + this.data.sampleHeight(this._toGridX(x), this._toGridY(y)) * this.object.getDepth();", + " }", + "", + " /** @returns {number} The angle of the ground with the horizontal, in degrees. */", + " getSlopeAt(x, y) {", + " const gridX = this._toGridX(x);", + " const gridY = this._toGridY(y);", + " const { data } = this;", + " const depth = this.object.getDepth();", + " const slopeX = ((data.sampleHeight(gridX + 0.5, gridY) - data.sampleHeight(gridX - 0.5, gridY)) * depth) / this.getCellWidth();", + " const slopeY = ((data.sampleHeight(gridX, gridY + 0.5) - data.sampleHeight(gridX, gridY - 0.5)) * depth) / this.getCellHeight();", + " return gdjs.toDegrees(Math.atan(Math.hypot(slopeX, slopeY)));", + " }", + "", + " /** @returns {number} The layer painted the most, from 1 to 4. */", + " getLayerAt(x, y) {", + " return this.data.getDominantLayer(this._toGridX(x), this._toGridY(y)) + 1;", + " }", + "", + " /**", + " * @param {number} layer From 1 to 4.", + " * @returns {number} How much the layer is painted, from 0 to 1.", + " */", + " getLayerWeightAt(x, y, layer) {", + " const layerIndex = clampInteger(layer - 1, 0, LAYER_COUNT - 1);", + " return this.data.sampleLayerWeight(this._toGridX(x), this._toGridY(y), layerIndex);", + " }", + "", + " /** @returns {number[]} The painted tint: red, green and blue, from 0 to 255 (white when there is none). */", + " getTintAt(x, y) {", + " const gridX = this._toGridX(x);", + " const gridY = this._toGridY(y);", + " return [0, 1, 2].map((channel) => Math.round(this.data.sampleColor(gridX, gridY, channel) * 255));", + " }", + "", + " /** @returns {number} How wet the ground is, from 0 (dry) to 1, painted or near the water (see the wet shore). */", + " getWetnessAt(x, y) {", + " const paintedWetness = this.data.sampleColor(this._toGridX(x), this._toGridY(y), 3);", + " const { object } = this;", + " if (!object._getWetShore()) return paintedWetness;", + " const fade = Math.max(object._getWetShoreFade(), 0.001);", + " const shoreWetness = clamp01((object._getWetShoreZ() + fade - this.getHeightAt(x, y)) / fade);", + " // Like the smoothstep of the shader.", + " return Math.max(paintedWetness, shoreWetness * shoreWetness * (3 - 2 * shoreWetness));", + " }", + "", + " _isInArea(x, y) {", + " const { object } = this;", + " return (", + " x >= object.getX() &&", + " x <= object.getX() + object.getWidth() &&", + " y >= object.getY() &&", + " y <= object.getY() + object.getHeight()", + " );", + " }", + "", + " /** @returns {boolean} true if the position is above or under the terrain, and not in a hole. */", + " containsPoint(x, y) {", + " return this._isInArea(x, y) && this.data.sampleSolidity(this._toGridX(x), this._toGridY(y)) >= 0.5;", + " }", + "", + " /** @returns {boolean} true if there is a hole in the terrain at the position, like what is drawn. */", + " isHoleAt(x, y) {", + " return this._isInArea(x, y) && this.data.sampleSolidity(this._toGridX(x), this._toGridY(y)) < 0.5;", + " }", + "", + " _getStroke(x1, y1, x2, y2, radius) {", + " const stroke = this._stroke;", + " stroke.cellWidth = this.getCellWidth();", + " stroke.cellHeight = this.getCellHeight();", + " stroke.ax = x1 - this.object.getX();", + " stroke.ay = y1 - this.object.getY();", + " stroke.bx = x2 - this.object.getX();", + " stroke.by = y2 - this.object.getY();", + " stroke.radius = Math.max(radius, 0);", + " return stroke;", + " }", + "", + " /**", + " * @param {SampleRectangle} samples", + " * @param {boolean} haveHeightsChanged false when only the paint changed.", + " */", + " _onSamplesChanged(samples, haveHeightsChanged) {", + " if (samples.isEmpty()) return;", + " this.version = ++lastGroundVersion;", + " this.changedSamples.add(samples);", + " // Painting changes the surface when it makes holes or fills them.", + " const hasSurfaceChanged = this._updateSurfaceHeights(samples);", + " if (!haveHeightsChanged && !hasSurfaceChanged) return;", + " this.surfaceVersion = this.version;", + " this.surfaceChanges.push({ version: this.version, samples: new SampleRectangle().set(samples.minI, samples.minJ, samples.maxI, samples.maxJ) });", + " if (this.surfaceChanges.length > MAX_SURFACE_CHANGES) {", + " this.surfaceChangesStartVersion = this.surfaceChanges.shift().version;", + " }", + " for (const bodyUpdater of this.bodyUpdaters) {", + " bodyUpdater.changedSamples.add(samples);", + " }", + " }", + "", + " /**", + " * @param {SampleRectangle} samples", + " * @returns {boolean} true if heights of the surface changed, or holes were made or filled.", + " */", + " _updateSurfaceHeights(samples) {", + " const { data, surfaceHeights } = this;", + " let hasChanged = false;", + " let hasHoles = false;", + " for (let j = samples.minJ; j <= samples.maxJ; j++) {", + " for (let i = samples.minI; i <= samples.maxI; i++) {", + " const index = j * data.size + i;", + " const isHole = data.isHole(index);", + " const height = isHole ? NaN : data.heights[index];", + " const previousHeight = surfaceHeights[index];", + " if (isHole) {", + " hasHoles = true;", + " if (Number.isNaN(previousHeight)) continue;", + " } else if (height === previousHeight) {", + " continue;", + " }", + " surfaceHeights[index] = height;", + " hasChanged = true;", + " }", + " }", + " // Filled holes keep the shaders drawing holes until the data is loaded again.", + " if (hasHoles) this.renderer.setHasHoles(true);", + " return hasChanged;", + " }", + "", + " /** @param {number} height In scene units, negative to lower the ground. */", + " raise(x1, y1, x2, y2, radius, height) {", + " const samples = this._strokeChangedSamples;", + " brushes.raise(this.data, this._getStroke(x1, y1, x2, y2, radius), height / this.object.getDepth(), samples);", + " this._onSamplesChanged(samples, true);", + " }", + "", + " /** @param {number} z The Z position to flatten the ground to. */", + " /** @param {number} [toZ] The Z position at the end of the segment, for a slope. The same as z by default. */", + " flatten(x1, y1, x2, y2, radius, z, strength, toZ = z) {", + " const samples = this._strokeChangedSamples;", + " const toHeight = (value) => (value - this.object.getZ()) / this.object.getDepth();", + " brushes.flatten(this.data, this._getStroke(x1, y1, x2, y2, radius), toHeight(z), strength, samples, toHeight(toZ));", + " this._onSamplesChanged(samples, true);", + " }", + "", + " smooth(x1, y1, x2, y2, radius, strength) {", + " const samples = this._strokeChangedSamples;", + " brushes.smooth(this.data, this._getStroke(x1, y1, x2, y2, radius), strength, samples);", + " this._onSamplesChanged(samples, true);", + " }", + "", + " /** @param {number} layer From 1 to 4. */", + " paint(x1, y1, x2, y2, radius, layer, strength) {", + " const layerIndex = clampInteger(layer - 1, 0, LAYER_COUNT - 1);", + " const samples = this._strokeChangedSamples;", + " brushes.paint(this.data, this._getStroke(x1, y1, x2, y2, radius), layerIndex, strength, samples);", + " this.hasPaintChanged = true;", + " this._onSamplesChanged(samples, false);", + " }", + "", + " /** @param {number[]} color The red, green and blue (0-255) of the tint: white removes it. */", + " tint(x1, y1, x2, y2, radius, color, strength) {", + " const samples = this._strokeChangedSamples;", + " brushes.tint(this.data, this._getStroke(x1, y1, x2, y2, radius), color, strength, samples);", + " this.hasPaintChanged = true;", + " this._onSamplesChanged(samples, false);", + " }", + "", + " /** @param {number} wetness From 0 (dry) to 1. */", + " wet(x1, y1, x2, y2, radius, wetness, strength) {", + " const samples = this._strokeChangedSamples;", + " brushes.wet(this.data, this._getStroke(x1, y1, x2, y2, radius), wetness, strength, samples);", + " this.hasPaintChanged = true;", + " this._onSamplesChanged(samples, false);", + " }", + "", + " /** Make a hole around a segment (`fillHoles` fills it again). */", + " makeHole(x1, y1, x2, y2, radius) {", + " const samples = this._strokeChangedSamples;", + " brushes.hole(this.data, this._getStroke(x1, y1, x2, y2, radius), samples);", + " this.hasPaintChanged = true;", + " this._onSamplesChanged(samples, false);", + " }", + "", + " /** Fill the holes around a segment with the layers of the ground around them. */", + " fillHoles(x1, y1, x2, y2, radius) {", + " const samples = this._strokeChangedSamples;", + " brushes.fill(this.data, this._getStroke(x1, y1, x2, y2, radius), samples);", + " this.hasPaintChanged = true;", + " this._onSamplesChanged(samples, false);", + " }", + "", + " /**", + " * Make the entrance of a tunnel or a cave: a hole, with the ground in", + " * front of it flattened to the floor of the tunnel.", + " * @param {number} angle The direction going into the ground, in degrees.", + " * @param {number} floorZ", + " * @param {number} length The length of the flat ground in front of the entrance.", + " */", + " carveEntrance(x, y, radius, angle, floorZ, length) {", + " const directionX = Math.cos(gdjs.toRad(angle));", + " const directionY = Math.sin(gdjs.toRad(angle));", + " this.flatten(x - directionX * length, y - directionY * length, x, y, radius * 1.5, floorZ, 1);", + " this.makeHole(x, y, x + directionX * radius, y + directionY * radius, radius);", + " }", + "", + " generate(reliefName, seed) {", + " this.baseData = new TerrainData(this.data.resolution);", + " relief.generate(this.baseData, reliefName, seed);", + " this.setData(copyTerrainData(this.baseData));", + " }", + "", + " loadHeightmap(imageResourceName) {", + " const baseData = new TerrainData(this.data.resolution);", + " if (loadHeightsFromImage(baseData, this.object.getRuntimeScene().getGame(), imageResourceName)) {", + " baseData.splat.set(this.data.splat);", + " baseData.colors.set(this.data.colors);", + " this.baseData = baseData;", + " this.setData(copyTerrainData(baseData));", + " }", + " }", + "", + " /** @param {gdjs.RuntimeObject[]} objects */", + " placeOnGround(objects) {", + " for (const object of objects) {", + " if (object === this.object || !gdjs.Base3DHandler.is3D(object)) continue;", + " const groundZ = this.getHeightAt(object.getCenterXInScene(), object.getCenterYInScene());", + " // The origin of the object is not always at its bottom.", + " object.setZ(groundZ + object.getZ() - object.getUnrotatedAABBMinZ());", + " }", + " }", + "", + " /**", + " * @returns {THREE.Vector3 | null} The point of the ground under the cursor,", + " * in the terrain group coordinates. Holes are hit too, so that brushes can fill them.", + " */", + " raycastFromCursor(game, raycaster, ndc) {", + " const layer = this.object.getInstanceContainer().getLayer(this.object.getLayer());", + " const camera = layer.getRenderer().getThreeCamera();", + " if (!camera) return null;", + " const inputManager = game.getInputManager();", + " ndc.set(", + " (inputManager.getCursorX() / game.getGameResolutionWidth()) * 2 - 1,", + " -(inputManager.getCursorY() / game.getGameResolutionHeight()) * 2 + 1", + " );", + " raycaster.setFromCamera(ndc, camera);", + " this.renderer.group.updateWorldMatrix(true, false);", + " const ray = raycaster.ray.clone().applyMatrix4(this.renderer.group.matrixWorld.clone().invert());", + " ray.direction.normalize();", + "", + " const box = new THREE.Box3(new THREE.Vector3(0, 0, 0), new THREE.Vector3(AREA_SIZE, AREA_SIZE, AREA_DEPTH));", + " const entry = box.containsPoint(ray.origin) ? ray.origin.clone() : ray.intersectBox(box, new THREE.Vector3());", + " if (!entry) return null;", + " const cellSize = AREA_SIZE / this.data.resolution;", + " const groundZAt = (point) => this.data.sampleHeight(point.x / cellSize, point.y / cellSize) * AREA_DEPTH;", + " const step = cellSize / 2;", + " const point = entry.clone();", + " const previousPoint = entry.clone();", + " for (let distance = 0; distance < AREA_SIZE * 2; distance += step) {", + " if (point.z <= groundZAt(point)) {", + " // Refine between the last point above the ground and this one.", + " for (let iteration = 0; iteration < 8; iteration++) {", + " const middle = previousPoint.clone().lerp(point, 0.5);", + " if (middle.z <= groundZAt(middle)) point.copy(middle);", + " else previousPoint.copy(middle);", + " }", + " return point;", + " }", + " if (!box.containsPoint(point)) return null;", + " previousPoint.copy(point);", + " point.addScaledVector(ray.direction, step);", + " }", + " return null;", + " }", + "", + " /**", + " * @param {THREE.Vector3} localPosition In the terrain group coordinates (like from `raycastFromCursor`).", + " * @returns {{x: number, y: number, z: number}} The position in the scene.", + " */", + " toScenePosition(localPosition) {", + " const { object } = this;", + " return {", + " x: object.getX() + (localPosition.x / AREA_SIZE) * object.getWidth(),", + " y: object.getY() + (localPosition.y / AREA_SIZE) * object.getHeight(),", + " z: object.getZ() + (localPosition.z / AREA_DEPTH) * object.getDepth(),", + " };", + " }", + "", + " /** Draw the changes of the ground (collisions are updated by the physics engine). */", + " applyChanges() {", + " this.renderer.updateSamples(this.changedSamples, this.hasPaintChanged);", + " this.changedSamples.clear();", + " this.hasPaintChanged = false;", + " }", + "", + " /** @returns {number} A number changing each time the ground changes (see `getGrounds`). */", + " getVersion() {", + " return this.version;", + " }", + "", + " /**", + " * @param {number} sinceVersion", + " * @returns {gdjs.SurfaceArea | null} The part of the ground whose shape changed since the version (see `gdjs.Surface`).", + " */", + " getChangedArea(sinceVersion) {", + " if (sinceVersion < this.surfaceChangesStartVersion) return null;", + " const samples = new SampleRectangle();", + " for (const change of this.surfaceChanges) {", + " if (change.version > sinceVersion) samples.add(change.samples);", + " }", + " if (samples.isEmpty()) return { minX: 0, minY: 0, maxX: 0, maxY: 0 };", + " const { resolution } = this.data;", + " return {", + " minX: samples.minI / resolution,", + " minY: samples.minJ / resolution,", + " maxX: samples.maxI / resolution,", + " maxY: samples.maxJ / resolution,", + " };", + " }", + "", + " _getTransform() {", + " const { object } = this;", + " return [object.getX(), object.getY(), object.getZ(), object.getWidth(), object.getHeight(), object.getDepth()].join(',');", + " }", + "", + " /** @param {string} imageResourceName A heightmap image that is not loaded yet. */", + " _reloadOnceLoaded(imageResourceName) {", + " if (this.loadingHeightmapImage === imageResourceName) return;", + " this.loadingHeightmapImage = imageResourceName;", + " const game = this.object.getRuntimeScene().getGame();", + " game.getImageManager()", + " .loadResource(imageResourceName)", + " .then(() => {", + " if (this.isDisposed || this.loadingHeightmapImage !== imageResourceName) return;", + " this.loadingHeightmapImage = '';", + " if (getLoadedImage(game, imageResourceName)) this.loadFromProperties();", + " })", + " .catch(() => {});", + " }", + "", + " update() {", + " const transform = this._getTransform();", + " if (transform !== this._transform) {", + " this._transform = transform;", + " this.version = ++lastGroundVersion;", + " }", + " this.applyChanges();", + " this.renderer.uniforms.terrainWorldScale.value = getWorldScale(this.object);", + " this._updateWetShore();", + " const layer = this.object.getInstanceContainer().getLayer(this.object.getLayer());", + " this.renderer.updateLevelsOfDetail(layer.getRenderer().getThreeCamera());", + " }", + "", + " _updateWetShore() {", + " const { object } = this;", + " // In the coordinates of the terrain group, like the vertices.", + " const depthScale = AREA_DEPTH / Math.max(object.getDepth(), 0.0001);", + " this.renderer.uniforms.terrainWetShore.value.set(", + " (object._getWetShoreZ() - object.getZ()) * depthScale,", + " Math.max(object._getWetShoreFade(), 0.001) * depthScale,", + " object._getWetShore() ? 1 : 0", + " );", + " }", + "", + " dispose() {", + " this.isDisposed = true;", + " getGrounds(this.object.getRuntimeScene()).delete(this);", + " if (typeof this.object.setSurface === 'function' && this.object.getSurface() === this.surface) {", + " this.object.setSurface(null);", + " }", + " this.renderer.dispose();", + " for (const bodyUpdater of this.bodyUpdaters) {", + " bodyUpdater.dispose();", + " }", + " }", + "}", + "", + "/**", + " * @param {gdjs.RuntimeObject} object", + " * @returns {number} The number of scene units by 3D world unit (1 in GDevelop versions before the world scale).", + " */", + "const getWorldScale = (object) => {", + " const scene = object.getRuntimeScene();", + " return typeof scene.getRenderer3DWorldScale === 'function' ? scene.getRenderer3DWorldScale() : 1;", + "};", + "", + "const averageColors = new WeakMap();", + "let white = null;", + "/**", + " * @param {THREE.Texture} texture", + " * @returns {THREE.Color} The average color of the texture (white when it's not loaded).", + " */", + "const getAverageColor = (texture) => {", + " const cachedColor = averageColors.get(texture);", + " if (cachedColor) return cachedColor;", + " const image = texture.image;", + " const isDrawable =", + " typeof document !== 'undefined' &&", + " ((image instanceof HTMLImageElement && image.complete && image.naturalWidth > 0) ||", + " image instanceof HTMLCanvasElement ||", + " (typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap));", + " if (!isDrawable) return white || (white = new THREE.Color(1, 1, 1));", + " const size = 8;", + " const canvas = document.createElement('canvas');", + " canvas.width = size;", + " canvas.height = size;", + " const context = canvas.getContext('2d');", + " context.drawImage(image, 0, 0, size, size);", + " const pixels = context.getImageData(0, 0, size, size).data;", + " let red = 0;", + " let green = 0;", + " let blue = 0;", + " for (let index = 0; index < pixels.length; index += 4) {", + " red += pixels[index];", + " green += pixels[index + 1];", + " blue += pixels[index + 2];", + " }", + " const count = size * size * 255;", + " const color = new THREE.Color().setRGB(red / count, green / count, blue / count, THREE.SRGBColorSpace);", + " averageColors.set(texture, color);", + " return color;", + "};", + "", + "const svgIcon = (content) =>", + " 'data:image/svg+xml,' +", + " encodeURIComponent('' + content + '');", + "const strokes = 'fill=\"none\" stroke=\"#000\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"';", + "const toolIcons = {", + " Raise: svgIcon(''),", + " Lower: svgIcon(''),", + " Smooth: svgIcon(''),", + " Flatten: svgIcon(''),", + " Size: svgIcon(''),", + " Strength: svgIcon(''),", + " Undo: svgIcon(''),", + " Hole: svgIcon(''),", + " FillHoles: svgIcon(''),", + " Wetness: svgIcon(''),", + "};", + "// Tints for the color brush when the editor has no color picker (older GDevelop versions).", + "const tintPresets = ['#e2c879', '#a5c45a', '#8a6a48', '#c98a62', '#9a9a92', '#ffffff'];", + "", + "/**", + " * The sculpt and paint tools shown in the scene editor when a terrain is", + " * selected. Without the needed editor functions (older GDevelop versions),", + " * nothing is shown: the terrain can still be changed with events.", + " */", + "class TerrainEditorTools {", + " constructor() {", + " /** '' when no brush is used. */", + " this.toolName = '';", + " this.sizePercent = 30;", + " this.strengthPercent = 50;", + " /** @type {Terrain | null} */", + " this.terrain = null;", + " /** @type {gdjs.InGameEditor | null} */", + " this.editor = null;", + " this.isStroking = false;", + " this.flattenHeight = 0;", + " /** The color of the \"Tint\" brush, as \"#rrggbb\". */", + " this.tintColor = tintPresets[0];", + " this.wetnessPercent = 100;", + " this.lastStrokeTime = 0;", + " /** @type {{data: TerrainData, baseData: TerrainData}[]} */", + " this.undoSteps = [];", + " this.raycaster = new THREE.Raycaster();", + " this.ndc = new THREE.Vector2();", + " this._swatchColor = new THREE.Color();", + " }", + "", + " /** @param {gdjs.InGameEditor} editor */", + " update(editor) {", + " if (typeof editor.showToolbar !== 'function') {", + " // Older GDevelop versions: the terrain can only be changed with events.", + " return;", + " }", + " this.editor = editor;", + " const selectedObjects = editor.getSelectedObjects();", + " const terrain =", + " selectedObjects.length === 1 && selectedObjects[0].__terrain3D ? selectedObjects[0].__terrain3D : null;", + " if (terrain !== this.terrain) {", + " this._endStroke();", + " if (this.terrain) this.terrain.renderer.setBrush(null, 0);", + " this.terrain = terrain;", + " this.undoSteps.length = 0;", + " }", + " if (terrain) editor.showToolbar('Terrain3D', this._getToolbarItems(terrain));", + " const game = editor.getRuntimeGame();", + " if (!terrain || !this.toolName) {", + " if (terrain) terrain.renderer.setBrush(null, 0);", + " return;", + " }", + "", + " const inputManager = game.getInputManager();", + " if (inputManager.wasKeyJustPressed(gdjs.evtTools.input.keysNameToCode.Escape)) {", + " this._endStroke();", + " this.toolName = '';", + " terrain.renderer.setBrush(null, 0);", + " return;", + " }", + " editor.captureLeftMouseButton();", + " const hit = terrain.raycastFromCursor(game, this.raycaster, this.ndc);", + " const radius = this._getRadius();", + " terrain.renderer.setBrush(hit, radius);", + "", + " // Older GDevelop versions don't have it.", + " const isPressedForTool =", + " typeof editor.isCapturedLeftMouseButtonPressed === 'function'", + " ? editor.isCapturedLeftMouseButtonPressed()", + " : inputManager.isMouseButtonPressed(0);", + " // The button is released outside the canvas when the mouse is on a toolbar.", + " const isPressed = isPressedForTool && inputManager.isMouseInsideCanvas();", + " if (isPressed && hit && !this.isStroking) {", + " this._startStroke(terrain, hit);", + " }", + " if (this.isStroking && isPressed && hit) {", + " this._applyBrush(terrain, hit, radius);", + " }", + " if (!isPressed) {", + " this._endStroke();", + " }", + " }", + "", + " _getRadius() {", + " const { object } = this.terrain;", + " const maxRadius = Math.max(object.getWidth(), object.getHeight()) / 4;", + " return Math.max(maxRadius * Math.pow(this.sizePercent / 100, 2), this.terrain.getCellWidth());", + " }", + "", + " _startStroke(terrain, hit) {", + " this.isStroking = true;", + " this.lastStrokeTime = performance.now();", + " this.flattenHeight = terrain.toScenePosition(hit).z;", + " this.undoSteps.push({ data: copyTerrainData(terrain.data), baseData: terrain.baseData });", + " const maxUndoSteps = Math.max(Math.floor(UNDO_STEPS_SAMPLES / terrain.data.heights.length), 2);", + " while (this.undoSteps.length > maxUndoSteps) this.undoSteps.shift();", + " }", + "", + " _endStroke() {", + " if (!this.isStroking) return;", + " this.isStroking = false;", + " this._save();", + " }", + "", + " _applyBrush(terrain, hit, radius) {", + " const now = performance.now();", + " const elapsedSeconds = Math.min((now - this.lastStrokeTime) / 1000, 0.1);", + " this.lastStrokeTime = now;", + " const { x, y } = terrain.toScenePosition(hit);", + " const amount = (this.strengthPercent / 100) * elapsedSeconds;", + " const depth = terrain.object.getDepth();", + " if (this.toolName === 'Raise') terrain.raise(x, y, x, y, radius, amount * depth * 0.5);", + " else if (this.toolName === 'Lower') terrain.raise(x, y, x, y, radius, -amount * depth * 0.5);", + " else if (this.toolName === 'Smooth') terrain.smooth(x, y, x, y, radius, amount * 10);", + " else if (this.toolName === 'Flatten') terrain.flatten(x, y, x, y, radius, this.flattenHeight, amount * 10);", + " else if (this.toolName.startsWith('Paint')) {", + " terrain.paint(x, y, x, y, radius, parseInt(this.toolName.slice(5), 10), amount * 8);", + " } else if (this.toolName === 'Hole') {", + " terrain.makeHole(x, y, x, y, radius);", + " } else if (this.toolName === 'FillHoles') {", + " terrain.fillHoles(x, y, x, y, radius);", + " } else if (this.toolName === 'Tint') {", + " terrain.tint(x, y, x, y, radius, gdjs.rgbOrHexToRGBColor(this.tintColor), amount * 8);", + " } else if (this.toolName === 'Wetness') {", + " terrain.wet(x, y, x, y, radius, this.wetnessPercent / 100, amount * 8);", + " }", + " // Drawn now, as the terrain was updated earlier in this frame.", + " terrain.applyChanges();", + " }", + "", + " _save() {", + " const { terrain, editor } = this;", + " if (!terrain || !editor) return;", + " editor.updateObjectProperties(terrain.object.getName(), { SculptData: terrain.save() });", + " }", + "", + " _undo() {", + " const undoStep = this.undoSteps.pop();", + " // The relief, the edits or the resolution may have changed since the stroke.", + " if (!this.terrain || !undoStep || undoStep.baseData !== this.terrain.baseData) {", + " this.undoSteps.length = 0;", + " return;", + " }", + " this.terrain.setData(undoStep.data);", + " this._save();", + " }", + "", + " /** @returns {Array} The settings of the tint or wetness brush, when it's used. */", + " _getColorToolItems() {", + " const { editor } = this;", + " if (this.toolName === 'Tint') {", + " if (editor && typeof editor.isToolbarItemTypeSupported === 'function' && editor.isToolbarItemTypeSupported('color')) {", + " return [", + " {", + " type: 'color',", + " id: 'TintColor',", + " tooltip: 'Color of the tint',", + " value: this.tintColor,", + " onChange: (color) => (this.tintColor = color),", + " },", + " ];", + " }", + " return tintPresets.map((color) => ({", + " type: 'button',", + " id: 'TintPreset' + color,", + " tooltip: color === '#ffffff' ? 'No tint' : 'Tint with this color',", + " color,", + " isActive: this.tintColor === color,", + " onClick: () => (this.tintColor = color),", + " }));", + " }", + " if (this.toolName === 'Wetness') {", + " return [", + " {", + " type: 'slider',", + " id: 'WetnessAmount',", + " tooltip: 'Wetness',", + " iconUrl: toolIcons.Wetness,", + " min: 0,", + " max: 100,", + " value: this.wetnessPercent,", + " onChange: (value) => (this.wetnessPercent = value),", + " },", + " ];", + " }", + " return [];", + " }", + "", + " /**", + " * @param {Terrain} terrain", + " * @returns {Array} The toolbar for the current state (the editor only", + " * updates what changed).", + " */", + " _getToolbarItems(terrain) {", + " const { object } = terrain;", + " const toolButton = (toolName, tooltip, color) => ({", + " type: 'button',", + " id: toolName,", + " tooltip,", + " iconUrl: toolIcons[toolName],", + " color,", + " isActive: this.toolName === toolName,", + " onClick: () => (this.toolName = this.toolName === toolName ? '' : toolName),", + " });", + " const layerColors = [object._getLayer1Color(), object._getLayer2Color(), object._getLayer3Color(), object._getLayer4Color()];", + " // Layers are shown with their color, tinting their texture.", + " const swatchColors = layerColors.map((color, index) =>", + " this._swatchColor", + " .set(gdjs.rgbOrHexStringToNumber(color))", + " .multiply(getAverageColor(terrain.renderer.uniforms['terrainLayerMap' + index].value))", + " .getHexString()", + " );", + " return [", + " toolButton('Raise', 'Raise the ground (drag on the terrain)'),", + " toolButton('Lower', 'Lower the ground'),", + " toolButton('Smooth', 'Smooth the ground'),", + " toolButton('Flatten', 'Flatten the ground to the height where the drag starts'),", + " { type: 'divider', id: 'PaintDivider' },", + " ...swatchColors.map((swatchColor, index) =>", + " toolButton('Paint' + (index + 1), 'Paint layer ' + (index + 1), '#' + swatchColor)", + " ),", + " toolButton('Hole', 'Make a hole, for the entrance of a tunnel or a cave'),", + " toolButton('FillHoles', 'Fill holes, with the layers of the ground around them'),", + " { type: 'divider', id: 'ColorDivider' },", + " toolButton('Tint', 'Tint the ground (white removes the tint)', this.tintColor),", + " toolButton('Wetness', 'Make the ground wet, or dry with a wetness of 0'),", + " ...this._getColorToolItems(),", + " { type: 'divider', id: 'BrushDivider' },", + " {", + " type: 'slider',", + " id: 'Size',", + " tooltip: 'Brush size',", + " iconUrl: toolIcons.Size,", + " min: 1,", + " max: 100,", + " value: this.sizePercent,", + " onChange: (value) => (this.sizePercent = value),", + " },", + " {", + " type: 'slider',", + " id: 'Strength',", + " tooltip: 'Brush strength',", + " iconUrl: toolIcons.Strength,", + " min: 5,", + " max: 100,", + " value: this.strengthPercent,", + " onChange: (value) => (this.strengthPercent = value),", + " },", + " { type: 'button', id: 'Undo', tooltip: 'Undo the last stroke on this terrain', iconUrl: toolIcons.Undo, onClick: () => this._undo() },", + " ];", + " }", + "}", + "", + "const editorTools = new TerrainEditorTools();", + "if (gdjs.registerInGameEditorPostStepCallback) {", + " gdjs.registerInGameEditorPostStepCallback((editor) => editorTools.update(editor));", + "}", + "", + "gdjs.__terrain3DExtension = {", + " Terrain,", + " // Used by the terrain grass and the scattered models.", + " getWorldScale,", + " getGrounds,", + " patchShaderCode,", + " createRandom,", + " findHighestGround,", + " GroundPlacement,", + " svgIcon,", + " toolIcons,", + " // Exposed for tests.", + " TerrainBodyUpdater,", + " TerrainData,", + " SampleRectangle,", + " brushes,", + " edits,", + " codec,", + " relief,", + "};" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [], + "description": "Define helper classes JavaScript code.", + "private": true + }, + { + "fullName": "Define grass helper classes", + "functionType": "Action", + "name": "DefineGrassHelperClasses", + "sentence": "Define grass helper classes JavaScript code", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (gdjs.__terrainGrassExtension) {", + " return;", + "}", + "// Blades grow on the grounds of the scene: the 3D terrains (see `getGrounds`", + "// in the terrain helper).", + "", + "// Size of the object inner area: instances are scaled from it.", + "const AREA_SIZE = 1000;", + "const AREA_DEPTH = 40;", + "const CHUNK_SIZE = 500;", + "const MAX_BENDING_OBJECTS = 8;", + "", + "/**", + " * A blade: a thin, tapered strip. Its width and height are 1: blades are", + " * scaled by the shader.", + " */", + "const createBladeGeometry = () => {", + " const halfWidths = [0.5, 0.42, 0.28, 0];", + " const heights = [0, 0.35, 0.7, 1];", + " const positions = [];", + " halfWidths.forEach((halfWidth, level) => {", + " positions.push(-halfWidth, 0, heights[level]);", + " if (halfWidth > 0) positions.push(halfWidth, 0, heights[level]);", + " });", + " const indices = [0, 1, 2, 1, 3, 2, 2, 3, 4, 3, 5, 4, 4, 5, 6];", + " const geometry = new THREE.InstancedBufferGeometry();", + " geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));", + " geometry.setAttribute('normal', new THREE.Float32BufferAttribute(new Array(positions.length).fill(0), 3));", + " geometry.setIndex(indices);", + " return geometry;", + "};", + "", + "const vertexShaderDeclarations = `", + "attribute vec4 grassRoot;", + "attribute vec2 grassShape;", + "// The tint of the ground at the root (linear, from 0 to 1).", + "attribute vec3 grassGroundTint;", + "uniform mat3 grassWorldToLocal;", + "uniform float grassWorldScale;", + "uniform float grassBladeWidth;", + "uniform float grassTime;", + "uniform float grassWindStrength;", + "uniform float grassWindSpeed;", + "uniform vec4 grassBenders[${MAX_BENDING_OBJECTS}];", + "uniform vec3 grassBaseColor;", + "uniform vec3 grassTipColor;", + "varying vec3 vGrassColor;", + "`;", + "const vertexShaderPosition = `", + "// Blades are shaped in scene units (with Y flipped like the 3D world), then", + "// moved back in the (scaled) object space.", + "float bladeHeight = position.z;", + "float grassAngle = grassRoot.w;", + "float bladeWidth = position.x * grassBladeWidth;", + "vec3 blade = vec3(", + " bladeWidth * cos(grassAngle),", + " bladeWidth * sin(grassAngle),", + " bladeHeight * grassShape.x", + ");", + "vec3 rootWorld = (modelMatrix * vec4(grassRoot.xyz, 1.0)).xyz * grassWorldScale;", + "// Tips bend more than the base.", + "float bendFactor = bladeHeight * bladeHeight * grassShape.x;", + "float gust = sin(grassTime * grassWindSpeed + rootWorld.x * 0.013 + rootWorld.y * 0.011);", + "float flutter = sin(grassTime * grassWindSpeed * 2.7 + grassShape.y * 40.0) * 0.3;", + "blade.xy += vec2(0.8, 0.5) * (gust + flutter + 0.6) * grassWindStrength * 0.35 * bendFactor;", + "for (int i = 0; i < ${MAX_BENDING_OBJECTS}; i++) {", + " vec4 bender = grassBenders[i];", + " if (bender.z <= 0.0) continue;", + " vec2 away = rootWorld.xy - bender.xy;", + " float distanceToBender = length(away);", + " float push = 1.0 - smoothstep(bender.z * 0.3, bender.z, distanceToBender);", + " if (push > 0.0) {", + " blade.xy += normalize(away + vec2(0.001)) * push * bendFactor * 0.9;", + " blade.z *= 1.0 - push * 0.5;", + " }", + "}", + "vec3 transformed = grassRoot.xyz + grassWorldToLocal * (blade / grassWorldScale);", + "vGrassColor = mix(grassBaseColor, grassTipColor, bladeHeight) * (0.85 + 0.3 * grassShape.y) * grassGroundTint;", + "`;", + "const vertexShaderNormal = `", + "// Blades are lit like the ground below them.", + "transformedNormal = normalize((viewMatrix * vec4(0.0, 0.0, 1.0, 0.0)).xyz);", + "`;", + "const fragmentShaderDeclarations = `", + "varying vec3 vGrassColor;", + "`;", + "const fragmentShaderColor = `", + "diffuseColor.rgb *= vGrassColor;", + "`;", + "// Both faces of blades are lit like the ground (not flipped for back faces).", + "const fragmentShaderNormal = `", + "#include ", + "normal = normalize(vNormal);", + "nonPerturbedNormal = normal;", + "`;", + "", + "const { getWorldScale, patchShaderCode, createRandom, findHighestGround, GroundPlacement } = gdjs.__terrain3DExtension;", + "", + "/**", + " * The grass of a terrain grass object: blades drawn with GPU instancing, in chunks", + " * that are hidden when off-screen and thinned out with the distance. They grow", + " * on the grounds below, like 3D terrains (only where a given layer is painted).", + " */", + "class Grass {", + " /** @param {gdjs.CustomRuntimeObject3D} object */", + " constructor(object) {", + " this.object = object;", + " this.group = new THREE.Group();", + " object.get3DRendererObject().add(this.group);", + " this.bladeGeometry = createBladeGeometry();", + " /** @type {Array<{mesh: THREE.Mesh, blades: number, center: THREE.Vector3}>} */", + " this.chunks = [];", + " this.placement = new GroundPlacement(object.getRuntimeScene());", + " this._groundAtBlade = { ground: null, z: 0 };", + " this.uniforms = {", + " grassWorldToLocal: { value: new THREE.Matrix3() },", + " // Scene units by 3D world unit.", + " grassWorldScale: { value: 1 },", + " grassBladeWidth: { value: 1 },", + " grassTime: { value: 0 },", + " grassWindStrength: { value: 1 },", + " grassWindSpeed: { value: 1 },", + " grassBenders: { value: Array.from({ length: MAX_BENDING_OBJECTS }, () => new THREE.Vector4()) },", + " grassBaseColor: { value: new THREE.Color() },", + " grassTipColor: { value: new THREE.Color() },", + " };", + " this.material = new THREE.MeshStandardMaterial({ roughness: 1, metalness: 0, side: THREE.DoubleSide });", + " this.material.onBeforeCompile = (shader) => {", + " Object.assign(shader.uniforms, this.uniforms);", + " shader.vertexShader = patchShaderCode(shader.vertexShader, [", + " ['#include ', '#include \\n' + vertexShaderDeclarations],", + " ['#include ', vertexShaderPosition],", + " ['#include ', '#include \\n' + vertexShaderNormal],", + " ], 'TerrainGrass');", + " shader.fragmentShader = patchShaderCode(shader.fragmentShader, [", + " ['#include ', '#include \\n' + fragmentShaderDeclarations],", + " ['#include ', '#include \\n' + fragmentShaderColor],", + " ['#include ', fragmentShaderNormal],", + " ], 'TerrainGrass');", + " };", + " this.material.customProgramCacheKey = () => 'TerrainGrass';", + "", + " this._matrix = new THREE.Matrix4();", + " this._cameraPosition = new THREE.Vector3();", + " this._chunkPosition = new THREE.Vector3();", + " this._scale = new THREE.Vector3();", + " this.updateFromProperties();", + " }", + "", + " updateFromProperties() {", + " const { object } = this;", + " this.uniforms.grassBaseColor.value.set(gdjs.rgbOrHexStringToNumber(object._getBaseColor()));", + " this.uniforms.grassTipColor.value.set(gdjs.rgbOrHexStringToNumber(object._getTipColor()));", + " this.uniforms.grassWindStrength.value = object._getWindStrength();", + " this.uniforms.grassWindSpeed.value = object._getWindSpeed();", + " this.uniforms.grassBladeWidth.value = Math.max(object._getBladeWidth(), 0);", + " this.material.needsUpdate = true;", + " this.placement.placeAgain();", + " }", + "", + " _getPlacementKey() {", + " const { object } = this;", + " return [", + " object.getX(), object.getY(), object.getZ(), object.getWidth(), object.getHeight(), object.getDepth(),", + " object._getDensity(), object._getBladeHeight(), object._getGroundLayer(), object._getMaxSlope(),", + " object._getSeed(),", + " ].join('|');", + " }", + "", + " /** Place the blades again, for example after the ground or the object changed. */", + " rebuild() {", + " const grounds = Array.from(this.placement.grounds);", + " for (const chunk of this.chunks) {", + " chunk.mesh.removeFromParent();", + " chunk.mesh.geometry.dispose();", + " }", + " this.chunks.length = 0;", + "", + " const { object } = this;", + " const width = object.getWidth();", + " const height = object.getHeight();", + " const scaleX = width / AREA_SIZE;", + " const scaleY = height / AREA_SIZE;", + " const scaleZ = Math.max(object.getDepth() / AREA_DEPTH, 0.0001);", + " const bladesPerChunk = Math.round((Math.max(object._getDensity(), 0) * CHUNK_SIZE * CHUNK_SIZE) / 10000);", + " const groundLayer = parseInt(object._getGroundLayer(), 10) || 0;", + " const maxSlope = object._getMaxSlope();", + " const chunksX = Math.max(Math.ceil(width / CHUNK_SIZE), 1);", + " const chunksY = Math.max(Math.ceil(height / CHUNK_SIZE), 1);", + " const nextRandom = createRandom(object._getSeed() * 7919 + 1);", + " const bladeHeight = Math.max(object._getBladeHeight(), 0);", + "", + " for (let chunkY = 0; chunkY < chunksY; chunkY++) {", + " for (let chunkX = 0; chunkX < chunksX; chunkX++) {", + " const roots = new Float32Array(bladesPerChunk * 4);", + " const shapes = new Float32Array(bladesPerChunk * 2);", + " const groundTints = new Uint8Array(bladesPerChunk * 3).fill(255);", + " let blades = 0;", + " let minZ = Infinity;", + " let maxZ = -Infinity;", + " for (let attempt = 0; attempt < bladesPerChunk; attempt++) {", + " // In scene units, relative to the object.", + " const x = (chunkX + nextRandom()) * CHUNK_SIZE;", + " const y = (chunkY + nextRandom()) * CHUNK_SIZE;", + " const angle = nextRandom() * Math.PI * 2;", + " const heightFactor = 0.6 + 0.8 * nextRandom();", + " const tint = nextRandom();", + " const growth = nextRandom();", + " if (x > width || y > height) continue;", + " const sceneX = object.getX() + x;", + " const sceneY = object.getY() + y;", + " const { ground, z } = findHighestGround(grounds, sceneX, sceneY, this._groundAtBlade);", + " const groundZ = ground ? z : object.getZ();", + " if (ground) {", + " if (groundLayer > 0 && growth > ground.getLayerWeightAt(sceneX, sceneY, groundLayer)) continue;", + " if (ground.getSlopeAt(sceneX, sceneY) > maxSlope) continue;", + " if (typeof ground.getTintAt === 'function') {", + " const tint = ground.getTintAt(sceneX, sceneY);", + " for (let channel = 0; channel < 3; channel++) {", + " groundTints[blades * 3 + channel] = Math.round(255 * Math.pow(tint[channel] / 255, 2.2));", + " }", + " }", + " }", + " const localZ = (groundZ - object.getZ()) / scaleZ;", + " roots[blades * 4] = x / scaleX;", + " roots[blades * 4 + 1] = y / scaleY;", + " roots[blades * 4 + 2] = localZ;", + " roots[blades * 4 + 3] = angle;", + " shapes[blades * 2] = heightFactor * bladeHeight;", + " shapes[blades * 2 + 1] = tint;", + " minZ = Math.min(minZ, localZ);", + " maxZ = Math.max(maxZ, localZ);", + " blades++;", + " }", + " if (blades === 0) continue;", + "", + " const geometry = new THREE.InstancedBufferGeometry();", + " geometry.index = this.bladeGeometry.index;", + " geometry.setAttribute('position', this.bladeGeometry.attributes.position);", + " geometry.setAttribute('normal', this.bladeGeometry.attributes.normal);", + " geometry.setAttribute('grassRoot', new THREE.InstancedBufferAttribute(roots.subarray(0, blades * 4), 4));", + " geometry.setAttribute('grassShape', new THREE.InstancedBufferAttribute(shapes.subarray(0, blades * 2), 2));", + " geometry.setAttribute('grassGroundTint', new THREE.InstancedBufferAttribute(groundTints.subarray(0, blades * 3), 3, true));", + " geometry.instanceCount = blades;", + " // The furthest tips can go with the wind and the bending objects (see the shader).", + " const maxBladeHeight = bladeHeight * 1.4;", + " const lean =", + " (0.35 * 1.9 * 0.943 * Math.max(object._getWindStrength(), 0) + 0.9) * maxBladeHeight +", + " Math.max(object._getBladeWidth(), 0) / 2;", + " const leanX = lean / scaleX;", + " const leanY = lean / scaleY;", + " geometry.boundingBox = new THREE.Box3(", + " new THREE.Vector3((chunkX * CHUNK_SIZE) / scaleX - leanX, (chunkY * CHUNK_SIZE) / scaleY - leanY, minZ),", + " new THREE.Vector3(", + " Math.min((chunkX + 1) * CHUNK_SIZE, width) / scaleX + leanX,", + " Math.min((chunkY + 1) * CHUNK_SIZE, height) / scaleY + leanY,", + " maxZ + (bladeHeight * 1.4) / scaleZ", + " )", + " );", + " geometry.boundingSphere = geometry.boundingBox.getBoundingSphere(new THREE.Sphere());", + " const mesh = new THREE.Mesh(geometry, this.material);", + " mesh.receiveShadow = true;", + " // Raycasts would only test one blade: the selection plane is used instead.", + " mesh.raycast = () => {};", + " this.group.add(mesh);", + " this.chunks.push({ mesh, blades, center: geometry.boundingSphere.center.clone() });", + " }", + " }", + " }", + "", + " _updateBendingObjects() {", + " const benders = this.uniforms.grassBenders.value;", + " const objectName = this.object._getBendingObject();", + " const bendingObjects = objectName ? this.object.getInstanceContainer().getObjects(objectName) || [] : [];", + " for (let index = 0; index < MAX_BENDING_OBJECTS; index++) {", + " const bendingObject = bendingObjects[index];", + " if (!bendingObject) {", + " benders[index].set(0, 0, 0, 0);", + " continue;", + " }", + " // Blades are pushed in the 3D scene, which is flipped on Y.", + " const radius = Math.max(bendingObject.getWidth(), bendingObject.getHeight()) * 1.2;", + " benders[index].set(bendingObject.getCenterXInScene(), -bendingObject.getCenterYInScene(), radius, 0);", + " }", + " }", + "", + " /**", + " * @param {number} elapsedSeconds", + " * @param {number} time In seconds, the same for all grass objects.", + " */", + " update(elapsedSeconds, time, camera) {", + " if (this.placement.shouldPlaceAgain(elapsedSeconds, this._getPlacementKey())) {", + " this.rebuild();", + " }", + "", + " this.uniforms.grassTime.value = time;", + " this.uniforms.grassWorldScale.value = getWorldScale(this.object);", + " // The object transformation is applied just before rendering, it's needed now.", + " this.object.getRenderer().ensureUpToDate();", + " this.group.updateWorldMatrix(true, false);", + " this.uniforms.grassWorldToLocal.value.setFromMatrix4(this._matrix.copy(this.group.matrixWorld).invert());", + " this._updateBendingObjects();", + " if (!camera) return;", + "", + " // Far chunks are hidden, and thinned out before (in 3D world units).", + " const fadeDistance = Math.max(this.object._getFadeDistance(), 1) / getWorldScale(this.object);", + " this._cameraPosition.setFromMatrixPosition(camera.matrixWorld);", + " for (const chunk of this.chunks) {", + " const distance = this._chunkPosition", + " .copy(chunk.center)", + " .applyMatrix4(this.group.matrixWorld)", + " .distanceTo(this._cameraPosition);", + " const density = gdjs.evtTools.common.clamp((fadeDistance - distance) / (fadeDistance * 0.5), 0, 1);", + " chunk.mesh.visible = density > 0;", + " chunk.mesh.geometry.instanceCount = Math.ceil(chunk.blades * density);", + " }", + " }", + "", + " dispose() {", + " for (const chunk of this.chunks) {", + " chunk.mesh.geometry.dispose();", + " }", + " this.bladeGeometry.dispose();", + " this.material.dispose();", + " this.group.removeFromParent();", + " }", + "}", + "", + "gdjs.__terrainGrassExtension = { Grass };" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [], + "description": "Define grass helper classes JavaScript code.", + "private": true + }, + { + "fullName": "Define scattered models helper classes", + "functionType": "Action", + "name": "DefineScatterHelperClasses", + "sentence": "Define scattered models helper classes JavaScript code", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (gdjs.__terrainScatterExtension) {", + " return;", + "}", + "// Copies grow on the grounds of the scene, like the terrain grass (see", + "// `getGrounds` in the terrain helper).", + "", + "const {", + " getWorldScale,", + " getGrounds,", + " patchShaderCode,", + " createRandom,", + " findHighestGround,", + " GroundPlacement,", + " SampleRectangle,", + " brushes,", + " codec,", + " svgIcon,", + " toolIcons,", + "} = gdjs.__terrain3DExtension;", + "", + "// Size of the object inner area: instances are scaled from it.", + "const AREA_SIZE = 2000;", + "const AREA_DEPTH = 200;", + "// The painted density is stored in a grid of this many cells on each side,", + "// whatever the size of the object.", + "const MASK_RESOLUTION = 256;", + "const MASK_FORMAT_VERSION = '1';", + "// Copies are hidden in chunks: big chunks mean fewer draw calls, small ones", + "// hide more copies out of the screen.", + "const MIN_CHUNK_SIZE = 500;", + "const MAX_CHUNK_SIZE = 2000;", + "// Copies shrink to nothing over this part of the draw distance, instead of popping out.", + "const FADE_PART = 0.15;", + "const MAX_COPIES = 200000;", + "// Prisms of the trunk collisions.", + "const TRUNK_SIDES = 6;", + "", + "const clamp01 = (value) => (value < 0 ? 0 : value > 1 ? 1 : value);", + "", + "/**", + " * @returns {number} A number for a cell of the copies grid, changing with the seed.", + " */", + "const hashCell = (seed, i, j) => {", + " let h = Math.imul(i, 374761393) + Math.imul(j, 668265263) + Math.imul(seed, 982451653);", + " h = Math.imul(h ^ (h >>> 13), 1274126177);", + " return (h ^ (h >>> 16)) >>> 0;", + "};", + "", + "/**", + " * How dense copies are, from 0 to 1, in a grid covering the object: painted", + " * with the editor brushes, the edits or events.", + " */", + "class DensityMask {", + " /** @param {number} fillValue From 0 to 255. */", + " constructor(fillValue) {", + " // `resolution` and `size` let terrain brushes paint it.", + " this.resolution = MASK_RESOLUTION;", + " this.size = MASK_RESOLUTION + 1;", + " this.values = new Uint8Array(this.size * this.size).fill(fillValue);", + " }", + "", + " /**", + " * @param {number} u From 0 to 1, along the object width.", + " * @param {number} v From 0 to 1, along the object height.", + " * @returns {number} The density, from 0 to 1, interpolated between samples.", + " */", + " sample(u, v) {", + " const x = clamp01(u) * this.resolution;", + " const y = clamp01(v) * this.resolution;", + " const i = Math.min(Math.floor(x), this.resolution - 1);", + " const j = Math.min(Math.floor(y), this.resolution - 1);", + " const fx = x - i;", + " const fy = y - j;", + " const value = (i, j) => this.values[j * this.size + i];", + " const top = value(i, j) + fx * (value(i + 1, j) - value(i, j));", + " const bottom = value(i, j + 1) + fx * (value(i + 1, j + 1) - value(i, j + 1));", + " return (top + fy * (bottom - top)) / 255;", + " }", + "", + " /**", + " * Bring the density towards a target around a segment: fully up to 70%", + " * of the radius, then fading out (forests have dense centers and soft edges).", + " * @param {{cellWidth: number, cellHeight: number, ax: number, ay: number, bx: number, by: number, radius: number}} stroke", + " * @param {number} density The target, from 0 to 1.", + " * @param {number} strength From 0 to 1.", + " * @param {SampleRectangle} changedSamples", + " */", + " paint(stroke, density, strength, changedSamples) {", + " const { values } = this;", + " const target = Math.round(clamp01(density) * 255);", + " brushes.forEachSample(", + " this,", + " stroke,", + " (index, falloff) => {", + " // The falloff of terrain brushes is (1 - distance² / radius²)².", + " const weight = clamp01(2 * Math.sqrt(falloff));", + " // Rounded towards the target, so that light strokes still paint.", + " const change = (target - values[index]) * clamp01(weight * strength);", + " values[index] += change > 0 ? Math.ceil(change) : Math.floor(change);", + " },", + " changedSamples", + " );", + " }", + "", + " /** @returns {DensityMask} */", + " clone() {", + " const mask = new DensityMask(0);", + " mask.values.set(this.values);", + " return mask;", + " }", + "}", + "", + "/**", + " * Edits written in the Edits property: a JSON list of brush strokes, like", + " * `[{\"tool\": \"add\", \"x\": 0.5, \"y\": 0.5, \"radius\": 0.2}]`. Positions and", + " * radius are fractions of the object size, like the terrain edits.", + " */", + "const applyEdits = (mask, text) => {", + " if (!text.trim()) return;", + " let editList;", + " try {", + " editList = JSON.parse(text);", + " } catch (error) {", + " console.warn('The edits of scattered models are not valid JSON: ' + error.message);", + " return;", + " }", + " if (!Array.isArray(editList)) {", + " console.warn('The edits of scattered models must be a JSON list (like [{\"tool\": \"add\", ...}]).');", + " return;", + " }", + " const numberOr = (value, defaultValue) => (typeof value === 'number' && isFinite(value) ? value : defaultValue);", + " const changedSamples = new SampleRectangle();", + " for (const edit of editList) {", + " if (!edit || typeof edit !== 'object') continue;", + " const x = numberOr(edit.x, 0.5);", + " const y = numberOr(edit.y, 0.5);", + " const stroke = {", + " cellWidth: 1 / mask.resolution,", + " cellHeight: 1 / mask.resolution,", + " ax: x,", + " ay: y,", + " bx: numberOr(edit.toX, x),", + " by: numberOr(edit.toY, y),", + " radius: numberOr(edit.radius, 0.1),", + " };", + " const strength = clamp01(numberOr(edit.strength, 1));", + " if (edit.tool === 'add') {", + " mask.paint(stroke, numberOr(edit.density, 1), strength, changedSamples);", + " } else if (edit.tool === 'erase') {", + " mask.paint(stroke, 0, strength, changedSamples);", + " } else {", + " console.warn('Unknown tool in the edits of scattered models: \"' + edit.tool + '\" (use add or erase).');", + " }", + " }", + "};", + "", + "/**", + " * The density painted in the editor, stored as its difference with the", + " * density from the properties (the coverage and the edits), like the terrain", + " * sculpt data.", + " */", + "const maskCodec = {", + " /**", + " * @param {DensityMask} mask", + " * @param {DensityMask} baseMask", + " */", + " encode(mask, baseMask) {", + " const changes = new Int32Array(mask.values.length);", + " let hasChanges = false;", + " for (let index = 0; index < changes.length; index++) {", + " changes[index] = mask.values[index] - baseMask.values[index];", + " if (changes[index] !== 0) hasChanges = true;", + " }", + " if (!hasChanges) return '';", + " return MASK_FORMAT_VERSION + ';' + MASK_RESOLUTION + ';' + codec.encodeGrids([changes], mask.size);", + " },", + "", + " /**", + " * @param {string} text", + " * @param {DensityMask} baseMask", + " * @returns {DensityMask | null} null if the text is empty or not valid.", + " */", + " decode(text, baseMask) {", + " if (!text) return null;", + " const parts = text.split(';');", + " if (parts[0] !== MASK_FORMAT_VERSION || parseInt(parts[1], 10) !== MASK_RESOLUTION) return null;", + " try {", + " const mask = baseMask.clone();", + " const [changes] = codec.decodeGrids(parts[2], mask.size, 1);", + " for (let index = 0; index < changes.length; index++) {", + " // The base may have changed since the data was saved.", + " mask.values[index] = Math.min(Math.max(mask.values[index] + changes[index], 0), 255);", + " }", + " return mask;", + " } catch (error) {", + " console.error('The painted data of scattered models could not be read:', error);", + " return null;", + " }", + " },", + "};", + "", + "const fadeShaderDeclarations = `", + "uniform float scatterDrawDistance;", + "`;", + "// Instance matrices are applied after this: copies shrink towards their base.", + "const fadeShaderCode = `", + "#ifdef USE_INSTANCING", + "vec3 scatterCopyPosition = (modelMatrix * instanceMatrix * vec4(0.0, 0.0, 0.0, 1.0)).xyz;", + "transformed *= clamp(", + " (scatterDrawDistance - distance(scatterCopyPosition, cameraPosition)) / (scatterDrawDistance * ${FADE_PART}),", + " 0.0,", + " 1.0", + ");", + "#endif", + "`;", + "", + "const flipTriangles = (geometry) => {", + " const index = geometry.index;", + " for (let offset = 0; offset + 2 < index.count; offset += 3) {", + " const second = index.getX(offset + 1);", + " index.setX(offset + 1, index.getX(offset + 2));", + " index.setX(offset + 2, second);", + " }", + "};", + "", + "/**", + " * The parts of a model, ready to be drawn with instancing: each glTF", + " * primitive with its material. Geometries are moved so that the model base", + " * is at the origin, its height is 1 and it stands along Z (like 3D model", + " * objects with a 90° rotation around X).", + " * @param {THREE.Object3D} modelScene", + " * @param {object} uniforms Uniforms added to the materials.", + " * @returns {{parts: Array<{geometry: THREE.BufferGeometry, material: THREE.Material | THREE.Material[]}>, width: number, depth: number}}", + " */", + "const createModelParts = (modelScene, uniforms) => {", + " modelScene.updateMatrixWorld(true);", + " const rootInverse = modelScene.matrixWorld.clone().invert();", + " const standUp = new THREE.Matrix4().makeRotationX(Math.PI / 2);", + " /** @type {Array<{mesh: THREE.Mesh, matrix: THREE.Matrix4}>} */", + " const meshes = [];", + " const box = new THREE.Box3();", + " modelScene.traverse((node) => {", + " if (!node.isMesh || !node.geometry || !node.geometry.attributes.position) return;", + " const matrix = standUp.clone().multiply(rootInverse).multiply(node.matrixWorld);", + " if (!node.geometry.boundingBox) node.geometry.computeBoundingBox();", + " box.union(node.geometry.boundingBox.clone().applyMatrix4(matrix));", + " meshes.push({ mesh: node, matrix });", + " });", + " if (box.isEmpty()) return { parts: [], width: 0, depth: 0 };", + "", + " const height = Math.max(box.max.z - box.min.z, 0.000001);", + " // The Y axis of the scene goes down: the model is flipped, like 3D model objects.", + " const normalization = new THREE.Matrix4()", + " .makeScale(1 / height, -1 / height, 1 / height)", + " .multiply(", + " new THREE.Matrix4().makeTranslation(", + " -(box.min.x + box.max.x) / 2,", + " -(box.min.y + box.max.y) / 2,", + " -box.min.z", + " )", + " );", + " const parts = meshes.map(({ mesh, matrix }) => {", + " let geometry = mesh.geometry.clone();", + " if (!geometry.index) {", + " const indices = Array.from({ length: geometry.attributes.position.count }, (_, index) => index);", + " geometry.setIndex(indices);", + " }", + " geometry.applyMatrix4(normalization.clone().multiply(matrix));", + " // The flip turned the faces inside out.", + " flipTriangles(geometry);", + " geometry.computeBoundingSphere();", + " geometry.computeBoundingBox();", + " const patchMaterial = (originalMaterial) => {", + " const material = originalMaterial.clone();", + " // Like 3D model objects (\"Standard without metalness\"), which look better without an environment map.", + " if (material.metalness) material.metalness = 0;", + " material.onBeforeCompile = (shader) => {", + " Object.assign(shader.uniforms, uniforms);", + " shader.vertexShader = patchShaderCode(shader.vertexShader, [", + " ['#include ', '#include \\n' + fadeShaderDeclarations],", + " ['#include ', '#include \\n' + fadeShaderCode],", + " ], 'ScatteredModels');", + " };", + " material.customProgramCacheKey = () => 'ScatteredModels';", + " return material;", + " };", + " const material = Array.isArray(mesh.material) ? mesh.material.map(patchMaterial) : patchMaterial(mesh.material);", + " return { geometry, material };", + " });", + " return {", + " parts,", + " width: (box.max.x - box.min.x) / height,", + " depth: (box.max.y - box.min.y) / height,", + " };", + "};", + "", + "/**", + " * A copy of the model: its base position in the scene, its scale (the", + " * height of the copy) and the key of its cell in the grid of copies.", + " * @typedef {{x: number, y: number, z: number, angle: number, height: number, key: number}} Copy", + " */", + "", + "/**", + " * The scattered models of an object: copies of a 3D model placed on the", + " * grounds of the scene, drawn with GPU instancing in chunks that are hidden", + " * when far or off-screen.", + " */", + "class Scatter {", + " /** @param {gdjs.CustomRuntimeObject3D} object */", + " constructor(object) {", + " this.object = object;", + " this.group = new THREE.Group();", + " object.get3DRendererObject().add(this.group);", + " /** @type {Array<{firstCellI: number, firstCellJ: number, meshes: THREE.InstancedMesh[], copies: Copy[], center: THREE.Vector3, radius: number}>} */", + " this.chunks = [];", + " /**", + " * The grid of copies: one copy at most in each cell, cells grouped in chunks.", + " * @type {{spacing: number, cellsPerChunk: number, cellsX: number, cellsY: number} | null}", + " */", + " this.grid = null;", + " this.placement = new GroundPlacement(object.getRuntimeScene());", + " this.uniforms = {", + " // In 3D world units.", + " scatterDrawDistance: { value: 1 },", + " };", + " /** @type {{parts: Array<{geometry: THREE.BufferGeometry, material: THREE.Material | THREE.Material[]}>, width: number, depth: number}} */", + " this.model = { parts: [], width: 0, depth: 0 };", + " this.modelResourceName = '';", + " this.loadingModelResourceName = '';", + " /**", + " * Copies removed by events (for example, cut trees), by key.", + " * @type {Map}", + " */", + " this.removedCopies = new Map();", + " /** Increased each time copies change, for the collisions. */", + " this.version = 0;", + " this.isDisposed = false;", + " /** The last value of the painted data property read or saved. */", + " this.paintData = '';", + " /**", + " * Values saved by the editor tools: the editor sends them back with a", + " * hot-reload, possibly after newer ones were saved.", + " * @type {string[]}", + " */", + " this.savedPaintData = [];", + " this.maskSource = '';", + " /** @type {DensityMask} */", + " this.baseMask = new DensityMask(255);", + " /** @type {DensityMask} */", + " this.mask = this.baseMask;", + " this.changedSamples = new SampleRectangle();", + " this._stroke = { cellWidth: 1, cellHeight: 1, ax: 0, ay: 0, bx: 0, by: 0, radius: 0 };", + " this._groundAtCopy = { ground: null, z: 0 };", + " this._matrix = new THREE.Matrix4();", + " this._raycastSphere = new THREE.Sphere();", + " this._raycastRay = new THREE.Ray();", + " this._raycastBox = new THREE.Box3();", + " this._raycastInverseMatrix = new THREE.Matrix4();", + " this._raycastScale = new THREE.Vector3();", + " /** Placed on a copy to test the triangles of the model. */", + " this._raycastMesh = new THREE.Mesh();", + " /** @type {THREE.Intersection[]} */", + " this._raycastHits = [];", + " this._color = new THREE.Color();", + " this._tint = new THREE.Color();", + " this._cameraPosition = new THREE.Vector3();", + " this._chunkPosition = new THREE.Vector3();", + " /**", + " * Trunks, for the 3D physics behavior and navigation meshes. Always given,", + " * empty without trunk collisions: otherwise these behaviors would use the", + " * object box, covering the whole area.", + " * @type {gdjs.Surface}", + " */", + " this.surface = {", + " getVersion: () => this.version,", + " getChangedArea: () => null,", + " getTriangles: () => this._getTrunkTriangles(),", + " getHeightField: () => null,", + " };", + " /** The trunk collision properties, changing the surface. */", + " this.trunkKey = '';", + " this.loadMaskFromProperties();", + " this.updateFromProperties();", + " // Before the behaviors create their shapes.", + " this._updateSurface();", + " }", + "", + " _getMaskSource() {", + " return this.object._getCoverage() + '|' + this.object._getEdits();", + " }", + "", + " loadMaskFromProperties() {", + " const { object } = this;", + " this.maskSource = this._getMaskSource();", + " this.paintData = object._getPaintData();", + " this.baseMask = new DensityMask(object._getCoverage() === 'Painted' ? 0 : 255);", + " applyEdits(this.baseMask, object._getEdits());", + " this.mask = maskCodec.decode(this.paintData, this.baseMask) || this.baseMask.clone();", + " this.placement.placeAgain();", + " }", + "", + " onHotReloading() {", + " const paintData = this.object._getPaintData();", + " const savedIndex = this.savedPaintData.indexOf(paintData);", + " if (savedIndex !== -1 && this._getMaskSource() === this.maskSource) {", + " // The object already has this data, or newer changes.", + " this.savedPaintData.splice(0, savedIndex + 1);", + " } else if (paintData !== this.paintData || this._getMaskSource() !== this.maskSource) {", + " this.savedPaintData.length = 0;", + " this.loadMaskFromProperties();", + " }", + " this.updateFromProperties();", + " }", + "", + " /** @returns {string} The data to save in the painted data property. */", + " save() {", + " this.paintData = maskCodec.encode(this.mask, this.baseMask);", + " this.savedPaintData.push(this.paintData);", + " return this.paintData;", + " }", + "", + " updateFromProperties() {", + " const { object } = this;", + " const modelResourceName = object._getModel();", + " if (modelResourceName !== this.modelResourceName) {", + " this.modelResourceName = modelResourceName;", + " this._loadModel();", + " }", + " this.placement.placeAgain();", + " }", + "", + " _disposeModel() {", + " for (const part of this.model.parts) {", + " part.geometry.dispose();", + " for (const material of Array.isArray(part.material) ? part.material : [part.material]) {", + " material.dispose();", + " }", + " }", + " this.model = { parts: [], width: 0, depth: 0 };", + " }", + "", + " _loadModel() {", + " this._disposeChunks(this.chunks);", + " this._disposeModel();", + " const resourceName = this.modelResourceName;", + " if (!resourceName) return;", + " const game = this.object.getRuntimeScene().getGame();", + " const modelManager = game.getModel3DManager();", + " const gltf = modelManager.getModel(resourceName);", + " if (gltf === modelManager._invalidModel) {", + " // Not loaded yet, for example a model just chosen in the editor.", + " if (this.loadingModelResourceName === resourceName) return;", + " this.loadingModelResourceName = resourceName;", + " game.getResourceLoader()", + " .loadResource(resourceName)", + " .then(() => {", + " if (this.isDisposed || this.modelResourceName !== resourceName) return;", + " this.loadingModelResourceName = '';", + " if (modelManager.getModel(resourceName) !== modelManager._invalidModel) {", + " this._loadModel();", + " this.placement.placeAgain();", + " }", + " })", + " .catch(() => {});", + " return;", + " }", + " this.model = createModelParts(gltf.scene, this.uniforms);", + " }", + "", + " _getPlacementKey() {", + " const { object } = this;", + " return [", + " object.getX(), object.getY(), object.getZ(), object.getWidth(), object.getHeight(), object.getDepth(),", + " object._getDensity(), object._getSize(), object._getMinScale(), object._getMaxScale(),", + " object._getRandomRotation(), object._getRandomTint(), object._getGroundLayer(), object._getMaxSlope(),", + " object._getMinGroundZ(), object._getMaxGroundZ(), object._getSeed(), object._getDrawDistance(),", + " object._getCastShadow(), object._getReceiveShadow(), this.model.parts.length,", + " ].join('|');", + " }", + "", + " _getChunkSize() {", + " return Math.min(Math.max(this.object._getDrawDistance() / 4, MIN_CHUNK_SIZE), MAX_CHUNK_SIZE);", + " }", + "", + " /** @returns {number} The distance between copies (one copy per cell of this size at most). */", + " _getSpacing() {", + " const { object } = this;", + " const density = Math.max(object._getDensity(), 0);", + " if (density <= 0) return 0;", + " const spacing = 100 / Math.sqrt(density);", + " // Too many cells would freeze the game: the density is lowered.", + " const cellCount = (object.getWidth() / spacing) * (object.getHeight() / spacing);", + " return cellCount > MAX_COPIES ? spacing * Math.sqrt(cellCount / MAX_COPIES) : spacing;", + " }", + "", + " /**", + " * Find the copy of a cell of the grid, if there is one.", + " * @returns {Copy | null}", + " */", + " _placeCopy(cellI, cellJ, spacing, grounds) {", + " const { object } = this;", + " const width = object.getWidth();", + " const height = object.getHeight();", + " const nextRandom = createRandom(hashCell(Math.floor(object._getSeed()), cellI, cellJ));", + " // Always drawn in the same order, so that a copy stays the same when properties change.", + " const x = (cellI + nextRandom()) * spacing;", + " const y = (cellJ + nextRandom()) * spacing;", + " const presence = nextRandom();", + " const growth = nextRandom();", + " const scaleFactor = nextRandom();", + " const angle = nextRandom() * Math.PI * 2;", + " if (x > width || y > height) return null;", + " if (presence >= this.mask.sample(x / width, y / height)) return null;", + " const key = cellJ * 65536 + cellI;", + " if (this.removedCopies.has(key)) return null;", + "", + " const sceneX = object.getX() + x;", + " const sceneY = object.getY() + y;", + " const { ground, z } = findHighestGround(grounds, sceneX, sceneY, this._groundAtCopy);", + " const groundZ = ground ? z : object.getZ();", + " if (groundZ < object._getMinGroundZ() || groundZ > object._getMaxGroundZ()) return null;", + " const copyHeight =", + " Math.max(object._getSize(), 0) *", + " (object._getMinScale() + (object._getMaxScale() - object._getMinScale()) * scaleFactor);", + " if (ground) {", + " if (ground.getSlopeAt(sceneX, sceneY) > object._getMaxSlope()) return null;", + " const groundLayer = parseInt(object._getGroundLayer(), 10) || 0;", + " if (groundLayer > 0) {", + " if (growth > ground.getLayerWeightAt(sceneX, sceneY, groundLayer)) return null;", + " // Copies don't stand at the border of their layer, so that trees don't cover roads.", + " const margin = (copyHeight * Math.max(this.model.width, this.model.depth)) / 4;", + " for (const [offsetX, offsetY] of [[margin, 0], [-margin, 0], [0, margin], [0, -margin]]) {", + " if (ground.getLayerWeightAt(sceneX + offsetX, sceneY + offsetY, groundLayer) < 0.5) return null;", + " }", + " }", + " }", + " return {", + " x: sceneX,", + " y: sceneY,", + " z: groundZ,", + " angle: object._getRandomRotation() ? angle : 0,", + " height: copyHeight,", + " key,", + " };", + " }", + "", + " _disposeChunks(chunks) {", + " for (const chunk of chunks) {", + " for (const mesh of chunk.meshes) {", + " mesh.removeFromParent();", + " // Geometries and materials are the ones of the model.", + " mesh.dispose();", + " }", + " }", + " if (chunks === this.chunks) this.chunks = [];", + " }", + "", + " /** Place the copies again, for example after the ground or the object changed. */", + " rebuild() {", + " this._disposeChunks(this.chunks);", + " this.version++;", + " const { object } = this;", + " const spacing = this._getSpacing();", + " if (spacing <= 0 || this.model.parts.length === 0) {", + " this.grid = null;", + " return;", + " }", + " const cellsPerChunk = Math.max(Math.round(this._getChunkSize() / spacing), 1);", + " const grid = {", + " spacing,", + " cellsPerChunk,", + " cellsX: Math.ceil(object.getWidth() / spacing),", + " cellsY: Math.ceil(object.getHeight() / spacing),", + " };", + " this.grid = grid;", + " for (let firstCellJ = 0; firstCellJ < grid.cellsY; firstCellJ += cellsPerChunk) {", + " for (let firstCellI = 0; firstCellI < grid.cellsX; firstCellI += cellsPerChunk) {", + " this._createChunk(firstCellI, firstCellJ);", + " }", + " }", + " }", + "", + " /**", + " * Place again the copies of the chunks touching a rectangle of the scene,", + " * for example after painting.", + " */", + " _rebuildArea(minX, minY, maxX, maxY) {", + " const { grid, object } = this;", + " if (!grid) return;", + " const chunkSize = grid.spacing * grid.cellsPerChunk;", + " const firstChunkI = Math.max(Math.floor((minX - object.getX()) / chunkSize), 0);", + " const firstChunkJ = Math.max(Math.floor((minY - object.getY()) / chunkSize), 0);", + " const lastChunkI = Math.min(Math.floor((maxX - object.getX()) / chunkSize), Math.ceil(grid.cellsX / grid.cellsPerChunk) - 1);", + " const lastChunkJ = Math.min(Math.floor((maxY - object.getY()) / chunkSize), Math.ceil(grid.cellsY / grid.cellsPerChunk) - 1);", + " if (firstChunkI > lastChunkI || firstChunkJ > lastChunkJ) return;", + " const isInArea = (chunk) => {", + " const chunkI = chunk.firstCellI / grid.cellsPerChunk;", + " const chunkJ = chunk.firstCellJ / grid.cellsPerChunk;", + " return chunkI >= firstChunkI && chunkI <= lastChunkI && chunkJ >= firstChunkJ && chunkJ <= lastChunkJ;", + " };", + " this._disposeChunks(this.chunks.filter(isInArea));", + " this.chunks = this.chunks.filter((chunk) => !isInArea(chunk));", + " for (let chunkJ = firstChunkJ; chunkJ <= lastChunkJ; chunkJ++) {", + " for (let chunkI = firstChunkI; chunkI <= lastChunkI; chunkI++) {", + " this._createChunk(chunkI * grid.cellsPerChunk, chunkJ * grid.cellsPerChunk);", + " }", + " }", + " this.version++;", + " }", + "", + " _createChunk(firstCellI, firstCellJ) {", + " const { grid } = this;", + " const grounds = Array.from(this.placement.grounds);", + " /** @type {Copy[]} */", + " const copies = [];", + " for (let cellJ = firstCellJ; cellJ < Math.min(firstCellJ + grid.cellsPerChunk, grid.cellsY); cellJ++) {", + " for (let cellI = firstCellI; cellI < Math.min(firstCellI + grid.cellsPerChunk, grid.cellsX); cellI++) {", + " const copy = this._placeCopy(cellI, cellJ, grid.spacing, grounds);", + " if (copy) copies.push(copy);", + " }", + " }", + " if (copies.length === 0) return;", + "", + " const { object } = this;", + " // Copies are placed in scene units, and the object group is scaled", + " // from its inner area: they are scaled back to keep their proportions.", + " const scaleX = Math.max(object.getWidth() / AREA_SIZE, 0.0001);", + " const scaleY = Math.max(object.getHeight() / AREA_SIZE, 0.0001);", + " const scaleZ = Math.max(object.getDepth() / AREA_DEPTH, 0.0001);", + " const matrices = new Float32Array(copies.length * 16);", + " const tintColor = gdjs.rgbOrHexStringToNumber(object._getRandomTint());", + " const hasTint = tintColor !== 0xffffff;", + " const colors = hasTint ? new Float32Array(copies.length * 3) : null;", + " this._tint.setHex(tintColor);", + " const min = new THREE.Vector3(Infinity, Infinity, Infinity);", + " const max = new THREE.Vector3(-Infinity, -Infinity, -Infinity);", + " copies.forEach((copy, index) => {", + " const cos = Math.cos(copy.angle) * copy.height;", + " const sin = Math.sin(copy.angle) * copy.height;", + " const localX = (copy.x - object.getX()) / scaleX;", + " const localY = (copy.y - object.getY()) / scaleY;", + " const localZ = (copy.z - object.getZ()) / scaleZ;", + " this._matrix.set(", + " cos / scaleX, -sin / scaleX, 0, localX,", + " sin / scaleY, cos / scaleY, 0, localY,", + " 0, 0, copy.height / scaleZ, localZ,", + " 0, 0, 0, 1", + " );", + " this._matrix.toArray(matrices, index * 16);", + " if (colors) {", + " // The tint of each copy is between white and the tint color.", + " this._color.setRGB(1, 1, 1).lerp(this._tint, createRandom(copy.key + 1)());", + " this._color.toArray(colors, index * 3);", + " }", + " const reach = (copy.height * Math.max(this.model.width, this.model.depth)) / 2;", + " min.min(this._chunkPosition.set(copy.x - reach, copy.y - reach, copy.z));", + " max.max(this._chunkPosition.set(copy.x + reach, copy.y + reach, copy.z + copy.height));", + " });", + " const instanceMatrix = new THREE.InstancedBufferAttribute(matrices, 16);", + " const instanceColor = colors ? new THREE.InstancedBufferAttribute(colors, 3) : null;", + " const castShadow = object._getCastShadow();", + " const receiveShadow = object._getReceiveShadow();", + " const meshes = this.model.parts.map((part) => {", + " const mesh = new THREE.InstancedMesh(part.geometry, part.material, 0);", + " // All the parts of the model share the positions of the copies.", + " mesh.instanceMatrix = instanceMatrix;", + " mesh.instanceColor = instanceColor;", + " mesh.count = copies.length;", + " mesh.computeBoundingSphere();", + " mesh.castShadow = castShadow;", + " mesh.receiveShadow = receiveShadow;", + " // The first mesh tests all the parts of the copies.", + " mesh.raycast = () => {};", + " this.group.add(mesh);", + " return mesh;", + " });", + " const center = new THREE.Vector3(", + " ((min.x + max.x) / 2 - object.getX()) / scaleX,", + " ((min.y + max.y) / 2 - object.getY()) / scaleY,", + " ((min.z + max.z) / 2 - object.getZ()) / scaleZ", + " );", + " const chunk = { firstCellI, firstCellJ, meshes, copies, center, radius: min.distanceTo(max) / 2 };", + " // Used by the scene editor to select the object when a copy is clicked.", + " meshes[0].raycast = (raycaster, intersects) => this._raycastChunk(chunk, raycaster, intersects);", + " this.chunks.push(chunk);", + " }", + "", + " /**", + " * Add the hits of a ray on the copies of a chunk, like they are drawn. There", + " * can be thousands of copies: only the ones whose box is crossed by the ray", + " * are tested with the triangles of the model.", + " * @param {{meshes: THREE.InstancedMesh[], copies: Copy[], center: THREE.Vector3, radius: number}} chunk", + " * @param {THREE.Raycaster} raycaster", + " * @param {THREE.Intersection[]} intersects", + " */", + " _raycastChunk(chunk, raycaster, intersects) {", + " const { object, group } = this;", + " this._raycastSphere.center.copy(chunk.center).applyMatrix4(group.matrixWorld);", + " this._raycastSphere.radius = chunk.radius / getWorldScale(object);", + " if (!raycaster.ray.intersectsSphere(this._raycastSphere)) return;", + "", + " // The ray in the scene coordinates, like the copies.", + " const scale = this._raycastScale.set(", + " Math.max(object.getWidth() / AREA_SIZE, 0.0001),", + " Math.max(object.getHeight() / AREA_SIZE, 0.0001),", + " Math.max(object.getDepth() / AREA_DEPTH, 0.0001)", + " );", + " const ray = this._raycastRay.copy(raycaster.ray).applyMatrix4(this._raycastInverseMatrix.copy(group.matrixWorld).invert());", + " ray.origin.multiply(scale).add(this._chunkPosition.set(object.getX(), object.getY(), object.getZ()));", + " ray.direction.multiply(scale);", + "", + " const reachPerHeight = Math.max(this.model.width, this.model.depth) / 2;", + " const instanceMatrices = chunk.meshes[0].instanceMatrix.array;", + " chunk.copies.forEach((copy, index) => {", + " const reach = copy.height * reachPerHeight;", + " this._raycastBox.min.set(copy.x - reach, copy.y - reach, copy.z);", + " this._raycastBox.max.set(copy.x + reach, copy.y + reach, copy.z + copy.height);", + " if (!ray.intersectsBox(this._raycastBox)) return;", + " this._raycastMesh.matrixWorld.fromArray(instanceMatrices, index * 16).premultiply(group.matrixWorld);", + " for (const mesh of chunk.meshes) {", + " this._raycastMesh.geometry = mesh.geometry;", + " this._raycastMesh.material = mesh.material;", + " this._raycastHits.length = 0;", + " this._raycastMesh.raycast(raycaster, this._raycastHits);", + " for (const hit of this._raycastHits) {", + " hit.object = mesh;", + " hit.instanceId = index;", + " intersects.push(hit);", + " }", + " }", + " });", + " }", + "", + " /** @param {THREE.Camera | null} camera */", + " _hideFarChunks(camera) {", + " if (!camera) return;", + " const worldScale = getWorldScale(this.object);", + " const drawDistance = Math.max(this.object._getDrawDistance(), 1) / worldScale;", + " this.uniforms.scatterDrawDistance.value = drawDistance;", + " this._cameraPosition.setFromMatrixPosition(camera.matrixWorld);", + " for (const chunk of this.chunks) {", + " const distance = this._chunkPosition", + " .copy(chunk.center)", + " .applyMatrix4(this.group.matrixWorld)", + " .distanceTo(this._cameraPosition);", + " const isVisible = distance - chunk.radius / worldScale < drawDistance;", + " for (const mesh of chunk.meshes) mesh.visible = isVisible;", + " }", + " }", + "", + " /**", + " * @param {number} elapsedSeconds", + " * @param {THREE.Camera | null} camera", + " */", + " update(elapsedSeconds, camera) {", + " if (this.placement.shouldPlaceAgain(elapsedSeconds, this._getPlacementKey())) {", + " this.rebuild();", + " }", + " if (!this.changedSamples.isEmpty()) {", + " const { object } = this;", + " const sampleWidth = object.getWidth() / MASK_RESOLUTION;", + " const sampleHeight = object.getHeight() / MASK_RESOLUTION;", + " const { minI, minJ, maxI, maxJ } = this.changedSamples;", + " // Copies between samples use the density of the samples around them.", + " this._rebuildArea(", + " object.getX() + (minI - 1) * sampleWidth,", + " object.getY() + (minJ - 1) * sampleHeight,", + " object.getX() + (maxI + 1) * sampleWidth,", + " object.getY() + (maxJ + 1) * sampleHeight", + " );", + " this.changedSamples.clear();", + " }", + " // The object transformation is applied just before rendering, it's needed now.", + " this.object.getRenderer().ensureUpToDate();", + " this.group.updateWorldMatrix(true, false);", + " this._hideFarChunks(camera);", + " this._updateSurface();", + " }", + "", + " _updateSurface() {", + " const { object } = this;", + " if (typeof object.setSurface !== 'function') return;", + " const trunkKey = object._getTrunkCollisions() + '|' + object._getTrunkDiameter();", + " if (trunkKey !== this.trunkKey) {", + " this.trunkKey = trunkKey;", + " this.version++;", + " }", + " if (object.getSurface() !== this.surface) object.setSurface(this.surface);", + " }", + "", + " /** @returns {Copy[]} */", + " getCopies() {", + " const copies = [];", + " for (const chunk of this.chunks) copies.push(...chunk.copies);", + " return copies;", + " }", + "", + " /**", + " * @returns {Copy | null} The copy whose base is the nearest to a position (on X and Y), if any.", + " */", + " findNearestCopy(x, y) {", + " let nearestCopy = null;", + " let nearestDistanceSquared = Infinity;", + " for (const chunk of this.chunks) {", + " for (const copy of chunk.copies) {", + " const distanceSquared = (copy.x - x) * (copy.x - x) + (copy.y - y) * (copy.y - y);", + " if (distanceSquared < nearestDistanceSquared) {", + " nearestDistanceSquared = distanceSquared;", + " nearestCopy = copy;", + " }", + " }", + " }", + " return nearestCopy;", + " }", + "", + " getCopyCount() {", + " let count = 0;", + " for (const chunk of this.chunks) count += chunk.copies.length;", + " return count;", + " }", + "", + " /** Remove the copies whose base is in a circle (they come back when the scene is restarted). */", + " removeCopies(x, y, radius) {", + " let hasRemovedCopies = false;", + " for (const chunk of this.chunks) {", + " for (const copy of chunk.copies) {", + " if ((copy.x - x) * (copy.x - x) + (copy.y - y) * (copy.y - y) <= radius * radius) {", + " this.removedCopies.set(copy.key, copy);", + " hasRemovedCopies = true;", + " }", + " }", + " }", + " if (hasRemovedCopies) this._rebuildArea(x - radius, y - radius, x + radius, y + radius);", + " }", + "", + " /** Paint the full density around a position, and bring back the copies removed there. */", + " addCopies(x, y, radius) {", + " for (const [key, copy] of this.removedCopies) {", + " if ((copy.x - x) * (copy.x - x) + (copy.y - y) * (copy.y - y) <= radius * radius) {", + " this.removedCopies.delete(key);", + " }", + " }", + " this.paint(x, y, x, y, radius, 1, 1);", + " }", + "", + " _getStroke(x1, y1, x2, y2, radius) {", + " const { object } = this;", + " const stroke = this._stroke;", + " stroke.cellWidth = object.getWidth() / MASK_RESOLUTION;", + " stroke.cellHeight = object.getHeight() / MASK_RESOLUTION;", + " stroke.ax = x1 - object.getX();", + " stroke.ay = y1 - object.getY();", + " stroke.bx = x2 - object.getX();", + " stroke.by = y2 - object.getY();", + " stroke.radius = Math.max(radius, 0);", + " return stroke;", + " }", + "", + " /**", + " * Paint the density around a segment (copies are placed again at the next frame).", + " * @param {number} density From 0 (erase) to 1 (as dense as the Density property).", + " * @param {number} strength From 0 to 1.", + " */", + " paint(x1, y1, x2, y2, radius, density, strength) {", + " const changedSamples = new SampleRectangle();", + " this.mask.paint(this._getStroke(x1, y1, x2, y2, radius), density, strength, changedSamples);", + " this.changedSamples.add(changedSamples);", + " }", + "", + " /** @param {DensityMask} mask */", + " setMask(mask) {", + " this.mask = mask;", + " this.rebuild();", + " }", + "", + " /**", + " * Prisms around the trunks, from 0 to 1 in the object box (see `gdjs.Surface`).", + " * @returns {gdjs.SurfaceTriangles}", + " */", + " _getTrunkTriangles() {", + " const { object } = this;", + " const copies = object._getTrunkCollisions() ? this.getCopies() : [];", + " const verticesPerCopy = TRUNK_SIDES * 2 + 1;", + " const positions = new Float32Array(copies.length * verticesPerCopy * 3);", + " const indices = new Uint32Array(copies.length * TRUNK_SIDES * 3 * 3);", + " const width = object.getWidth() || 1;", + " const height = object.getHeight() || 1;", + " const depth = object.getDepth() || 1;", + " let indexOffset = 0;", + " copies.forEach((copy, copyIndex) => {", + " const radius = (Math.max(object._getTrunkDiameter(), 0) / 2) * (copy.height / Math.max(object._getSize(), 1));", + " const top = copy.z + copy.height * 0.8;", + " const firstVertex = copyIndex * verticesPerCopy;", + " const setVertex = (vertex, x, y, z) => {", + " positions[vertex * 3] = (x - object.getX()) / width;", + " positions[vertex * 3 + 1] = (y - object.getY()) / height;", + " positions[vertex * 3 + 2] = (z - object.getZ()) / depth;", + " };", + " for (let side = 0; side < TRUNK_SIDES; side++) {", + " const angle = ((side + 0.5) / TRUNK_SIDES) * Math.PI * 2;", + " const x = copy.x + Math.cos(angle) * radius;", + " const y = copy.y + Math.sin(angle) * radius;", + " // Starting a bit under the ground, which can be sloped.", + " setVertex(firstVertex + side * 2, x, y, copy.z - radius);", + " setVertex(firstVertex + side * 2 + 1, x, y, top);", + " }", + " const center = firstVertex + TRUNK_SIDES * 2;", + " setVertex(center, copy.x, copy.y, top);", + " for (let side = 0; side < TRUNK_SIDES; side++) {", + " const bottom = firstVertex + side * 2;", + " const nextBottom = firstVertex + ((side + 1) % TRUNK_SIDES) * 2;", + " // Triangles face out of the prism (vertices at odd indices are at the top).", + " indices.set([bottom, nextBottom, bottom + 1, nextBottom, nextBottom + 1, bottom + 1], indexOffset);", + " indices.set([center, bottom + 1, nextBottom + 1], indexOffset + 6);", + " indexOffset += 9;", + " }", + " });", + " if (copies.length === 0) {", + " // Without triangles, the physics engine and navigation meshes would use", + " // the object box: a tiny triangle under the object is used instead.", + " return { positions: new Float32Array([0, 0, -1, 0.0001, 0, -1, 0, 0.0001, -1]), indices: new Uint32Array([0, 2, 1]) };", + " }", + " return { positions, indices };", + " }", + "", + " dispose() {", + " this.isDisposed = true;", + " if (typeof this.object.setSurface === 'function' && this.object.getSurface() === this.surface) {", + " this.object.setSurface(null);", + " }", + " this._disposeChunks(this.chunks);", + " this._disposeModel();", + " this.group.removeFromParent();", + " }", + "}", + "", + "const scatterToolIcons = {", + " Add: svgIcon(", + " ''", + " ),", + " Erase: svgIcon(", + " ''", + " ),", + "};", + "", + "/**", + " * The brushes shown in the scene editor when scattered models are selected,", + " * to add or erase copies. Without the needed editor functions (older GDevelop", + " * versions), nothing is shown: copies can still be painted with events.", + " */", + "class ScatterEditorTools {", + " constructor() {", + " /** '' when no brush is used. */", + " this.toolName = '';", + " this.sizePercent = 30;", + " this.strengthPercent = 50;", + " /** @type {Scatter | null} */", + " this.scatter = null;", + " /** @type {gdjs.InGameEditor | null} */", + " this.editor = null;", + " this.isStroking = false;", + " this.lastStrokeTime = 0;", + " /** @type {Array<{mask: DensityMask, baseMask: DensityMask}>} */", + " this.undoSteps = [];", + " this.raycaster = new THREE.Raycaster();", + " this.ndc = new THREE.Vector2();", + " this._groundAtCursor = { ground: null, z: 0 };", + " /** A circle following the ground under the cursor. */", + " this.brushRing = new THREE.LineLoop(", + " new THREE.BufferGeometry().setAttribute('position', new THREE.BufferAttribute(new Float32Array(64 * 3), 3)),", + " new THREE.LineBasicMaterial({ color: 0xffffff, depthTest: false, transparent: true })", + " );", + " this.brushRing.renderOrder = 1000;", + " this.brushRing.frustumCulled = false;", + " this.brushRing.raycast = () => {};", + " }", + "", + " /** @param {gdjs.InGameEditor} editor */", + " update(editor) {", + " if (typeof editor.showToolbar !== 'function') return;", + " this.editor = editor;", + " const selectedObjects = editor.getSelectedObjects();", + " const scatter =", + " selectedObjects.length === 1 && selectedObjects[0].__terrainScatter ? selectedObjects[0].__terrainScatter : null;", + " if (scatter !== this.scatter) {", + " this._endStroke();", + " this.scatter = scatter;", + " this.undoSteps.length = 0;", + " }", + " if (scatter) editor.showToolbar('ScatteredModels', this._getToolbarItems());", + " const game = editor.getRuntimeGame();", + " if (!scatter || !this.toolName) {", + " this.brushRing.removeFromParent();", + " return;", + " }", + " const inputManager = game.getInputManager();", + " if (inputManager.wasKeyJustPressed(gdjs.evtTools.input.keysNameToCode.Escape)) {", + " this._endStroke();", + " this.toolName = '';", + " this.brushRing.removeFromParent();", + " return;", + " }", + " editor.captureLeftMouseButton();", + " const hit = this._getPositionUnderCursor(scatter, game);", + " const radius = this._getRadius();", + " this._showBrushRing(scatter, hit, radius);", + "", + " const isPressedForTool =", + " typeof editor.isCapturedLeftMouseButtonPressed === 'function'", + " ? editor.isCapturedLeftMouseButtonPressed()", + " : inputManager.isMouseButtonPressed(0);", + " // The button is released outside the canvas when the mouse is on a toolbar.", + " const isPressed = isPressedForTool && inputManager.isMouseInsideCanvas();", + " if (isPressed && hit && !this.isStroking) {", + " this._startStroke(scatter);", + " }", + " if (this.isStroking && isPressed && hit) {", + " this._applyBrush(scatter, hit, radius);", + " }", + " if (!isPressed) {", + " this._endStroke();", + " }", + " }", + "", + " _getRadius() {", + " const { object } = this.scatter;", + " const maxRadius = Math.max(object.getWidth(), object.getHeight()) / 4;", + " return Math.max(maxRadius * Math.pow(this.sizePercent / 100, 2), 10);", + " }", + "", + " /**", + " * @returns {{x: number, y: number} | null} The position of the scene under the cursor: on the", + " * highest ground hit, or on the plane at the object Z.", + " */", + " _getPositionUnderCursor(scatter, game) {", + " let hit = null;", + " for (const ground of getGrounds(scatter.object.getRuntimeScene())) {", + " if (typeof ground.raycastFromCursor !== 'function') continue;", + " const localHit = ground.raycastFromCursor(game, this.raycaster, this.ndc);", + " if (!localHit) continue;", + " const sceneHit = ground.toScenePosition(localHit);", + " if (!hit || sceneHit.z > hit.z) hit = sceneHit;", + " }", + " if (hit) return hit;", + "", + " const { object } = scatter;", + " const layer = object.getInstanceContainer().getLayer(object.getLayer());", + " const camera = layer.getRenderer().getThreeCamera();", + " if (!camera) return null;", + " const inputManager = game.getInputManager();", + " this.ndc.set(", + " (inputManager.getCursorX() / game.getGameResolutionWidth()) * 2 - 1,", + " -(inputManager.getCursorY() / game.getGameResolutionHeight()) * 2 + 1", + " );", + " this.raycaster.setFromCamera(this.ndc, camera);", + " scatter.group.updateWorldMatrix(true, false);", + " const ray = this.raycaster.ray.clone().applyMatrix4(scatter.group.matrixWorld.clone().invert());", + " const point = ray.intersectPlane(new THREE.Plane(new THREE.Vector3(0, 0, 1), 0), new THREE.Vector3());", + " if (!point) return null;", + " return {", + " x: object.getX() + (point.x / AREA_SIZE) * object.getWidth(),", + " y: object.getY() + (point.y / AREA_SIZE) * object.getHeight(),", + " };", + " }", + "", + " _showBrushRing(scatter, hit, radius) {", + " if (!hit) {", + " this.brushRing.removeFromParent();", + " return;", + " }", + " const { object } = scatter;", + " if (this.brushRing.parent !== scatter.group) scatter.group.add(this.brushRing);", + " const grounds = getGrounds(object.getRuntimeScene());", + " const positions = this.brushRing.geometry.attributes.position;", + " for (let index = 0; index < positions.count; index++) {", + " const angle = (index / positions.count) * Math.PI * 2;", + " const x = hit.x + Math.cos(angle) * radius;", + " const y = hit.y + Math.sin(angle) * radius;", + " const { ground, z } = findHighestGround(grounds, x, y, this._groundAtCursor);", + " positions.setXYZ(", + " index,", + " ((x - object.getX()) / object.getWidth()) * AREA_SIZE,", + " ((y - object.getY()) / object.getHeight()) * AREA_SIZE,", + " (((ground ? z : object.getZ()) + 2 - object.getZ()) / Math.max(object.getDepth(), 0.0001)) * AREA_DEPTH", + " );", + " }", + " positions.needsUpdate = true;", + " }", + "", + " _startStroke(scatter) {", + " this.isStroking = true;", + " this.lastStrokeTime = performance.now();", + " this.undoSteps.push({ mask: scatter.mask.clone(), baseMask: scatter.baseMask });", + " while (this.undoSteps.length > 30) this.undoSteps.shift();", + " }", + "", + " _endStroke() {", + " if (!this.isStroking) return;", + " this.isStroking = false;", + " this._save();", + " }", + "", + " _applyBrush(scatter, hit, radius) {", + " const now = performance.now();", + " const elapsedSeconds = Math.min((now - this.lastStrokeTime) / 1000, 0.1);", + " this.lastStrokeTime = now;", + " const strength = (this.strengthPercent / 100) * elapsedSeconds * 8;", + " scatter.paint(hit.x, hit.y, hit.x, hit.y, radius, this.toolName === 'Add' ? 1 : 0, strength);", + " }", + "", + " _save() {", + " const { scatter, editor } = this;", + " if (!scatter || !editor) return;", + " editor.updateObjectProperties(scatter.object.getName(), { PaintData: scatter.save() });", + " }", + "", + " _undo() {", + " const undoStep = this.undoSteps.pop();", + " // The coverage or the edits may have changed since the stroke.", + " if (!this.scatter || !undoStep || undoStep.baseMask !== this.scatter.baseMask) {", + " this.undoSteps.length = 0;", + " return;", + " }", + " this.scatter.setMask(undoStep.mask);", + " this._save();", + " }", + "", + " /** @returns {Array} */", + " _getToolbarItems() {", + " const toolButton = (toolName, tooltip) => ({", + " type: 'button',", + " id: toolName,", + " tooltip,", + " iconUrl: scatterToolIcons[toolName],", + " isActive: this.toolName === toolName,", + " onClick: () => (this.toolName = this.toolName === toolName ? '' : toolName),", + " });", + " return [", + " toolButton('Add', 'Add copies (drag on the ground)'),", + " toolButton('Erase', 'Erase copies'),", + " { type: 'divider', id: 'BrushDivider' },", + " {", + " type: 'slider',", + " id: 'Size',", + " tooltip: 'Brush size',", + " iconUrl: toolIcons.Size,", + " min: 1,", + " max: 100,", + " value: this.sizePercent,", + " onChange: (value) => (this.sizePercent = value),", + " },", + " {", + " type: 'slider',", + " id: 'Strength',", + " tooltip: 'Brush strength',", + " iconUrl: toolIcons.Strength,", + " min: 5,", + " max: 100,", + " value: this.strengthPercent,", + " onChange: (value) => (this.strengthPercent = value),", + " },", + " {", + " type: 'button',", + " id: 'Undo',", + " tooltip: 'Undo the last stroke on these scattered models',", + " iconUrl: toolIcons.Undo,", + " onClick: () => this._undo(),", + " },", + " ];", + " }", + "}", + "", + "const editorTools = new ScatterEditorTools();", + "if (gdjs.registerInGameEditorPostStepCallback) {", + " gdjs.registerInGameEditorPostStepCallback((editor) => editorTools.update(editor));", + "}", + "", + "gdjs.__terrainScatterExtension = {", + " Scatter,", + " // Exposed for tests.", + " DensityMask,", + " maskCodec,", + " applyEdits,", + "};", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [], + "description": "Define scattered models helper classes JavaScript code.", + "private": true + } + ], + "eventsBasedBehaviors": [], + "eventsBasedObjects": [ + { + "areaMaxX": 4096, + "areaMaxY": 4096, + "areaMaxZ": 600, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "defaultName": "Terrain", + "assetStoreTag": "3d terrains", + "description": "A ground with hills, mountains or valleys that can be sculpted and painted in the scene editor, or changed with events. Add the 3D physics behavior to it to make objects walk or roll on it.", + "fullName": "3D terrain", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXRlcnJhaW4iIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTQsNkwxMC4yNSwxMUwxMy4xLDE0LjhMMTEuNSwxNkM5LjgxLDEzLjc1IDcsMTAgNywxMEwxLDE4SDIzTDE0LDZaIiAvPjwvc3ZnPg==", + "is3D": true, + "isUsingLegacyInstancesRenderer": true, + "name": "Terrain3D", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/10e0a26c0d500830dfe23b94138beb0ef61607ba62bca05a5aa5c849777f1b06_terrain.svg", + "objects": [ + { + "assetStoreId": "", + "name": "AreaPlaceholder", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 4096, + "height": 4096, + "depth": 600, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "Basic", + "tint": "255;255;255", + "frontFaceResourceName": "", + "frontFaceVisible": false, + "frontFaceResourceRepeat": false, + "backFaceResourceName": "", + "backFaceVisible": false, + "backFaceResourceRepeat": false, + "leftFaceResourceName": "", + "leftFaceVisible": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceName": "", + "rightFaceVisible": false, + "rightFaceResourceRepeat": false, + "topFaceResourceName": "", + "topFaceVisible": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceName": "", + "bottomFaceVisible": false, + "bottomFaceResourceRepeat": false + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "AreaPlaceholder" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [ + { + "angle": 0, + "customSize": true, + "width": 4096, + "height": 4096, + "depth": 600, + "keepRatio": true, + "layer": "", + "name": "AreaPlaceholder", + "persistentUuid": "5d7a8b1e-3c2f-4e6a-9b1d-004096004096", + "x": 0, + "y": 0, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": {}, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Terrain3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "for (const child of object.getChildrenContainer().getAdhocListOfAllInstances()) {", + " child.deleteFromScene();", + "}", + "if (object.__terrain3D) object.__terrain3D.dispose();", + "object.__terrain3D = new gdjs.__terrain3DExtension.Terrain(object);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDestroy", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrain3D.dispose();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrain3D.update();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrain3D.onHotReloading();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + } + ], + "objectGroups": [] + }, + { + "fullName": "Raise or lower the ground", + "functionType": "Action", + "name": "RaiseGround", + "sentence": "Raise the ground of _PARAM0_ by _PARAM4_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const height = eventsFunctionContext.getArgument(\"Height\");", + "object.__terrain3D.raise(x, y, x, y, radius, height);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Height to add (negative to lower the ground)", + "name": "Height", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Raise the ground around a position (or lower it with a negative height). It's raised the most at the center and less and less up to the radius.", + "group": "Sculpt" + }, + { + "fullName": "Raise or lower the ground along a line", + "functionType": "Action", + "name": "RaiseGroundAlongLine", + "sentence": "Raise the ground of _PARAM0_ by _PARAM6_ from _PARAM1_; _PARAM2_ to _PARAM3_; _PARAM4_ with a radius of _PARAM5_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x1 = eventsFunctionContext.getArgument(\"X1\");", + "const y1 = eventsFunctionContext.getArgument(\"Y1\");", + "const x2 = eventsFunctionContext.getArgument(\"X2\");", + "const y2 = eventsFunctionContext.getArgument(\"Y2\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const height = eventsFunctionContext.getArgument(\"Height\");", + "object.__terrain3D.raise(x1, y1, x2, y2, radius, height);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the start", + "name": "X1", + "type": "expression" + }, + { + "description": "Y position of the start", + "name": "Y1", + "type": "expression" + }, + { + "description": "X position of the end", + "name": "X2", + "type": "expression" + }, + { + "description": "Y position of the end", + "name": "Y2", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance from the line." + }, + { + "description": "Height to add (negative to lower the ground)", + "name": "Height", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Raise the ground along a line, for example for a ridge, or lower it with a negative height to dig a river bed or a valley.", + "group": "Sculpt" + }, + { + "fullName": "Flatten the ground", + "functionType": "Action", + "name": "FlattenGround", + "sentence": "Flatten the ground of _PARAM0_ to a Z of _PARAM4_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const z = eventsFunctionContext.getArgument(\"Z\");", + "object.__terrain3D.flatten(x, y, x, y, radius, z, 1);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Z position of the flat ground", + "name": "Z", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Flatten the ground around a position to a given height, for example to place a building.", + "group": "Sculpt" + }, + { + "fullName": "Smooth the ground", + "functionType": "Action", + "name": "SmoothGround", + "sentence": "Smooth the ground of _PARAM0_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_ (strength: _PARAM4_)", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const strength = eventsFunctionContext.getArgument(\"Strength\");", + "object.__terrain3D.smooth(x, y, x, y, radius, strength);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Strength (between 0 and 1)", + "name": "Strength", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Smooth the ground around a position, softening bumps and cliffs.", + "group": "Sculpt" + }, + { + "fullName": "Paint a layer", + "functionType": "Action", + "name": "PaintLayer", + "sentence": "Paint layer _PARAM4_ on _PARAM0_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const layer = eventsFunctionContext.getArgument(\"Layer\");", + "object.__terrain3D.paint(x, y, x, y, radius, layer, 1);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Layer (1 to 4)", + "name": "Layer", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Paint a layer (like grass, dirt, rock or sand) on the ground around a position.", + "group": "Paint" + }, + { + "fullName": "Paint a layer along a line", + "functionType": "Action", + "name": "PaintLayerAlongLine", + "sentence": "Paint layer _PARAM6_ on _PARAM0_ from _PARAM1_; _PARAM2_ to _PARAM3_; _PARAM4_ with a radius of _PARAM5_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x1 = eventsFunctionContext.getArgument(\"X1\");", + "const y1 = eventsFunctionContext.getArgument(\"Y1\");", + "const x2 = eventsFunctionContext.getArgument(\"X2\");", + "const y2 = eventsFunctionContext.getArgument(\"Y2\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const layer = eventsFunctionContext.getArgument(\"Layer\");", + "object.__terrain3D.paint(x1, y1, x2, y2, radius, layer, 1);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the start", + "name": "X1", + "type": "expression" + }, + { + "description": "Y position of the start", + "name": "Y1", + "type": "expression" + }, + { + "description": "X position of the end", + "name": "X2", + "type": "expression" + }, + { + "description": "Y position of the end", + "name": "Y2", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance from the line." + }, + { + "description": "Layer (1 to 4)", + "name": "Layer", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Paint a layer on the ground along a line, for example for a path or a road.", + "group": "Paint" + }, + { + "fullName": "Tint the ground", + "functionType": "Action", + "name": "TintGround", + "sentence": "Tint the ground of _PARAM0_ with _PARAM4_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const color = gdjs.rgbOrHexToRGBColor(eventsFunctionContext.getArgument(\"Color\"));", + "object.__terrain3D.tint(x, y, x, y, radius, color, 1);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Color", + "name": "Color", + "type": "color" + } + ], + "objectGroups": [], + "description": "Tint the ground around a position with a color multiplying its layers, like dry grass or darker mud. White removes the tint.", + "group": "Paint" + }, + { + "fullName": "Change the wetness of the ground", + "functionType": "Action", + "name": "WetGround", + "sentence": "Change the wetness of the ground of _PARAM0_ to _PARAM4_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const wetness = eventsFunctionContext.getArgument(\"Wetness\");", + "object.__terrain3D.wet(x, y, x, y, radius, wetness, 1);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression", + "longDescription": "The effect fades out smoothly up to this distance." + }, + { + "description": "Wetness (from 0 for dry to 1)", + "name": "Wetness", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Make the ground around a position wet (darker and shiny, like after rain or near water) or dry.", + "group": "Paint" + }, + { + "fullName": "Ground wetness", + "functionType": "ExpressionAndCondition", + "name": "GroundWetness", + "sentence": "the wetness of the ground at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.getWetnessAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the wetness of the ground at a position, from 0 (dry) to 1, painted or near the water (wet shore).", + "group": "Paint", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Ground tint", + "functionType": "StringExpression", + "name": "GroundTint", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.getTintAt(x, y).join(\";\");" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "The tint painted on the ground at a position, like \"255;255;255\" (white when there is none).", + "group": "Paint", + "expressionType": { + "type": "string" + } + }, + { + "fullName": "Make a hole", + "functionType": "Action", + "name": "MakeHole", + "sentence": "Make a hole in _PARAM0_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "object.__terrain3D.makeHole(x, y, x, y, radius);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Remove the ground in a circle, for the entrance of a tunnel, a cave or a well: there is nothing drawn and no collision there. Fill it again with the \"Fill holes\" action. A height field can't go under itself: put a 3D model (with a collision shape) for the tunnel or the cave under the ground. The physics engine removes the triangles touching a hole, so the hole in the collisions can be up to a cell larger than the one drawn: make the model go a bit further than the hole, or put rocks around the entrance.", + "group": "Holes" + }, + { + "fullName": "Fill holes", + "functionType": "Action", + "name": "FillHoles", + "sentence": "Fill the holes in _PARAM0_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "object.__terrain3D.fillHoles(x, y, x, y, radius);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Fill the holes in a circle with the layers of the ground around them: the ground is drawn and has collisions again.", + "group": "Holes" + }, + { + "fullName": "Carve an entrance", + "functionType": "Action", + "name": "CarveEntrance", + "sentence": "Carve an entrance in _PARAM0_ at _PARAM1_; _PARAM2_ with a radius of _PARAM3_, going in at _PARAM4_° with its floor at Z _PARAM5_ and _PARAM6_ pixels of flat ground in front", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "const angle = eventsFunctionContext.getArgument(\"Angle\");", + "const floorZ = eventsFunctionContext.getArgument(\"FloorZ\");", + "const length = eventsFunctionContext.getArgument(\"Length\");", + "object.__terrain3D.carveEntrance(x, y, radius, angle, floorZ, length);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression" + }, + { + "description": "Direction going into the ground (in degrees)", + "name": "Angle", + "type": "expression" + }, + { + "description": "Z position of the floor", + "name": "FloorZ", + "type": "expression" + }, + { + "description": "Length of the flat ground in front of the entrance (in pixels)", + "name": "Length", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Make the entrance of a tunnel or a cave on a slope: a hole, with the ground in front of it flattened to the floor of the tunnel.", + "group": "Holes" + }, + { + "fullName": "Hole", + "functionType": "Condition", + "name": "IsHole", + "sentence": "There is a hole in _PARAM0_ at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.isHoleAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Check if there is a hole in the ground at a position.", + "group": "Holes" + }, + { + "fullName": "Ground Z position, with holes", + "functionType": "Expression", + "name": "GroundZWithHoles", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const terrain = object.__terrain3D;", + "eventsFunctionContext.returnValue = terrain.isHoleAt(x, y) ? eventsFunctionContext.getArgument(\"ZInHoles\") : terrain.getHeightAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + }, + { + "description": "Value in holes", + "name": "ZInHoles", + "type": "expression", + "longDescription": "For example, a Z position far under the ground." + } + ], + "objectGroups": [], + "description": "The Z position of the ground at a position, or the given value where there is a hole.", + "group": "Holes", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Generate a relief", + "functionType": "Action", + "name": "GenerateRelief", + "sentence": "Generate a relief of _PARAM1_ on _PARAM0_ with seed _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const relief = eventsFunctionContext.getArgument(\"Relief\");", + "const seed = eventsFunctionContext.getArgument(\"Seed\");", + "object.__terrain3D.generate(relief, seed);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "Relief", + "name": "Relief", + "type": "stringWithSelector", + "supplementaryInformation": "[\"Flat\", \"Hills\", \"Mountains\", \"Island\"]" + }, + { + "description": "Seed", + "name": "Seed", + "type": "expression", + "longDescription": "Each seed gives a different terrain with the same relief." + } + ], + "objectGroups": [], + "description": "Replace the ground with a generated relief. Painted layers are reset.", + "group": "Generation" + }, + { + "fullName": "Load a heightmap", + "functionType": "Action", + "name": "LoadHeightmap", + "sentence": "Load the heights of _PARAM0_ from _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const image = eventsFunctionContext.getArgument(\"Image\");", + "object.__terrain3D.loadHeightmap(image);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "Heightmap image", + "name": "Image", + "type": "imageResource", + "longDescription": "Black is the lowest, white the highest. For more precision, store the height on 16 bits in the red (most significant byte) and green channels." + } + ], + "objectGroups": [], + "description": "Replace the ground with the heights of an image. Painted layers are kept.", + "group": "Generation" + }, + { + "fullName": "Put objects on the ground", + "functionType": "Action", + "name": "PutOnGround", + "sentence": "Put _PARAM1_ on the ground of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrain3D.placeOnGround(eventsFunctionContext.getObjects(\"Objects\"));" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "Objects to put on the ground", + "name": "Objects", + "type": "objectList" + } + ], + "objectGroups": [], + "description": "Move objects up or down so that their bottom is on the ground, under their center. Useful to place trees, rocks or characters on a terrain.", + "group": "Position" + }, + { + "fullName": "Ground Z position", + "functionType": "ExpressionAndCondition", + "name": "GroundZ", + "sentence": "the ground Z position at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.getHeightAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the Z position of the ground at a position of the scene.", + "group": "Position", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Ground slope", + "functionType": "ExpressionAndCondition", + "name": "GroundSlope", + "sentence": "the ground slope at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.getSlopeAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the slope of the ground (angle with the horizontal, in degrees) at a position of the scene.", + "group": "Position", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Ground layer", + "functionType": "ExpressionAndCondition", + "name": "GroundLayer", + "sentence": "the ground layer at _PARAM1_; _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "eventsFunctionContext.returnValue = object.__terrain3D.getLayerAt(x, y);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::Terrain3D" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the layer (from 1 to 4) painted the most at a position of the scene. Useful to play footstep sounds or to slow down characters in water or mud.", + "group": "Paint", + "expressionType": { + "type": "expression" + } + } + ], + "propertyDescriptors": [ + { + "value": "Hills", + "type": "Choice", + "label": "Relief", + "description": "The initial shape, before sculpting.", + "group": "Shape", + "choices": [ + { + "label": "Flat", + "value": "Flat" + }, + { + "label": "Hills", + "value": "Hills" + }, + { + "label": "Mountains", + "value": "Mountains" + }, + { + "label": "Island", + "value": "Island" + } + ], + "name": "Relief" + }, + { + "value": "1", + "type": "Number", + "label": "Seed", + "description": "Each seed gives a different terrain with the same relief.", + "group": "Shape", + "name": "Seed" + }, + { + "value": "", + "type": "Resource", + "label": "Heightmap image", + "description": "Optional. Replaces the relief: black is the lowest, white the highest (the object depth).", + "group": "Shape", + "extraInformation": [ + "image" + ], + "name": "HeightmapImage" + }, + { + "value": "", + "type": "MultilineString", + "label": "Edits", + "description": "Changes of the relief or heightmap, applied in order, as a JSON list, for example [{\"tool\": \"raise\", \"x\": 0.3, \"y\": 0.4, \"radius\": 0.15, \"height\": 0.4}, {\"tool\": \"paint\", \"x\": 0.1, \"y\": 0.9, \"toX\": 0.8, \"toY\": 0.2, \"radius\": 0.02, \"layer\": 2}]. Tools: raise (height added to the ground, negative to lower), flatten (z: the ground is brought to this height, fully at the center, cutting what is above it and filling what is below; with toX and toY, toZ gives the height at the end of the line, for a road going up), smooth (strength), paint (layer, from 1 to 4, and strength), tint (color, like \"120;140;60\", multiplying the layers, white removing the tint, and strength), wetness (amount, from 0 for dry to 1 for wet, and strength), hole (no ground in the radius, for the entrances of tunnels and caves) and fill (the holes in the radius are filled with the layers of the ground around them). x, y and radius are fractions (0 to 1) of the terrain size (x is (scene X - terrain X) / terrain width). height and z are fractions of its depth: the ground at z is at Z = terrain Z + z * terrain depth. Add toX and toY to change the ground along a line (paths, rivers, ridges). Shown in the scene editor.", + "group": "Shape", + "advanced": true, + "name": "Edits" + }, + { + "value": "256", + "type": "Choice", + "label": "Resolution", + "description": "The number of cells on each side of the terrain. Higher is more detailed but slower.", + "group": "Shape", + "choices": [ + { + "label": "64 x 64", + "value": "64" + }, + { + "label": "128 x 128", + "value": "128" + }, + { + "label": "256 x 256", + "value": "256" + }, + { + "label": "512 x 512", + "value": "512" + }, + { + "label": "1024 x 1024", + "value": "1024" + } + ], + "name": "Resolution" + }, + { + "value": "96;160;64", + "type": "Color", + "label": "Layer 1 color (grass)", + "group": "Layers", + "name": "Layer1Color" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 1 texture", + "description": "Optional. Tinted by the layer color.", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer1Texture" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 1 normal map", + "description": "Optional. The bumps of the texture, lit by the lights (an OpenGL normal map, like the textures of ambientCG).", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer1NormalMap" + }, + { + "value": "150;112;72", + "type": "Color", + "label": "Layer 2 color (dirt)", + "group": "Layers", + "name": "Layer2Color" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 2 texture", + "description": "Optional. Tinted by the layer color.", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer2Texture" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 2 normal map", + "description": "Optional. The bumps of the texture, lit by the lights (an OpenGL normal map, like the textures of ambientCG).", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer2NormalMap" + }, + { + "value": "124;118;112", + "type": "Color", + "label": "Layer 3 color (rock)", + "group": "Layers", + "name": "Layer3Color" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 3 texture", + "description": "Optional. Tinted by the layer color.", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer3Texture" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 3 normal map", + "description": "Optional. The bumps of the texture, lit by the lights (an OpenGL normal map, like the textures of ambientCG).", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer3NormalMap" + }, + { + "value": "224;204;148", + "type": "Color", + "label": "Layer 4 color (sand)", + "group": "Layers", + "name": "Layer4Color" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 4 texture", + "description": "Optional. Tinted by the layer color.", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer4Texture" + }, + { + "value": "", + "type": "Resource", + "label": "Layer 4 normal map", + "description": "Optional. The bumps of the texture, lit by the lights (an OpenGL normal map, like the textures of ambientCG).", + "group": "Layers", + "extraInformation": [ + "image" + ], + "name": "Layer4NormalMap" + }, + { + "value": "1", + "type": "Number", + "label": "Normal map strength", + "description": "How much the normal maps bump the ground: 0 for none, 1 as they are, more to exaggerate them.", + "group": "Layers", + "name": "NormalMapStrength" + }, + { + "value": "256", + "type": "Number", + "label": "Texture size", + "unit": "Pixel", + "description": "The size, in the scene, of one repetition of the layer textures.", + "group": "Layers", + "name": "TextureSize" + }, + { + "value": "0.5", + "type": "Number", + "label": "Height blending", + "description": "Where layers meet, the bright parts of textures (stones, tufts) cover the dark ones (gaps, sand) instead of fading into each other: 0 for none, 1 for the most.", + "group": "Layers", + "name": "HeightBlending" + }, + { + "value": "0.5", + "type": "Number", + "label": "Texture variation", + "description": "Makes the repetition of textures less visible, with patches of bigger and rotated textures and colors slowly varying: 0 for none, 1 for the most.", + "group": "Layers", + "name": "TextureVariation" + }, + { + "value": "false", + "type": "Boolean", + "label": "Wet shore", + "description": "Make the ground wet (darker and shiny) under a Z position, like the sand of a beach near the water.", + "group": "Wetness", + "name": "WetShore" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Wet below Z", + "description": "The ground is wet under this Z position (for example, the Z of a 3D water object).", + "group": "Wetness", + "name": "WetShoreZ" + }, + { + "value": "60", + "type": "Number", + "unit": "Pixel", + "label": "Wet shore fade", + "description": "The ground becomes dry over this height above \"Wet below Z\".", + "group": "Wetness", + "name": "WetShoreFade" + }, + { + "value": "3", + "type": "Choice", + "label": "Layer on steep slopes", + "description": "This layer is shown where the ground is steep, whatever is painted.", + "group": "Layers", + "choices": [ + { + "label": "None", + "value": "0" + }, + { + "label": "Layer 1", + "value": "1" + }, + { + "label": "Layer 2", + "value": "2" + }, + { + "label": "Layer 3", + "value": "3" + }, + { + "label": "Layer 4", + "value": "4" + } + ], + "name": "CliffLayer" + }, + { + "value": "45", + "type": "Number", + "label": "Steep slope angle", + "unit": "DegreeAngle", + "group": "Layers", + "name": "CliffAngle" + }, + { + "value": "true", + "type": "Boolean", + "label": "Cast shadows", + "group": "Rendering", + "name": "CastShadow" + }, + { + "value": "true", + "type": "Boolean", + "label": "Receive shadows", + "group": "Rendering", + "name": "ReceiveShadow" + }, + { + "value": "", + "type": "String", + "label": "Sculpted and painted data", + "description": "Written by the sculpt and paint tools of the scene editor, in a compact format not meant to be read or written by hand: use Edits instead. Clear it to go back to the relief, heightmap and edits.", + "advanced": true, + "name": "SculptData" + } + ] + }, + { + "areaMaxX": 1000, + "areaMaxY": 1000, + "areaMaxZ": 40, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "defaultName": "Grass", + "description": "A field of grass blades swaying in the wind and pushed aside by characters. They grow in the area of the object, on the highest 3D terrain at each point (one field can cover several terrains), or at the Z position of the object where there is no terrain.", + "fullName": "Terrain grass", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0zIDIxYzEtNSAxLTkgMC0xMyAyIDMgMyA3IDMgMTN6bTQgMGMwLTYgMS0xMSA0LTE2LTEgNS0xIDEwIDAgMTZ6bTUgMGMwLTUgMi05IDUtMTItMiA0LTIgOC0xIDEyem01IDBjMS00IDItNiA0LTgtMSAzLTEgNS0xIDh6Ii8+PC9zdmc+", + "is3D": true, + "isUsingLegacyInstancesRenderer": true, + "name": "TerrainGrass", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/732ef90b7fcf5dd9171fe95d0cf262e09159b487304915a4693baa893d9ce16c_grass.svg", + "objects": [ + { + "assetStoreId": "", + "name": "AreaPlaceholder", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 1000, + "height": 1000, + "depth": 40, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "Basic", + "tint": "255;255;255", + "frontFaceResourceName": "", + "frontFaceVisible": false, + "frontFaceResourceRepeat": false, + "backFaceResourceName": "", + "backFaceVisible": false, + "backFaceResourceRepeat": false, + "leftFaceResourceName": "", + "leftFaceVisible": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceName": "", + "rightFaceVisible": false, + "rightFaceResourceRepeat": false, + "topFaceResourceName": "", + "topFaceVisible": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceName": "", + "bottomFaceVisible": false, + "bottomFaceResourceRepeat": false + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "AreaPlaceholder" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [ + { + "angle": 0, + "customSize": true, + "width": 1000, + "height": 1000, + "depth": 40, + "keepRatio": true, + "layer": "", + "name": "AreaPlaceholder", + "persistentUuid": "5d7a8b1e-3c2f-4e6a-9b1d-001000001000", + "x": 0, + "y": 0, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": {}, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Terrain3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Terrain3D::DefineGrassHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "for (const child of object.getChildrenContainer().getAdhocListOfAllInstances()) {", + " child.deleteFromScene();", + "}", + "if (object.__terrainGrass) object.__terrainGrass.dispose();", + "object.__terrainGrass = new gdjs.__terrainGrassExtension.Grass(object);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::TerrainGrass" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDestroy", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrainGrass.dispose();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::TerrainGrass" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const game = runtimeScene.getGame();", + "// Time doesn't pass in the scene editor: real time is used to show the wind.", + "const isInGameEdition = game.isInGameEdition && game.isInGameEdition();", + "const elapsedTime = isInGameEdition ? 1 / 60 : object.getElapsedTime() / 1000;", + "const time = isInGameEdition", + " ? performance.now() / 1000", + " : object.getRuntimeScene().getTimeManager().getTimeFromStart() / 1000;", + "const layer = object.getInstanceContainer().getLayer(object.getLayer());", + "object.__terrainGrass.update(elapsedTime, time, layer.getRenderer().getThreeCamera());" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::TerrainGrass" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrainGrass.updateFromProperties();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::TerrainGrass" + } + ], + "objectGroups": [] + } + ], + "propertyDescriptors": [ + { + "value": "60", + "type": "Number", + "label": "Density", + "description": "The number of blades in a square of 100 x 100 pixels.", + "group": "Blades", + "name": "Density" + }, + { + "value": "36", + "type": "Number", + "label": "Blade height", + "unit": "Pixel", + "description": "The average height of blades (each blade is between 60% and 140% of it).", + "group": "Blades", + "name": "BladeHeight" + }, + { + "value": "5", + "type": "Number", + "label": "Blade width", + "unit": "Pixel", + "group": "Blades", + "name": "BladeWidth" + }, + { + "value": "52;104;36", + "type": "Color", + "label": "Base color", + "group": "Blades", + "name": "BaseColor" + }, + { + "value": "150;196;82", + "type": "Color", + "label": "Tip color", + "group": "Blades", + "name": "TipColor" + }, + { + "value": "1", + "type": "Number", + "label": "Seed", + "description": "Each seed places blades differently.", + "group": "Blades", + "name": "Seed" + }, + { + "value": "1", + "type": "Choice", + "label": "Grows on terrain layer", + "description": "On a 3D terrain, blades only grow where this layer is painted. A new terrain has layer 1 painted everywhere.", + "group": "Ground", + "choices": [ + { + "label": "Any", + "value": "0" + }, + { + "label": "Layer 1", + "value": "1" + }, + { + "label": "Layer 2", + "value": "2" + }, + { + "label": "Layer 3", + "value": "3" + }, + { + "label": "Layer 4", + "value": "4" + } + ], + "name": "GroundLayer" + }, + { + "value": "40", + "type": "Number", + "label": "Maximum slope", + "unit": "DegreeAngle", + "description": "No blades grow on steeper ground.", + "group": "Ground", + "name": "MaxSlope" + }, + { + "value": "1", + "type": "Number", + "label": "Wind strength", + "description": "0 for no wind.", + "group": "Wind", + "name": "WindStrength" + }, + { + "value": "2", + "type": "Number", + "label": "Wind speed", + "group": "Wind", + "name": "WindSpeed" + }, + { + "value": "", + "type": "String", + "label": "Object bending the grass", + "description": "The name of an object pushing blades aside when walking through the grass, like the player. The 8 first instances are used.", + "group": "Wind", + "name": "BendingObject" + }, + { + "value": "2500", + "type": "Number", + "label": "Fade distance", + "unit": "Pixel", + "description": "Blades are less and less dense up to this distance from the camera, and hidden after.", + "group": "Rendering", + "name": "FadeDistance" + } + ] + }, + { + "areaMaxX": 2000, + "areaMaxY": 2000, + "areaMaxZ": 200, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "defaultName": "ScatteredModels", + "assetStoreTag": "3d scattered models", + "description": "Many copies of a 3D model (trees, rocks, bushes, flowers...) placed in the area of the object and drawn all at once, which is much faster than an object for each one. They stand on the highest 3D terrain at each point, or at the Z position of the object where there is no terrain. Paint where they grow with the brushes of the scene editor.", + "fullName": "Scattered models (forests, rocks...)", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0xMCAyMXYtM0gzbDUtNUg1bDUtNUg3bDUtNSA1IDVoLTNsNSA1aC0zbDUgNWgtN3YzeiIvPjwvc3ZnPg==", + "is3D": true, + "isUsingLegacyInstancesRenderer": true, + "name": "ScatteredModels", + "previewIconUrl": "", + "objects": [ + { + "assetStoreId": "", + "name": "AreaPlaceholder", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 2000, + "height": 2000, + "depth": 200, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "backFaceUpThroughWhichAxisRotation": "X", + "materialType": "Basic", + "tint": "255;255;255", + "frontFaceResourceName": "", + "frontFaceVisible": false, + "frontFaceResourceRepeat": false, + "backFaceResourceName": "", + "backFaceVisible": false, + "backFaceResourceRepeat": false, + "leftFaceResourceName": "", + "leftFaceVisible": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceName": "", + "rightFaceVisible": false, + "rightFaceResourceRepeat": false, + "topFaceResourceName": "", + "topFaceVisible": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceName": "", + "bottomFaceVisible": false, + "bottomFaceResourceRepeat": false + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "AreaPlaceholder" + } + ] + }, + "objectsGroups": [], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "", + "renderingType": "", + "visibility": true, + "cameras": [ + { + "defaultSize": true, + "defaultViewport": true, + "height": 0, + "viewportBottom": 1, + "viewportLeft": 0, + "viewportRight": 1, + "viewportTop": 0, + "width": 0 + } + ], + "effects": [] + } + ], + "instances": [ + { + "angle": 0, + "customSize": true, + "width": 2000, + "height": 2000, + "depth": 200, + "keepRatio": true, + "layer": "", + "name": "AreaPlaceholder", + "persistentUuid": "5d7a8b1e-3c2f-4e6a-9b1d-002000002000", + "x": 0, + "y": 0, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": {}, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Terrain3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Terrain3D::DefineScatterHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "for (const child of object.getChildrenContainer().getAdhocListOfAllInstances()) {", + " child.deleteFromScene();", + "}", + "if (object.__terrainScatter) object.__terrainScatter.dispose();", + "object.__terrainScatter = new gdjs.__terrainScatterExtension.Scatter(object);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDestroy", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrainScatter.dispose();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const game = runtimeScene.getGame();", + "const isInGameEdition = game.isInGameEdition && game.isInGameEdition();", + "const elapsedTime = isInGameEdition ? 1 / 60 : object.getElapsedTime() / 1000;", + "const layer = object.getInstanceContainer().getLayer(object.getLayer());", + "object.__terrainScatter.update(elapsedTime, layer.getRenderer().getThreeCamera());" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "object.__terrainScatter.onHotReloading();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + } + ], + "objectGroups": [] + }, + { + "fullName": "Add copies", + "functionType": "Action", + "name": "AddCopies", + "sentence": "Add copies of _PARAM0_ around _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "object.__terrainScatter.addCopies(x, y, radius);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Paint the density of copies around a position, up to the density of the object (copies removed with \"Erase copies\" come back). The effect fades out up to the radius.", + "group": "Paint" + }, + { + "fullName": "Erase copies", + "functionType": "Action", + "name": "EraseCopies", + "sentence": "Erase the copies of _PARAM0_ in a circle at _PARAM1_; _PARAM2_ with a radius of _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const radius = eventsFunctionContext.getArgument(\"Radius\");", + "object.__terrainScatter.removeCopies(x, y, radius);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position of the center", + "name": "X", + "type": "expression" + }, + { + "description": "Y position of the center", + "name": "Y", + "type": "expression" + }, + { + "description": "Radius", + "name": "Radius", + "type": "expression" + } + ], + "objectGroups": [], + "description": "Remove the copies standing in a circle, for example a tree cut by the player (use the position of the nearest copy and a small radius) or a forest cleared for a building. They come back when the scene is restarted.", + "group": "Paint" + }, + { + "fullName": "Number of copies", + "functionType": "ExpressionAndCondition", + "name": "CopyCount", + "sentence": "the number of copies", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "eventsFunctionContext.returnValue = object.__terrainScatter.getCopyCount();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + } + ], + "objectGroups": [], + "description": "the number of copies placed in the area.", + "group": "Copies", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "X position of the nearest copy", + "functionType": "Expression", + "name": "NearestCopyX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const copy = object.__terrainScatter.findNearestCopy(x, y);", + "eventsFunctionContext.returnValue = copy ? copy.x : x;" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "The X position of the base of the copy nearest to a position (the position itself if there is no copy).", + "group": "Copies", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Y position of the nearest copy", + "functionType": "Expression", + "name": "NearestCopyY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const copy = object.__terrainScatter.findNearestCopy(x, y);", + "eventsFunctionContext.returnValue = copy ? copy.y : y;" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "The Y position of the base of the copy nearest to a position (the position itself if there is no copy).", + "group": "Copies", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Z position of the nearest copy", + "functionType": "Expression", + "name": "NearestCopyZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const copy = object.__terrainScatter.findNearestCopy(x, y);", + "eventsFunctionContext.returnValue = copy ? copy.z : object.getZ();" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "The Z position of the base of the copy nearest to a position (on the ground).", + "group": "Copies", + "expressionType": { + "type": "expression" + } + }, + { + "fullName": "Distance to the nearest copy", + "functionType": "ExpressionAndCondition", + "name": "NearestCopyDistance", + "sentence": "the distance from _PARAM1_; _PARAM2_ to the nearest copy", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */", + "const object = objects[0];", + "const x = eventsFunctionContext.getArgument(\"X\");", + "const y = eventsFunctionContext.getArgument(\"Y\");", + "const copy = object.__terrainScatter.findNearestCopy(x, y);", + "eventsFunctionContext.returnValue = copy ? Math.hypot(copy.x - x, copy.y - y) : Number.MAX_VALUE;" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object", + "supplementaryInformation": "Terrain3D::ScatteredModels" + }, + { + "description": "X position", + "name": "X", + "type": "expression" + }, + { + "description": "Y position", + "name": "Y", + "type": "expression" + } + ], + "objectGroups": [], + "description": "the distance (on X and Y) from a position to the base of the nearest copy. It's a very big number when there is no copy.", + "group": "Copies", + "expressionType": { + "type": "expression" + } + } + ], + "propertyDescriptors": [ + { + "value": "", + "type": "Resource", + "label": "3D model", + "description": "The model copied everywhere (a .glb file). Models standing on the Y axis, like most models of the asset store, are stood up along Z.", + "group": "Model", + "extraInformation": [ + "model3D" + ], + "name": "Model" + }, + { + "value": "300", + "type": "Number", + "label": "Height of a copy", + "unit": "Pixel", + "description": "Copies keep the proportions of the model.", + "group": "Model", + "name": "Size" + }, + { + "value": "0.8", + "type": "Number", + "label": "Smallest random scale", + "description": "Each copy is scaled at random between the smallest and the largest scale.", + "group": "Model", + "name": "MinScale" + }, + { + "value": "1.2", + "type": "Number", + "label": "Largest random scale", + "group": "Model", + "name": "MaxScale" + }, + { + "value": "true", + "type": "Boolean", + "label": "Random rotation", + "description": "Turn each copy around the Z axis at random.", + "group": "Model", + "name": "RandomRotation" + }, + { + "value": "255;255;255", + "type": "Color", + "label": "Random tint", + "description": "Each copy is tinted between white and this color, at random. White for no tint.", + "group": "Model", + "name": "RandomTint" + }, + { + "value": "0.3", + "type": "Number", + "label": "Density", + "description": "The number of copies in a square of 100 x 100 pixels where the density is fully painted (0.3 is about one copy every 180 pixels). Copies never overlap their neighbors much: they are placed at random in a grid.", + "group": "Placement", + "name": "Density" + }, + { + "value": "1", + "type": "Number", + "label": "Seed", + "description": "Each seed places copies differently.", + "group": "Placement", + "name": "Seed" + }, + { + "value": "Everywhere", + "type": "Choice", + "label": "Copies grow", + "description": "Everywhere in the area, or only where they are painted with the \"Add copies\" brush (or the edits).", + "group": "Placement", + "choices": [ + { + "label": "Everywhere (erase them with the brush)", + "value": "Everywhere" + }, + { + "label": "Only where painted", + "value": "Painted" + } + ], + "name": "Coverage" + }, + { + "value": "1", + "type": "Choice", + "label": "Grows on terrain layer", + "description": "On a 3D terrain, copies only grow where this layer is painted, away from its borders (so that trees don't stand on roads painted with another layer). A new terrain has layer 1 painted everywhere.", + "group": "Ground", + "choices": [ + { + "label": "Any", + "value": "0" + }, + { + "label": "Layer 1", + "value": "1" + }, + { + "label": "Layer 2", + "value": "2" + }, + { + "label": "Layer 3", + "value": "3" + }, + { + "label": "Layer 4", + "value": "4" + } + ], + "name": "GroundLayer" + }, + { + "value": "30", + "type": "Number", + "label": "Maximum slope", + "unit": "DegreeAngle", + "description": "No copies grow on steeper ground.", + "group": "Ground", + "name": "MaxSlope" + }, + { + "value": "-100000", + "type": "Number", + "label": "Minimum ground Z", + "unit": "Pixel", + "description": "No copies grow where the ground is lower (for example, under the water level).", + "group": "Ground", + "name": "MinGroundZ" + }, + { + "value": "100000", + "type": "Number", + "label": "Maximum ground Z", + "unit": "Pixel", + "description": "No copies grow where the ground is higher (for example, above the tree line).", + "group": "Ground", + "name": "MaxGroundZ" + }, + { + "value": "false", + "type": "Boolean", + "label": "Trunk collisions", + "description": "Give each copy a static collision shape around its trunk, for the 3D physics behavior (add it to this object, with the \"Static\" type) and navigation meshes. Without them, these behaviors have no effect on this object.", + "group": "Collisions", + "name": "TrunkCollisions" + }, + { + "value": "30", + "type": "Number", + "label": "Trunk diameter", + "unit": "Pixel", + "description": "The diameter of the collision shape of a copy at the height given in \"Height of a copy\" (it is scaled with the copy).", + "group": "Collisions", + "name": "TrunkDiameter" + }, + { + "value": "8000", + "type": "Number", + "label": "Draw distance", + "unit": "Pixel", + "description": "Copies shrink and are hidden at this distance from the camera.", + "group": "Rendering", + "name": "DrawDistance" + }, + { + "value": "true", + "type": "Boolean", + "label": "Cast shadows", + "group": "Rendering", + "name": "CastShadow" + }, + { + "value": "true", + "type": "Boolean", + "label": "Receive shadows", + "group": "Rendering", + "name": "ReceiveShadow" + }, + { + "value": "", + "type": "MultilineString", + "label": "Edits", + "description": "Where copies are added or erased, applied in order, as a JSON list, for example [{\"tool\": \"add\", \"x\": 0.3, \"y\": 0.4, \"radius\": 0.15}, {\"tool\": \"erase\", \"x\": 0.1, \"y\": 0.9, \"toX\": 0.8, \"toY\": 0.2, \"radius\": 0.02}]. Tools: add (density: from 0 to 1, 1 by default, how dense copies are compared to the Density property) and erase. strength (from 0 to 1) makes a stroke lighter. x, y and radius are fractions (0 to 1) of the object size (x is (scene X - object X) / object width). Add toX and toY to paint along a line (a row of trees, a path through a forest). With \"Copies grow: only where painted\", only the added areas have copies. Shown in the scene editor.", + "group": "Placement", + "advanced": true, + "name": "Edits" + }, + { + "value": "", + "type": "String", + "label": "Painted data", + "description": "Written by the brushes of the scene editor, in a compact format not meant to be read or written by hand: use Edits instead. Clear it to go back to the coverage and the edits.", + "advanced": true, + "name": "PaintData" + } + ] + } + ] + } + ], + "externalLayouts": [] +} diff --git a/examples/starting-expanded-3d-tank/theme-slots.json b/examples/starting-expanded-3d-tank/theme-slots.json new file mode 100644 index 000000000..a769ecff1 --- /dev/null +++ b/examples/starting-expanded-3d-tank/theme-slots.json @@ -0,0 +1,53 @@ +{ + "version": 1, + "objects": { + "scene:Game Scene/Target": "character.enemy", + "scene:Game Scene/Bullet": "projectile.player", + "object:TankConfiguration::CombinedTank/TankBase": "vehicle.tank.base", + "object:TankConfiguration::TankTop/TankTop": "vehicle.tank.turret", + "object:TankConfiguration::TankTop/TankCanon": "vehicle.tank.cannon", + "scene:Game Scene/SackTrench": "prop.obstacle", + "scene:Game Scene/Container": "prop.container", + "scene:Game Scene/Trees": { + "Model": "nature.tree" + }, + "scene:Game Scene/TallTrees": { + "Model": "nature.tree" + }, + "scene:Game Scene/Terrain": { + "Layer1Texture": { + "slot": "terrain.ground", + "normalMap": "Layer1NormalMap" + }, + "Layer2Texture": { + "slot": "terrain.road", + "normalMap": "Layer2NormalMap" + }, + "Layer3Texture": { + "slot": "terrain.rock", + "normalMap": "Layer3NormalMap" + }, + "Layer4Texture": { + "slot": "terrain.dirt", + "normalMap": "Layer4NormalMap" + } + }, + "scene:Game Scene/Grass": { + "BaseColor": "terrain.grass.base", + "TipColor": "terrain.grass.tip" + } + }, + "effects": { + "effect:Game Scene//SkyBox": "sky.day" + }, + "ignoredObjects": [ + "scene:Game Scene/BrickWall", + "scene:Game Scene/BrickWallBroken", + "scene:Game Scene/Wreck", + "scene:Game Scene/WaterTank", + "scene:Game Scene/MapLimit", + "object:Terrain3D::Terrain3D/AreaPlaceholder", + "object:Terrain3D::TerrainGrass/AreaPlaceholder", + "object:Terrain3D::ScatteredModels/AreaPlaceholder" + ] +}