diff --git a/examples/starting-expanded-3d-flight-sim/README.md b/examples/starting-expanded-3d-flight-sim/README.md new file mode 100644 index 000000000..7592e1cd5 --- /dev/null +++ b/examples/starting-expanded-3d-flight-sim/README.md @@ -0,0 +1,3 @@ +# 3D Flight Simulator Expanded + +A 3D flight game over a large landscape made with a 3D terrain: mountains with snowy tops, valleys, a lake, forests and farms. The player flies a plane, a 3D model, by controlling its tilt (left/right), pitch (forward/backward) and speed, using keyboard or mobile touch controls. The plane stays above the ground and inside the map. A third-person camera follows the plane's angle and pitch. diff --git a/examples/starting-expanded-3d-flight-sim/assets/Barn.glb b/examples/starting-expanded-3d-flight-sim/assets/Barn.glb new file mode 100644 index 000000000..c62c9ec15 Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/Barn.glb differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/Common Tree 2.glb b/examples/starting-expanded-3d-flight-sim/assets/Common Tree 2.glb new file mode 100644 index 000000000..515708b3c Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/Common Tree 2.glb differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/Flat dark joystick border vertical.png b/examples/starting-expanded-3d-flight-sim/assets/Flat dark joystick border vertical.png new file mode 100644 index 000000000..ec315d4b6 Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/Flat dark joystick border vertical.png differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/Flat dark joystick border.png b/examples/starting-expanded-3d-flight-sim/assets/Flat dark joystick border.png new file mode 100644 index 000000000..b4e83e112 Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/Flat dark joystick border.png differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/Flat dark joystick thumb.png b/examples/starting-expanded-3d-flight-sim/assets/Flat dark joystick thumb.png new file mode 100644 index 000000000..8acf179f8 Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/Flat dark joystick thumb.png differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/Pine Tree 1.glb b/examples/starting-expanded-3d-flight-sim/assets/Pine Tree 1.glb new file mode 100644 index 000000000..72841baa2 Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/Pine Tree 1.glb differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/Silo.glb b/examples/starting-expanded-3d-flight-sim/assets/Silo.glb new file mode 100644 index 000000000..318f05137 Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/Silo.glb differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/Skybox_Back.png b/examples/starting-expanded-3d-flight-sim/assets/Skybox_Back.png new file mode 100644 index 000000000..aa0891ef2 Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/Skybox_Back.png differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/Skybox_Bottom.png b/examples/starting-expanded-3d-flight-sim/assets/Skybox_Bottom.png new file mode 100644 index 000000000..16e34f3da Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/Skybox_Bottom.png differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/Skybox_Front.png b/examples/starting-expanded-3d-flight-sim/assets/Skybox_Front.png new file mode 100644 index 000000000..018d5faac Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/Skybox_Front.png differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/Skybox_Left.png b/examples/starting-expanded-3d-flight-sim/assets/Skybox_Left.png new file mode 100644 index 000000000..e4053f388 Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/Skybox_Left.png differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/Skybox_Right.png b/examples/starting-expanded-3d-flight-sim/assets/Skybox_Right.png new file mode 100644 index 000000000..d14c192b1 Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/Skybox_Right.png differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/Skybox_Top.png b/examples/starting-expanded-3d-flight-sim/assets/Skybox_Top.png new file mode 100644 index 000000000..e83ef6a1b Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/Skybox_Top.png differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/TerrainDirt.jpg b/examples/starting-expanded-3d-flight-sim/assets/TerrainDirt.jpg new file mode 100644 index 000000000..71221d59d Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/TerrainDirt.jpg differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/TerrainDirtNormal.jpg b/examples/starting-expanded-3d-flight-sim/assets/TerrainDirtNormal.jpg new file mode 100644 index 000000000..f701da364 Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/TerrainDirtNormal.jpg differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/TerrainGrass.jpg b/examples/starting-expanded-3d-flight-sim/assets/TerrainGrass.jpg new file mode 100644 index 000000000..c1638afab Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/TerrainGrass.jpg differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/TerrainGrassNormal.jpg b/examples/starting-expanded-3d-flight-sim/assets/TerrainGrassNormal.jpg new file mode 100644 index 000000000..3a28a4784 Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/TerrainGrassNormal.jpg differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/TerrainRock.jpg b/examples/starting-expanded-3d-flight-sim/assets/TerrainRock.jpg new file mode 100644 index 000000000..d838e88fb Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/TerrainRock.jpg differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/TerrainRockNormal.jpg b/examples/starting-expanded-3d-flight-sim/assets/TerrainRockNormal.jpg new file mode 100644 index 000000000..7c8288e66 Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/TerrainRockNormal.jpg differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/TerrainSnow.jpg b/examples/starting-expanded-3d-flight-sim/assets/TerrainSnow.jpg new file mode 100644 index 000000000..0308e76c5 Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/TerrainSnow.jpg differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/TerrainSnowNormal.jpg b/examples/starting-expanded-3d-flight-sim/assets/TerrainSnowNormal.jpg new file mode 100644 index 000000000..283e68f2f Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/TerrainSnowNormal.jpg differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/WaterNormalMap.jpg b/examples/starting-expanded-3d-flight-sim/assets/WaterNormalMap.jpg new file mode 100644 index 000000000..558426e0e Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/WaterNormalMap.jpg differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/Windmill.glb b/examples/starting-expanded-3d-flight-sim/assets/Windmill.glb new file mode 100644 index 000000000..af75ef3eb Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/Windmill.glb differ diff --git a/examples/starting-expanded-3d-flight-sim/assets/aircraft_orange.glb b/examples/starting-expanded-3d-flight-sim/assets/aircraft_orange.glb new file mode 100644 index 000000000..204e0ee72 Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/assets/aircraft_orange.glb differ diff --git a/examples/starting-expanded-3d-flight-sim/preview.png b/examples/starting-expanded-3d-flight-sim/preview.png new file mode 100644 index 000000000..8258b6f56 Binary files /dev/null and b/examples/starting-expanded-3d-flight-sim/preview.png differ diff --git a/examples/starting-expanded-3d-flight-sim/starting-expanded-3d-flight-sim.json b/examples/starting-expanded-3d-flight-sim/starting-expanded-3d-flight-sim.json new file mode 100644 index 000000000..838e7e66c --- /dev/null +++ b/examples/starting-expanded-3d-flight-sim/starting-expanded-3d-flight-sim.json @@ -0,0 +1,21098 @@ +{ + "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": "ab3fbeb0-49a4-5cb7-a08b-574031b8fc34", + "scaleMode": "linear", + "sizeOnStartupMode": "", + "templateSlug": "starting-top-down", + "version": "1.0.0", + "name": "3D Flight Simulator Expanded", + "description": "A 3D flight game over mountains, valleys and a lake made with a 3D terrain, where the plane cannot fly into the ground.", + "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 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/aircraft_orange.glb", + "kind": "model3D", + "metadata": "", + "name": "assets\\aircraft_orange.glb", + "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/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/TerrainSnow.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainSnow.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainSnowNormal.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainSnowNormal.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/WaterNormalMap.jpg", + "kind": "image", + "metadata": "", + "name": "assets/WaterNormalMap.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/Barn.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Barn.glb", + "userAdded": true + }, + { + "file": "assets/Silo.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Silo.glb", + "userAdded": true + }, + { + "file": "assets/Windmill.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Windmill.glb", + "userAdded": true + }, + { + "file": "assets/Pine Tree 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Pine Tree 1.glb", + "userAdded": true + }, + { + "file": "assets/Common Tree 2.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Common 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": true, + "gridType": "rectangular", + "gridWidth": 64, + "gridHeight": 64, + "gridDepth": 32, + "gridOffsetX": 0, + "gridOffsetY": 0, + "gridOffsetZ": 0, + "gridColor": 10401023, + "gridAlpha": 0.8, + "snap": true, + "zoomFactor": 0.546875000000003, + "windowMask": false, + "selectedLayer": "", + "gameEditorMode": "embedded-game" + }, + "objectsGroups": [ + { + "name": "MobileJoysticks", + "objects": [ + { + "name": "MoveJoystick" + }, + { + "name": "SpeedJoystick" + } + ] + } + ], + "variables": [], + "instances": [ + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "MobileControls", + "locked": true, + "name": "MoveJoystick", + "persistentUuid": "b0c31bb4-0add-4dda-9367-c5136e4b118d", + "width": 0, + "x": 176, + "y": 576, + "zOrder": 4, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "MobileControls", + "locked": true, + "name": "SpeedJoystick", + "persistentUuid": "67fa0421-8daf-47b5-ac59-a24e1d35ddef", + "width": 0, + "x": 1104, + "y": 576, + "zOrder": 6, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 16384, + "width": 16384, + "layer": "", + "locked": false, + "name": "Terrain", + "persistentUuid": "99099fd4-3a81-566e-9a57-c73ed800fd4f", + "x": -8192, + "y": -8192, + "z": 0, + "zOrder": 0, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 3000 + }, + { + "angle": 30.96, + "customSize": true, + "height": 1286.2, + "width": 2418.1, + "layer": "", + "locked": false, + "name": "Water", + "persistentUuid": "9ac7c7c5-9a1f-5da8-bc02-50153fa31a89", + "x": 595.4, + "y": -50.4, + "z": 568.5, + "zOrder": 2, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 1 + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "PlayerAircraft", + "persistentUuid": "179e3e24-b557-53b9-82f4-fac75d6cb266", + "x": -900, + "y": 2400, + "z": 1005.2, + "zOrder": 3, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "House", + "persistentUuid": "10bcc4ec-6cdb-5e1b-a31a-34366cd4d17b", + "x": 300, + "y": 2950, + "z": 619.7, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Silo", + "persistentUuid": "e5c988fd-6644-5d97-a6eb-270462a2b5a4", + "x": 650, + "y": 2950, + "z": 714.2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Windsock", + "persistentUuid": "ca64bdab-1055-5564-a4c3-64a6711f781f", + "x": -700, + "y": 2050, + "z": 620, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 16384, + "width": 16384, + "layer": "", + "locked": true, + "name": "PineTrees", + "persistentUuid": "d956bd94-cb6c-5d00-82b0-63d99ad0bfb2", + "x": -8192, + "y": -8192, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 16384, + "width": 16384, + "layer": "", + "locked": true, + "name": "Trees", + "persistentUuid": "679cff4a-c697-5423-a240-29572c6950bc", + "x": -8192, + "y": -8192, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + } + ], + "objects": [ + { + "assetStoreId": "e71bd69f896d6c7531b48c65ceb5da25071d4fbdeb518aeceecba8d21f34ed8d", + "name": "MoveJoystick", + "type": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "variant": "", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "DeadZoneRadius": 0.1 + }, + "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": [] + } + ] + } + ] + } + ] + } + } + }, + { + "assetStoreId": "e71bd69f896d6c7531b48c65ceb5da25071d4fbdeb518aeceecba8d21f34ed8d", + "name": "SpeedJoystick", + "type": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "variant": "", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "DeadZoneRadius": 0.1, + "JoystickIdentifier": "Secondary", + "ControllerIdentifier": 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 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": "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": "PlayerAircraft", + "persistentUuid": "d202a93f-6f43-41a3-9e4e-7c8d6f651659", + "type": "Scene3D::Model3DObject", + "variables": [ + { + "name": "FlyingSpeed", + "type": "number", + "value": 200 + } + ], + "effects": [], + "behaviors": [ + { + "name": "ThirdPersonCamera", + "type": "ThirdPersonCamera::ThirdPersonCamera", + "Object3D": "Object3D", + "RotationHalfwayDuration": 0.2, + "ElevationHalfwayDuration": 0.2, + "TranslationZHalfwayDuration": 0.2, + "Distance": 400, + "OffsetX": 0, + "OffsetY": 0, + "OffsetZ": 0, + "RotationAngleOffset": 0, + "ElevationAngleOffset": 10, + "FollowFreeAreaZMax": 0, + "FollowFreeAreaZMin": 0, + "RotationLogSpeed": 2.0247e-320, + "ElevationLogSpeed": 2.0247e-320, + "TranslationZLogSpeed": 2.0247e-320, + "IsCalledManually": false, + "CameraZ": 0, + "HasJustBeenCreated": true, + "IsRotatingWithObject": true, + "IsElevatingWithObject": true, + "TargetedRotationAngle": 0, + "TargetedElevationAngle": 0 + } + ], + "content": { + "centerLocation": "ModelOrigin", + "crossfadeDuration": 0.10000000149011612, + "depth": 100, + "height": 100, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets\\aircraft_orange.glb", + "originLocation": "ModelOrigin", + "rotationX": 0, + "rotationY": 0, + "rotationZ": 0, + "width": 100, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "Terrain", + "type": "Terrain3D::Terrain3D", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Relief": "Hills", + "Seed": 7, + "Resolution": "256", + "TextureSize": 900, + "CliffLayer": "3", + "CliffAngle": 35, + "NormalMapStrength": 1, + "HeightBlending": 0.8, + "TextureVariation": 0.5, + "Edits": "[{\"tool\":\"raise\",\"x\":0,\"y\":0,\"toX\":1,\"toY\":0,\"radius\":0.1343,\"height\":0.5667},{\"tool\":\"raise\",\"x\":0,\"y\":1,\"toX\":1,\"toY\":1,\"radius\":0.1343,\"height\":0.5667},{\"tool\":\"raise\",\"x\":0,\"y\":0,\"toX\":0,\"toY\":1,\"radius\":0.1343,\"height\":0.5667},{\"tool\":\"raise\",\"x\":1,\"y\":0,\"toX\":1,\"toY\":1,\"radius\":0.1343,\"height\":0.5667},{\"tool\":\"raise\",\"x\":0.1535,\"y\":0,\"radius\":0.1218,\"height\":0.2846},{\"tool\":\"raise\",\"x\":1,\"y\":0.1435,\"radius\":0.084,\"height\":0.3715},{\"tool\":\"raise\",\"x\":0.158,\"y\":1,\"radius\":0.1045,\"height\":0.4446},{\"tool\":\"raise\",\"x\":0,\"y\":0.0844,\"radius\":0.1222,\"height\":0.4061},{\"tool\":\"raise\",\"x\":0.3991,\"y\":0,\"radius\":0.0801,\"height\":0.3419},{\"tool\":\"raise\",\"x\":1,\"y\":0.3451,\"radius\":0.1286,\"height\":0.3006},{\"tool\":\"raise\",\"x\":0.4133,\"y\":1,\"radius\":0.1191,\"height\":0.2925},{\"tool\":\"raise\",\"x\":0,\"y\":0.3029,\"radius\":0.1229,\"height\":0.2819},{\"tool\":\"raise\",\"x\":0.5909,\"y\":0,\"radius\":0.0861,\"height\":0.343},{\"tool\":\"raise\",\"x\":1,\"y\":0.6987,\"radius\":0.0928,\"height\":0.2522},{\"tool\":\"raise\",\"x\":0.6773,\"y\":1,\"radius\":0.0968,\"height\":0.3284},{\"tool\":\"raise\",\"x\":0,\"y\":0.6979,\"radius\":0.0943,\"height\":0.3234},{\"tool\":\"raise\",\"x\":0.8326,\"y\":0,\"radius\":0.1271,\"height\":0.4019},{\"tool\":\"raise\",\"x\":1,\"y\":0.9318,\"radius\":0.1074,\"height\":0.4212},{\"tool\":\"raise\",\"x\":0.8974,\"y\":1,\"radius\":0.087,\"height\":0.3817},{\"tool\":\"raise\",\"x\":0,\"y\":0.8866,\"radius\":0.0967,\"height\":0.4151},{\"tool\":\"raise\",\"x\":0.1948,\"y\":0.2864,\"toX\":0.3474,\"toY\":0.4084,\"radius\":0.0793,\"height\":0.2333},{\"tool\":\"raise\",\"x\":0.3474,\"y\":0.4084,\"toX\":0.4084,\"toY\":0.561,\"radius\":0.0793,\"height\":0.2333},{\"tool\":\"raise\",\"x\":0.1948,\"y\":0.2864,\"radius\":0.0549,\"height\":0.2479},{\"tool\":\"raise\",\"x\":0.3474,\"y\":0.4084,\"radius\":0.0549,\"height\":0.1678},{\"tool\":\"raise\",\"x\":0.4084,\"y\":0.561,\"radius\":0.0549,\"height\":0.2909},{\"tool\":\"raise\",\"x\":0.6221,\"y\":0.1948,\"toX\":0.7136,\"toY\":0.3779,\"radius\":0.0793,\"height\":0.2333},{\"tool\":\"raise\",\"x\":0.7136,\"y\":0.3779,\"toX\":0.8357,\"toY\":0.4695,\"radius\":0.0793,\"height\":0.2333},{\"tool\":\"raise\",\"x\":0.6221,\"y\":0.1948,\"radius\":0.0549,\"height\":0.2122},{\"tool\":\"raise\",\"x\":0.7136,\"y\":0.3779,\"radius\":0.0549,\"height\":0.2133},{\"tool\":\"raise\",\"x\":0.8357,\"y\":0.4695,\"radius\":0.0549,\"height\":0.2833},{\"tool\":\"raise\",\"x\":0.5916,\"y\":0.7136,\"toX\":0.7441,\"toY\":0.7747,\"radius\":0.0793,\"height\":0.2333},{\"tool\":\"raise\",\"x\":0.5916,\"y\":0.7136,\"radius\":0.0549,\"height\":0.1999},{\"tool\":\"raise\",\"x\":0.7441,\"y\":0.7747,\"radius\":0.0549,\"height\":0.19},{\"tool\":\"raise\",\"x\":0.1643,\"y\":0.7136,\"toX\":0.2864,\"toY\":0.8052,\"radius\":0.0793,\"height\":0.2333},{\"tool\":\"raise\",\"x\":0.1643,\"y\":0.7136,\"radius\":0.0549,\"height\":0.2778},{\"tool\":\"raise\",\"x\":0.2864,\"y\":0.8052,\"radius\":0.0549,\"height\":0.2039},{\"tool\":\"raise\",\"x\":0.865,\"y\":0.1438,\"radius\":0.0455,\"height\":0.1385},{\"tool\":\"raise\",\"x\":0.3639,\"y\":0.6177,\"radius\":0.0338,\"height\":0.0593},{\"tool\":\"raise\",\"x\":0.3194,\"y\":0.815,\"radius\":0.0326,\"height\":0.1357},{\"tool\":\"raise\",\"x\":0.8324,\"y\":0.3427,\"radius\":0.0533,\"height\":0.1499},{\"tool\":\"raise\",\"x\":0.754,\"y\":0.5578,\"radius\":0.0714,\"height\":0.132},{\"tool\":\"raise\",\"x\":0.5294,\"y\":0.2143,\"radius\":0.0431,\"height\":0.1118},{\"tool\":\"raise\",\"x\":0.2473,\"y\":0.3184,\"radius\":0.0633,\"height\":0.0662},{\"tool\":\"raise\",\"x\":0.1924,\"y\":0.3731,\"radius\":0.0649,\"height\":0.125},{\"tool\":\"raise\",\"x\":0.4359,\"y\":0.2053,\"radius\":0.0429,\"height\":0.1616},{\"tool\":\"raise\",\"x\":0.3729,\"y\":0.6485,\"radius\":0.0623,\"height\":0.0619},{\"tool\":\"raise\",\"x\":0.7076,\"y\":0.8162,\"radius\":0.0564,\"height\":0.063},{\"tool\":\"raise\",\"x\":0.3988,\"y\":0.4289,\"radius\":0.0327,\"height\":0.0992},{\"tool\":\"raise\",\"x\":0.806,\"y\":0.3618,\"radius\":0.0633,\"height\":0.0561},{\"tool\":\"raise\",\"x\":0.7318,\"y\":0.2075,\"radius\":0.0524,\"height\":0.1465},{\"tool\":\"raise\",\"x\":0.2874,\"y\":0.5707,\"radius\":0.0348,\"height\":0.0924},{\"tool\":\"raise\",\"x\":0.6197,\"y\":0.32,\"radius\":0.0596,\"height\":0.078},{\"tool\":\"raise\",\"x\":0.3193,\"y\":0.855,\"radius\":0.058,\"height\":0.1028},{\"tool\":\"smooth\",\"x\":0.5,\"y\":0.5,\"radius\":0.4272,\"strength\":0.2},{\"tool\":\"smooth\",\"x\":0.6099,\"y\":0.5366,\"radius\":0.1282,\"strength\":0.3},{\"tool\":\"flatten\",\"x\":0.5793,\"y\":0.5183,\"toX\":0.6404,\"toY\":0.5549,\"radius\":0.0793,\"z\":0.2095},{\"tool\":\"flatten\",\"x\":0.5793,\"y\":0.5183,\"toX\":0.6404,\"toY\":0.5549,\"radius\":0.0793,\"z\":0.2095},{\"tool\":\"flatten\",\"x\":0.5793,\"y\":0.5183,\"toX\":0.6404,\"toY\":0.5549,\"radius\":0.0793,\"z\":0.2095},{\"tool\":\"flatten\",\"x\":0.5793,\"y\":0.5183,\"toX\":0.6404,\"toY\":0.5549,\"radius\":0.0793,\"z\":0.2095},{\"tool\":\"paint\",\"x\":0.5793,\"y\":0.5183,\"toX\":0.6404,\"toY\":0.5549,\"radius\":0.0916,\"layer\":2,\"strength\":0.6},{\"tool\":\"flatten\",\"x\":0.5793,\"y\":0.5183,\"toX\":0.6404,\"toY\":0.5549,\"radius\":0.0549,\"z\":0.1795},{\"tool\":\"flatten\",\"x\":0.5793,\"y\":0.5183,\"toX\":0.6404,\"toY\":0.5549,\"radius\":0.0549,\"z\":0.1795},{\"tool\":\"flatten\",\"x\":0.5793,\"y\":0.5183,\"toX\":0.6404,\"toY\":0.5549,\"radius\":0.0549,\"z\":0.1795},{\"tool\":\"smooth\",\"x\":0.5793,\"y\":0.5183,\"toX\":0.6404,\"toY\":0.5549,\"radius\":0.0671,\"strength\":0.4},{\"tool\":\"flatten\",\"x\":0.4634,\"y\":0.6465,\"toX\":0.5549,\"toY\":0.6465,\"radius\":0.0397,\"z\":0.2184},{\"tool\":\"flatten\",\"x\":0.4634,\"y\":0.6465,\"toX\":0.5549,\"toY\":0.6465,\"radius\":0.0397,\"z\":0.2184},{\"tool\":\"flatten\",\"x\":0.4634,\"y\":0.6465,\"toX\":0.5549,\"toY\":0.6465,\"radius\":0.0397,\"z\":0.2184},{\"tool\":\"flatten\",\"x\":0.4634,\"y\":0.6465,\"toX\":0.5549,\"toY\":0.6465,\"radius\":0.0397,\"z\":0.2184},{\"tool\":\"paint\",\"x\":0.4695,\"y\":0.6465,\"toX\":0.5488,\"toY\":0.6465,\"radius\":0.0073,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0,\"y\":0,\"toX\":1,\"toY\":0,\"radius\":0.0916,\"layer\":4,\"strength\":0.9},{\"tool\":\"paint\",\"x\":0,\"y\":1,\"toX\":1,\"toY\":1,\"radius\":0.0916,\"layer\":4,\"strength\":0.9},{\"tool\":\"paint\",\"x\":0,\"y\":0,\"toX\":0,\"toY\":1,\"radius\":0.0916,\"layer\":4,\"strength\":0.9},{\"tool\":\"paint\",\"x\":1,\"y\":0,\"toX\":1,\"toY\":1,\"radius\":0.0916,\"layer\":4,\"strength\":0.9},{\"tool\":\"paint\",\"x\":0.1948,\"y\":0.2864,\"radius\":0.0427,\"layer\":4,\"strength\":0.8},{\"tool\":\"paint\",\"x\":0.3474,\"y\":0.4084,\"radius\":0.0427,\"layer\":4,\"strength\":0.8},{\"tool\":\"paint\",\"x\":0.4084,\"y\":0.561,\"radius\":0.0427,\"layer\":4,\"strength\":0.8},{\"tool\":\"paint\",\"x\":0.6221,\"y\":0.1948,\"radius\":0.0427,\"layer\":4,\"strength\":0.8},{\"tool\":\"paint\",\"x\":0.7136,\"y\":0.3779,\"radius\":0.0427,\"layer\":4,\"strength\":0.8},{\"tool\":\"paint\",\"x\":0.8357,\"y\":0.4695,\"radius\":0.0427,\"layer\":4,\"strength\":0.8},{\"tool\":\"paint\",\"x\":0.5916,\"y\":0.7136,\"radius\":0.0427,\"layer\":4,\"strength\":0.8},{\"tool\":\"paint\",\"x\":0.7441,\"y\":0.7747,\"radius\":0.0427,\"layer\":4,\"strength\":0.8},{\"tool\":\"paint\",\"x\":0.1643,\"y\":0.7136,\"radius\":0.0427,\"layer\":4,\"strength\":0.8},{\"tool\":\"paint\",\"x\":0.2864,\"y\":0.8052,\"radius\":0.0427,\"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/TerrainSnow.jpg", + "Layer4NormalMap": "assets/TerrainSnowNormal.jpg" + } + }, + { + "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;90;120" + } + }, + { + "assetStoreId": "8ed3b3c69d08f9f2dc09acad8812504ce6dcf77c739bdb875d53f796ff6c2556", + "name": "House", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 306.9, + "height": 420, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Barn.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 394.7, + "animations": [] + } + }, + { + "assetStoreId": "c78c6c8fc40f7a394bbfd4fc139b9a292bbefd791cd4a9faad7b0b8a0bf03960", + "name": "Silo", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 380, + "height": 147.1, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Silo.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 153.7, + "animations": [] + } + }, + { + "assetStoreId": "15e8ff9ee0f71068457846fd559f5a8327d50a8e4b6a689c9a881c1776bba082", + "name": "Windsock", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 300, + "height": 50.9, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Windmill.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 174.4, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "PineTrees", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Model": "assets/Pine Tree 1.glb", + "Size": 520, + "Density": 0.03, + "Seed": 1, + "Coverage": "Painted", + "Edits": "[{\"tool\":\"add\",\"x\":0.7084,\"y\":0.2299,\"radius\":0.0723},{\"tool\":\"add\",\"x\":0.2585,\"y\":0.64,\"radius\":0.0576},{\"tool\":\"add\",\"x\":0.4451,\"y\":0.8516,\"radius\":0.0767},{\"tool\":\"add\",\"x\":0.5211,\"y\":0.8613,\"radius\":0.0652},{\"tool\":\"add\",\"x\":0.1882,\"y\":0.5754,\"radius\":0.0746},{\"tool\":\"add\",\"x\":0.137,\"y\":0.4011,\"radius\":0.0508},{\"tool\":\"add\",\"x\":0.5392,\"y\":0.3011,\"radius\":0.0659},{\"tool\":\"add\",\"x\":0.5724,\"y\":0.3661,\"radius\":0.0875},{\"tool\":\"add\",\"x\":0.4819,\"y\":0.1882,\"radius\":0.0854},{\"tool\":\"add\",\"x\":0.5726,\"y\":0.2755,\"radius\":0.0704},{\"tool\":\"add\",\"x\":0.2695,\"y\":0.545,\"radius\":0.0507},{\"tool\":\"add\",\"x\":0.4214,\"y\":0.3366,\"radius\":0.0828},{\"tool\":\"add\",\"x\":0.5447,\"y\":0.7916,\"radius\":0.0567},{\"tool\":\"add\",\"x\":0.7208,\"y\":0.6593,\"radius\":0.0656},{\"tool\":\"add\",\"x\":0.1896,\"y\":0.5737,\"radius\":0.0856},{\"tool\":\"add\",\"x\":0.724,\"y\":0.8677,\"radius\":0.0571},{\"tool\":\"erase\",\"x\":0.4207,\"y\":0.6465,\"toX\":0.5977,\"toY\":0.6465,\"radius\":0.0427},{\"tool\":\"erase\",\"x\":0.6099,\"y\":0.5366,\"radius\":0.0916},{\"tool\":\"erase\",\"x\":0.5183,\"y\":0.6801,\"radius\":0.0153},{\"tool\":\"erase\",\"x\":0.5397,\"y\":0.6801,\"radius\":0.0071},{\"tool\":\"erase\",\"x\":0.4573,\"y\":0.6251,\"radius\":0.0049}]", + "MaxGroundZ": 1500, + "MaxSlope": 27, + "DrawDistance": 14000 + } + }, + { + "assetStoreId": "", + "name": "Trees", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Model": "assets/Common Tree 2.glb", + "Size": 460, + "Density": 0.02, + "Seed": 2, + "Coverage": "Painted", + "Edits": "[{\"tool\":\"add\",\"x\":0.2688,\"y\":0.2255,\"radius\":0.0482},{\"tool\":\"add\",\"x\":0.1995,\"y\":0.1591,\"radius\":0.0508},{\"tool\":\"add\",\"x\":0.3743,\"y\":0.2222,\"radius\":0.0606},{\"tool\":\"add\",\"x\":0.1171,\"y\":0.4106,\"radius\":0.049},{\"tool\":\"add\",\"x\":0.3841,\"y\":0.861,\"radius\":0.0655},{\"tool\":\"add\",\"x\":0.684,\"y\":0.6752,\"radius\":0.0645},{\"tool\":\"add\",\"x\":0.7525,\"y\":0.6236,\"radius\":0.0618},{\"tool\":\"add\",\"x\":0.7739,\"y\":0.1443,\"radius\":0.0504},{\"tool\":\"add\",\"x\":0.6601,\"y\":0.8514,\"radius\":0.0743},{\"tool\":\"add\",\"x\":0.1578,\"y\":0.8279,\"radius\":0.0643},{\"tool\":\"add\",\"x\":0.3136,\"y\":0.2352,\"radius\":0.0632},{\"tool\":\"add\",\"x\":0.6589,\"y\":0.2584,\"radius\":0.037},{\"tool\":\"add\",\"x\":0.4226,\"y\":0.2522,\"radius\":0.054},{\"tool\":\"add\",\"x\":0.2327,\"y\":0.7781,\"radius\":0.0677},{\"tool\":\"erase\",\"x\":0.4207,\"y\":0.6465,\"toX\":0.5977,\"toY\":0.6465,\"radius\":0.0427},{\"tool\":\"erase\",\"x\":0.6099,\"y\":0.5366,\"radius\":0.0916},{\"tool\":\"erase\",\"x\":0.5183,\"y\":0.6801,\"radius\":0.0153},{\"tool\":\"erase\",\"x\":0.5397,\"y\":0.6801,\"radius\":0.0071},{\"tool\":\"erase\",\"x\":0.4573,\"y\":0.6251,\"radius\":0.0049}]", + "MaxGroundZ": 1500, + "MaxSlope": 27, + "DrawDistance": 14000 + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "PlayerAircraft" + }, + { + "folderName": "Mobile Controls", + "children": [ + { + "objectName": "MoveJoystick" + }, + { + "objectName": "SpeedJoystick" + } + ] + }, + { + "folderName": "World", + "children": [ + { + "objectName": "Terrain" + }, + { + "objectName": "Water" + } + ] + }, + { + "folderName": "Decoration", + "children": [ + { + "objectName": "House" + }, + { + "objectName": "Silo" + }, + { + "objectName": "Windsock" + } + ] + }, + { + "folderName": "Nature", + "children": [ + { + "objectName": "PineTrees" + }, + { + "objectName": "Trees" + } + ] + } + ] + }, + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "AddForceAL" + }, + "parameters": [ + "PlayerAircraft", + "PlayerAircraft.Angle()", + "PlayerAircraft.FlyingSpeed", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationY" + }, + "parameters": [ + "PlayerAircraft", + "Object3D", + "=", + "PlayerAircraft.Object3D::RotationY() + (-0.05 * PlayerAircraft.Object3D::RotationY())" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationX" + }, + "parameters": [ + "PlayerAircraft", + "Object3D", + "=", + "PlayerAircraft.Object3D::RotationX() + (-0.05 * PlayerAircraft.Object3D::RotationX())" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"w\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "SpeedJoystick", + "\"Up\"", + "" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberObjectVariable" + }, + "parameters": [ + "PlayerAircraft", + "FlyingSpeed", + "=", + "min(500, PlayerAircraft.FlyingSpeed + 3 * TimeDelta() * 60)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"s\"" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::IsDirectionPushed4Way" + }, + "parameters": [ + "SpeedJoystick", + "\"Down\"", + "" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberObjectVariable" + }, + "parameters": [ + "PlayerAircraft", + "FlyingSpeed", + "=", + "max(100, PlayerAircraft.FlyingSpeed - 3 * TimeDelta() * 60)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::StickForceX" + }, + "parameters": [ + "MoveJoystick", + "!=", + "0", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationX" + }, + "parameters": [ + "PlayerAircraft", + "Object3D", + "=", + "max(-60, PlayerAircraft.Object3D::RotationX() - 3 * SpriteMultitouchJoystick::StickForceX(1, \"Primary\") * TimeDelta() * 60)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::StickForceY" + }, + "parameters": [ + "MoveJoystick", + "!=", + "0", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationY" + }, + "parameters": [ + "PlayerAircraft", + "Object3D", + "=", + "min(45, PlayerAircraft.Object3D::RotationY() - 1.5 * SpriteMultitouchJoystick::StickForceY(1, \"Primary\") * TimeDelta() * 60)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"Right\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationX" + }, + "parameters": [ + "PlayerAircraft", + "Object3D", + "=", + "max(-60, PlayerAircraft.Object3D::RotationX() - 3 * TimeDelta() * 60)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"Left\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationX" + }, + "parameters": [ + "PlayerAircraft", + "Object3D", + "=", + "min(60, PlayerAircraft.Object3D::RotationX() + 3 * TimeDelta() * 60)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"Up\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationY" + }, + "parameters": [ + "PlayerAircraft", + "Object3D", + "=", + "min(45, PlayerAircraft.Object3D::RotationY() + 2 * TimeDelta() * 60)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"Down\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationY" + }, + "parameters": [ + "PlayerAircraft", + "Object3D", + "=", + "max(-45, PlayerAircraft.Object3D::RotationY() - 2 * TimeDelta() * 60)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Rotate" + }, + "parameters": [ + "PlayerAircraft", + "PlayerAircraft.Object3D::RotationX() * -2 * TimeDelta() * 60", + "" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetZ" + }, + "parameters": [ + "PlayerAircraft", + "Object3D", + "=", + "PlayerAircraft.Object3D::Z() - (PlayerAircraft.Object3D::RotationY() / 10 * TimeDelta() * 60)" + ] + }, + { + "type": { + "value": "ThirdPersonCamera::ThirdPersonCamera::SetDistance" + }, + "parameters": [ + "PlayerAircraft", + "ThirdPersonCamera", + "=", + "lerp(PlayerAircraft.ThirdPersonCamera::Distance(), 200 + PlayerAircraft.FlyingSpeed / 3 * TimeDelta() * 60, 0.05)", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Scene3D::Base3DBehavior::Z" + }, + "parameters": [ + "PlayerAircraft", + "Object3D", + "<", + "Terrain.GroundZ(PlayerAircraft.X(), PlayerAircraft.Y()) + 20" + ] + } + ], + "actions": [ + { + "type": { + "value": "Scene3D::Base3DBehavior::SetZ" + }, + "parameters": [ + "PlayerAircraft", + "Object3D", + "=", + "Terrain.GroundZ(PlayerAircraft.X(), PlayerAircraft.Y()) + 20" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Keep the plane above the map." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "MettreX" + }, + "parameters": [ + "PlayerAircraft", + "=", + "clamp(PlayerAircraft.X(), -7592, 7592)" + ] + }, + { + "type": { + "value": "MettreY" + }, + "parameters": [ + "PlayerAircraft", + "=", + "clamp(PlayerAircraft.Y(), -7592, 7592)" + ] + } + ] + } + ], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 20000, + "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 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::DirectionalLight", + "name": "3D Sun Light", + "doubleParameters": { + "distanceFromCamera": 1500, + "elevation": 40, + "frustumSize": 2000, + "intensity": 0.75, + "minimumShadowBias": 0.001, + "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": 20000, + "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": "Resizable", + "type": "ResizableCapability::ResizableBehavior" + }, + { + "name": "Scale", + "type": "ScalableCapability::ScalableBehavior" + }, + { + "name": "ThirdPersonCamera", + "type": "ThirdPersonCamera::ThirdPersonCamera" + } + ] + } + ], + "externalEvents": [], + "eventsFunctionsExtensions": [ + { + "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": "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", + "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": [], + "tests": [ + { + "name": "The plane flies forward above the ground", + "description": "Lets the plane fly for a few seconds: it moves forward and stays above the terrain.", + "type": "gameplay", + "source": [ + "await harness.goToScene('Game Scene');", + "// Faster in the software renderer used to run tests (it is slow with the trees, drawn in batches).", + "harness.setGameResolutionSize(160, 90);", + "harness.getCurrentRuntimeScene().getLayer('').setEffectBooleanParameter('3D Sun Light', 'isCastingShadow', false);", + "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 getPlane = () => harness.getObjects('PlayerAircraft')[0];", + "const start = getPlane();", + "harness.assert(start.z > terrain.getHeightAt(start.centerX, start.centerY) + 100, 'The plane starts in the air.');", + "let lowest = Infinity;", + "await harness.stepFrames(40, { ...DT, onFrame: () => { const plane = getPlane(); lowest = Math.min(lowest, plane.z - terrain.getHeightAt(plane.x, plane.y)); } });", + "const end = getPlane();", + "const distance = Math.hypot(end.centerX - start.centerX, end.centerY - start.centerY);", + "console.log('flew ' + distance.toFixed(0) + ', lowest above the ground: ' + lowest.toFixed(0));", + "harness.assert(distance > 200, 'The plane flew forward (' + distance.toFixed(0) + ').');", + "harness.assert(lowest >= 19, 'The plane stayed above the ground (' + lowest.toFixed(0) + ' at the lowest).');" + ] + }, + { + "name": "The plane stays inside the map", + "description": "Puts the plane at the border of the map flying outward: it stays inside.", + "type": "gameplay", + "source": [ + "await harness.goToScene('Game Scene');", + "// Faster in the software renderer used to run tests (it is slow with the trees, drawn in batches).", + "harness.setGameResolutionSize(160, 90);", + "harness.getCurrentRuntimeScene().getLayer('').setEffectBooleanParameter('3D Sun Light', 'isCastingShadow', false);", + "for (const object of harness.getCurrentRuntimeScene().getAdhocListOfAllInstances()) if (object.type === 'Terrain3D::ScatteredModels') object.hide(true);", + "harness.setTimeoutMs(60000);", + "const DT = { dtMs: 50 };", + "const plane = harness.getRuntimeObject(harness.getObjects('PlayerAircraft')[0].id);", + "plane.setPosition(7492, 0);", + "plane.setZ(3500);", + "plane.setAngle(0);", + "await harness.stepFrames(20, DT);", + "const end = harness.getObjects('PlayerAircraft')[0];", + "harness.assert(end.x <= 7592, 'The plane is stopped at the border (x=' + end.x.toFixed(0) + ').');" + ] + } + ] +} diff --git a/examples/starting-expanded-3d-flight-sim/theme-slots.json b/examples/starting-expanded-3d-flight-sim/theme-slots.json new file mode 100644 index 000000000..fc6e1e417 --- /dev/null +++ b/examples/starting-expanded-3d-flight-sim/theme-slots.json @@ -0,0 +1,38 @@ +{ + "version": 1, + "objects": { + "scene:Game Scene/PlayerAircraft": "vehicle.aircraft.player", + "scene:Game Scene/PineTrees": { + "Model": "nature.tree" + }, + "scene:Game Scene/Trees": { + "Model": "nature.tree" + }, + "scene:Game Scene/House": "building.tower", + "scene:Game Scene/Terrain": { + "Layer1Texture": { + "slot": "terrain.ground", + "normalMap": "Layer1NormalMap" + }, + "Layer2Texture": { + "slot": "terrain.dirt", + "normalMap": "Layer2NormalMap" + }, + "Layer3Texture": { + "slot": "terrain.rock", + "normalMap": "Layer3NormalMap" + } + } + }, + "effects": { + "effect:Game Scene//SkyBox": "sky.day" + }, + "ignoredObjects": [ + "scene:Game Scene/Silo", + "scene:Game Scene/Windsock", + "object:Water3D::Water3D/AreaPlaceholder", + "object:Terrain3D::Terrain3D/AreaPlaceholder", + "object:Terrain3D::TerrainGrass/AreaPlaceholder", + "object:Terrain3D::ScatteredModels/AreaPlaceholder" + ] +} diff --git a/examples/starting-expanded-3d-rts-unit-selection/README.md b/examples/starting-expanded-3d-rts-unit-selection/README.md new file mode 100644 index 000000000..8cac5e7c0 --- /dev/null +++ b/examples/starting-expanded-3d-rts-unit-selection/README.md @@ -0,0 +1,3 @@ +# Top-down RTS (unit selection) Expanded + +A 3D angled top-down RTS-style game in a valley made with a 3D terrain, with hills, a shallow stream, forests, farms and a town. The player can click to select individual units or click and drag to select multiple units, then give them a waypoint to move to: the units use navmesh pathfinding to walk around buildings, trees and steep hills, without bumping into each other. The map is bigger than the screen: the arrow keys (or WASD), or dragging with two fingers on touch screens, move the camera. Unit selection and waypoints work with either mouse or touch screen controls. diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/Barracks First Age Level 1.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/Barracks First Age Level 1.glb new file mode 100644 index 000000000..5f81afb9f Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/Barracks First Age Level 1.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/Farm First Age Level 1 Wheat.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/Farm First Age Level 1 Wheat.glb new file mode 100644 index 000000000..64e9351d2 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/Farm First Age Level 1 Wheat.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/Houses First Age 1 Level 1.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/Houses First Age 1 Level 1.glb new file mode 100644 index 000000000..2c7700678 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/Houses First Age 1 Level 1.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/Houses First Age 2 Level 1.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/Houses First Age 2 Level 1.glb new file mode 100644 index 000000000..ff499fc71 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/Houses First Age 2 Level 1.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/Houses First Age 3 Level 1.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/Houses First Age 3 Level 1.glb new file mode 100644 index 000000000..7b77528b1 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/Houses First Age 3 Level 1.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/Market First Age Level 1.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/Market First Age Level 1.glb new file mode 100644 index 000000000..0e8f2bf2e Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/Market First Age Level 1.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/Mine.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/Mine.glb new file mode 100644 index 000000000..645e280d4 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/Mine.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Gold 1.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Gold 1.glb new file mode 100644 index 000000000..305ce8a32 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Gold 1.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Gold 2.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Gold 2.glb new file mode 100644 index 000000000..8e68209ed Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Gold 2.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Pine Tree.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Pine Tree.glb new file mode 100644 index 000000000..5ccc08c35 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Pine Tree.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Rock 1.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Rock 1.glb new file mode 100644 index 000000000..741d15c89 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Rock 1.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Rock 2.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Rock 2.glb new file mode 100644 index 000000000..6c487a359 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Rock 2.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Tree 1.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Tree 1.glb new file mode 100644 index 000000000..b01c43e95 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Tree 1.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Tree 2.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Tree 2.glb new file mode 100644 index 000000000..51bbc435f Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Tree 2.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Tree Group.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Tree Group.glb new file mode 100644 index 000000000..1c84f9eb9 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/Resource Tree Group.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/StartingCameraBody.png b/examples/starting-expanded-3d-rts-unit-selection/assets/StartingCameraBody.png new file mode 100644 index 000000000..ca3bca425 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/StartingCameraBody.png differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/StartingCameraEye.png b/examples/starting-expanded-3d-rts-unit-selection/assets/StartingCameraEye.png new file mode 100644 index 000000000..e4e6e9c56 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/StartingCameraEye.png differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/StartingCameraTop.png b/examples/starting-expanded-3d-rts-unit-selection/assets/StartingCameraTop.png new file mode 100644 index 000000000..e90865197 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/StartingCameraTop.png differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/StartingSelectionBox.png b/examples/starting-expanded-3d-rts-unit-selection/assets/StartingSelectionBox.png new file mode 100644 index 000000000..cd06ee402 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/StartingSelectionBox.png differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/Storage First Age Level 1.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/Storage First Age Level 1.glb new file mode 100644 index 000000000..dc80c8fbe Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/Storage First Age Level 1.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainDirt.jpg b/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainDirt.jpg new file mode 100644 index 000000000..d44490927 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainDirt.jpg differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainDirtNormal.jpg b/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainDirtNormal.jpg new file mode 100644 index 000000000..92c153006 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainDirtNormal.jpg differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainGrass.jpg b/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainGrass.jpg new file mode 100644 index 000000000..220cddc0d Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainGrass.jpg differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainGrassNormal.jpg b/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainGrassNormal.jpg new file mode 100644 index 000000000..cd5053235 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainGrassNormal.jpg differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainRock.jpg b/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainRock.jpg new file mode 100644 index 000000000..d838e88fb Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainRock.jpg differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainRockNormal.jpg b/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainRockNormal.jpg new file mode 100644 index 000000000..7c8288e66 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainRockNormal.jpg differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainStreamBed.jpg b/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainStreamBed.jpg new file mode 100644 index 000000000..9ac2bd8f2 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainStreamBed.jpg differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainStreamBedNormal.jpg b/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainStreamBedNormal.jpg new file mode 100644 index 000000000..f0be877c9 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/TerrainStreamBedNormal.jpg differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/Town Center First Age Level 2.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/Town Center First Age Level 2.glb new file mode 100644 index 000000000..b1c97b1eb Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/Town Center First Age Level 2.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/Watch Tower First Age Level 1.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/Watch Tower First Age Level 1.glb new file mode 100644 index 000000000..b7a1b10a1 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/Watch Tower First Age Level 1.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/WaterNormalMap.jpg b/examples/starting-expanded-3d-rts-unit-selection/assets/WaterNormalMap.jpg new file mode 100644 index 000000000..558426e0e Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/WaterNormalMap.jpg differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/Windmill First Age.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/Windmill First Age.glb new file mode 100644 index 000000000..348ec2f6a Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/Windmill First Age.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/grass_multiple_blades.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/grass_multiple_blades.glb new file mode 100644 index 000000000..078a86ba4 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/grass_multiple_blades.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/assets/starting_unit_orange.glb b/examples/starting-expanded-3d-rts-unit-selection/assets/starting_unit_orange.glb new file mode 100644 index 000000000..b13cf08d4 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/assets/starting_unit_orange.glb differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/preview.png b/examples/starting-expanded-3d-rts-unit-selection/preview.png new file mode 100644 index 000000000..5e3f31b06 Binary files /dev/null and b/examples/starting-expanded-3d-rts-unit-selection/preview.png differ diff --git a/examples/starting-expanded-3d-rts-unit-selection/starting-expanded-3d-rts-unit-selection.json b/examples/starting-expanded-3d-rts-unit-selection/starting-expanded-3d-rts-unit-selection.json new file mode 100644 index 000000000..61e7454c1 --- /dev/null +++ b/examples/starting-expanded-3d-rts-unit-selection/starting-expanded-3d-rts-unit-selection.json @@ -0,0 +1,26469 @@ +{ + "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": "5a24131a-6e26-5c9d-ae73-5d533929c20e", + "scaleMode": "linear", + "sizeOnStartupMode": "", + "templateSlug": "starting-top-down", + "version": "1.0.0", + "name": "Top-down RTS (unit selection) Expanded", + "description": "A 3D RTS-style game where units are selected and sent around hills and across a stream, in a valley made with a 3D terrain.", + "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/StartingCameraTop.png", + "kind": "image", + "metadata": "", + "name": "assets\\StartingCameraTop.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/StartingCameraBody.png", + "kind": "image", + "metadata": "", + "name": "assets\\StartingCameraBody.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/StartingCameraEye.png", + "kind": "image", + "metadata": "", + "name": "assets\\StartingCameraEye.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/starting_unit_orange.glb", + "kind": "model3D", + "metadata": "", + "name": "assets\\starting_unit_orange.glb", + "userAdded": false + }, + { + "file": "assets/grass_multiple_blades.glb", + "kind": "model3D", + "metadata": "", + "name": "assets\\grass_multiple_blades.glb", + "userAdded": false + }, + { + "file": "assets/StartingSelectionBox.png", + "kind": "image", + "metadata": "", + "name": "assets\\StartingSelectionBox.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/TerrainStreamBed.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainStreamBed.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainStreamBedNormal.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainStreamBedNormal.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/WaterNormalMap.jpg", + "kind": "image", + "metadata": "", + "name": "assets/WaterNormalMap.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/Town Center First Age Level 2.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Town Center First Age Level 2.glb", + "userAdded": true + }, + { + "file": "assets/Houses First Age 1 Level 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Houses First Age 1 Level 1.glb", + "userAdded": true + }, + { + "file": "assets/Houses First Age 2 Level 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Houses First Age 2 Level 1.glb", + "userAdded": true + }, + { + "file": "assets/Houses First Age 3 Level 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Houses First Age 3 Level 1.glb", + "userAdded": true + }, + { + "file": "assets/Watch Tower First Age Level 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Watch Tower First Age Level 1.glb", + "userAdded": true + }, + { + "file": "assets/Barracks First Age Level 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Barracks First Age Level 1.glb", + "userAdded": true + }, + { + "file": "assets/Market First Age Level 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Market First Age Level 1.glb", + "userAdded": true + }, + { + "file": "assets/Storage First Age Level 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Storage First Age Level 1.glb", + "userAdded": true + }, + { + "file": "assets/Windmill First Age.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Windmill First Age.glb", + "userAdded": true + }, + { + "file": "assets/Mine.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Mine.glb", + "userAdded": true + }, + { + "file": "assets/Farm First Age Level 1 Wheat.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Farm First Age Level 1 Wheat.glb", + "userAdded": true + }, + { + "file": "assets/Resource Gold 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Resource Gold 1.glb", + "userAdded": true + }, + { + "file": "assets/Resource Gold 2.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Resource Gold 2.glb", + "userAdded": true + }, + { + "file": "assets/Resource Tree 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Resource Tree 1.glb", + "userAdded": true + }, + { + "file": "assets/Resource Tree 2.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Resource Tree 2.glb", + "userAdded": true + }, + { + "file": "assets/Resource Pine Tree.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Resource Pine Tree.glb", + "userAdded": true + }, + { + "file": "assets/Resource Tree Group.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Resource Tree Group.glb", + "userAdded": true + }, + { + "file": "assets/Resource Rock 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Resource Rock 1.glb", + "userAdded": true + }, + { + "file": "assets/Resource Rock 2.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Resource Rock 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.40575189059439154, + "windowMask": false, + "selectedLayer": "", + "gameEditorMode": "embedded-game" + }, + "objectsGroups": [], + "variables": [ + { + "name": "ControlType", + "persistentUuid": "72236269-d4a0-4899-a5f6-491837afd6ae", + "type": "string", + "value": "Keyboard" + }, + { + "name": "Panning", + "type": "boolean", + "value": false + }, + { + "name": "PanAnchorX", + "type": "number", + "value": 0 + }, + { + "name": "PanAnchorY", + "type": "number", + "value": 0 + } + ], + "instances": [ + { + "angle": -90, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "", + "locked": true, + "name": "Camera", + "persistentUuid": "22d29c4f-1f9b-4632-9534-8dc7e58b948b", + "rotationY": 60, + "width": 0, + "x": 576, + "y": 704, + "z": 800, + "zOrder": 9, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "", + "name": "RTSUnit", + "persistentUuid": "ae06f524-2ae7-46d5-bb05-05af9db90d72", + "width": 0, + "x": 244, + "y": 268, + "zOrder": 14, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "", + "name": "RTSUnit", + "persistentUuid": "3b79fe2d-0f0a-4b3e-9a03-05158488263f", + "width": 0, + "x": 896, + "y": 128, + "zOrder": 14, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "", + "name": "RTSUnit", + "persistentUuid": "bb001ebb-0561-49e2-917a-799f3dc81f2d", + "width": 0, + "x": 948, + "y": 268, + "zOrder": 14, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "", + "name": "RTSUnit", + "persistentUuid": "fc7fc2f1-6ddd-4c56-b65c-218e477c97c2", + "width": 0, + "x": 372, + "y": 460, + "zOrder": 14, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "", + "name": "RTSUnit", + "persistentUuid": "8367c89c-3b06-40d4-b71b-1ef34a2b7f02", + "width": 0, + "x": 460, + "y": 180, + "zOrder": 14, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "", + "name": "RTSUnit", + "persistentUuid": "9bbd7fb2-96cb-448a-885c-aea2fd5b020f", + "width": 0, + "x": 844, + "y": 460, + "zOrder": 14, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 36, + "height": 59, + "keepRatio": true, + "layer": "", + "name": "Grass", + "persistentUuid": "9b099b54-85b1-46f8-9dc3-c250514eb615", + "width": 52, + "x": 755, + "y": -217, + "zOrder": 17, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 39, + "height": 63, + "keepRatio": true, + "layer": "", + "name": "Grass", + "persistentUuid": "df2c1142-fb5f-4451-93ad-cdf657ac4d55", + "width": 55, + "x": 528, + "y": 643, + "zOrder": 17, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 24, + "height": 39, + "keepRatio": true, + "layer": "", + "name": "Grass", + "persistentUuid": "d883a502-84e7-4f81-bb42-998a5a15b418", + "width": 34, + "x": 768, + "y": -46, + "zOrder": 17, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 24, + "height": 39, + "keepRatio": true, + "layer": "", + "name": "Grass", + "persistentUuid": "7e57e70a-84d7-491c-a78c-6a52dc86be70", + "width": 34, + "x": 523, + "y": 175, + "zOrder": 17, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 24, + "height": 39, + "keepRatio": true, + "layer": "", + "name": "Grass", + "persistentUuid": "f0ec2251-4fdf-4a02-8f74-beadc754b9cc", + "width": 34, + "x": 766, + "y": 523, + "zOrder": 17, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 4096, + "width": 4096, + "layer": "", + "locked": false, + "name": "Terrain", + "persistentUuid": "8772788e-f7bd-50b3-b489-d64824f447fd", + "x": -2048, + "y": -2048, + "z": -60, + "zOrder": 0, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 800 + }, + { + "angle": 0, + "customSize": true, + "height": 3300, + "width": 500, + "layer": "", + "locked": false, + "name": "Water", + "persistentUuid": "cb86a755-a3a7-5514-a0f7-e26894584507", + "x": 390, + "y": -1650, + "z": -6, + "zOrder": 2, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 1 + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "TownCenter", + "persistentUuid": "8d4e0596-8a44-5cff-9798-cc298eb1e212", + "x": 128, + "y": -96, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "House", + "persistentUuid": "3cd75692-5a28-57da-94d4-1c3c006710fa", + "x": 1152, + "y": 576, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "HouseTall", + "persistentUuid": "ccd461cc-d6bc-5c8e-9824-8e36efc61f92", + "x": 128, + "y": 576, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "WatchTower", + "persistentUuid": "417eea1d-d959-5e87-b9db-9ea37ca4c7a7", + "x": 32, + "y": 256, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Barracks", + "persistentUuid": "116ce5c7-f7d9-57b2-af8f-2d8d4d82da3b", + "x": 896, + "y": -192, + "z": -2.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "HouseSmall", + "persistentUuid": "eb20493a-07a4-5085-b468-ceb266a31954", + "x": 416, + "y": -192, + "z": -3.7, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Market", + "persistentUuid": "4b293a87-ae6b-5361-bd30-ba792a7bd499", + "x": 1184, + "y": -160, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Storage", + "persistentUuid": "3bf1fd72-2b2f-59af-b2f2-a0fa77f3c419", + "x": 1248, + "y": 192, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Farm", + "persistentUuid": "21f53664-797b-57f5-aecf-bfd003c28031", + "x": 280, + "y": 900, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Farm", + "persistentUuid": "19b907a9-3db4-5e1e-9009-7d8793ac3f24", + "x": -80, + "y": 620, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Windmill", + "persistentUuid": "d2181fce-6955-5c31-a596-7076ae6f07c9", + "x": -100, + "y": 980, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Mine", + "persistentUuid": "73cce596-9f49-542c-9379-c631bd540faa", + "x": 1300, + "y": -520, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "WatchTower", + "persistentUuid": "98212805-345d-5d47-8e6e-bbcf9cfdaeab", + "x": -1190, + "y": 390, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "House", + "persistentUuid": "2155de03-bc2b-5aec-b109-cbe3a1a18159", + "x": -1300, + "y": 160, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "HouseSmall", + "persistentUuid": "84a9439f-c1cf-5c93-89dd-6f43cdde1f75", + "x": -1060, + "y": 200, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 38, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "GoldLarge", + "persistentUuid": "e256c5ea-ff79-51c7-a1ce-a7f48425421f", + "x": 1367.7, + "y": -777, + "z": -3.9, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 42, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "GoldLarge", + "persistentUuid": "bc333748-a21d-52d7-ad80-ec4a423c0143", + "x": 1072.4, + "y": -656.4, + "z": -4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 168, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "GoldLarge", + "persistentUuid": "98c5dc5a-2d77-572a-b41a-a11f8db1b960", + "x": 1449.6, + "y": -264.3, + "z": -4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 4096, + "width": 4096, + "layer": "", + "locked": true, + "name": "Trees", + "persistentUuid": "d898e18b-0810-56d5-95de-30a215d27b24", + "x": -2048, + "y": -2048, + "z": -60, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 4096, + "width": 4096, + "layer": "", + "locked": true, + "name": "RoundTrees", + "persistentUuid": "0e4585dd-e048-551e-a85f-da23ebc02343", + "x": -2048, + "y": -2048, + "z": -60, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 4096, + "width": 4096, + "layer": "", + "locked": true, + "name": "PineTrees", + "persistentUuid": "f00a0175-2f76-5d77-843e-0dda62527ce1", + "x": -2048, + "y": -2048, + "z": -60, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 4096, + "width": 4096, + "layer": "", + "locked": true, + "name": "TreeGroups", + "persistentUuid": "03fe085c-17a3-5c5d-8dc0-f0e0a3b51d8c", + "x": -2048, + "y": -2048, + "z": -60, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 4096, + "width": 4096, + "layer": "", + "locked": true, + "name": "Rocks", + "persistentUuid": "021f70a4-33c7-5c02-a6a1-37586a44130a", + "x": -2048, + "y": -2048, + "z": -60, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 4096, + "width": 4096, + "layer": "", + "locked": true, + "name": "LargeRocks", + "persistentUuid": "de836e5f-3fbc-53d8-be18-70853b22473c", + "x": -2048, + "y": -2048, + "z": -60, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 4096, + "width": 4096, + "layer": "", + "locked": true, + "name": "GrassTufts", + "persistentUuid": "96b225e1-fd40-55a2-952c-1d09d15f3713", + "x": -2048, + "y": -2048, + "z": -60, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + } + ], + "objects": [ + { + "assetStoreId": "", + "name": "SelectionTool_Hidden", + "persistentUuid": "61917cc0-5a59-473c-92b9-97c220af68b4", + "type": "PrimitiveDrawing::Drawer", + "variables": [], + "effects": [], + "behaviors": [], + "fillOpacity": 50, + "outlineSize": 10, + "outlineOpacity": 255, + "absoluteCoordinates": false, + "clearBetweenFrames": true, + "antialiasing": "low", + "fillColor": { + "r": 103, + "g": 92, + "b": 223 + }, + "outlineColor": { + "r": 103, + "g": 92, + "b": 223 + } + }, + { + "assetStoreId": "", + "name": "Camera", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 128, + "height": 128, + "depth": 128, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "frontFaceResourceName": "assets\\StartingCameraTop.png", + "backFaceResourceName": "assets\\StartingCameraBody.png", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "assets\\StartingCameraBody.png", + "rightFaceResourceName": "assets\\StartingCameraEye.png", + "topFaceResourceName": "assets\\StartingCameraBody.png", + "bottomFaceResourceName": "assets\\StartingCameraBody.png", + "frontFaceVisible": true, + "backFaceVisible": true, + "leftFaceVisible": true, + "rightFaceVisible": true, + "topFaceVisible": true, + "bottomFaceVisible": true, + "frontFaceResourceRepeat": false, + "backFaceResourceRepeat": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceRepeat": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceRepeat": false, + "materialType": "Basic", + "tint": "255;255;255", + "isCastingShadow": false, + "isReceivingShadow": false + } + }, + { + "assetStoreId": "", + "name": "RTSUnit", + "persistentUuid": "4abd7691-e4a8-437c-98d7-1b5cb761ce96", + "type": "Scene3D::Model3DObject", + "variables": [ + { + "name": "DestinationX", + "type": "number", + "value": 0 + }, + { + "name": "DestinationY", + "type": "number", + "value": 0 + } + ], + "effects": [], + "behaviors": [ + { + "name": "NavMeshCharacter", + "type": "NavMeshPathfinding::NavMeshCharacterBehavior", + "acceleration": 2000, + "maxSpeed": 250, + "angularMaxSpeed": 180, + "rotateObject": false, + "angleOffset": 0, + "radius": 0, + "avoidanceSightRange": 120 + } + ], + "content": { + "centerLocation": "ModelOrigin", + "crossfadeDuration": 0.10000000149011612, + "depth": 50, + "height": 23.08, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets\\starting_unit_orange.glb", + "originLocation": "ModelOrigin", + "rotationX": 0, + "rotationY": 0, + "rotationZ": 0, + "width": 23.08, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "Grass", + "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\\grass_multiple_blades.glb", + "originLocation": "ModelOrigin", + "rotationX": 0, + "rotationY": 0, + "rotationZ": 0, + "width": 100, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "SelectionTool_3DSprite", + "persistentUuid": "19d3d02c-8ec2-457a-96f6-f57be8b25bb3", + "type": "Sprite3D::Sprite3D", + "variant": "", + "variables": [ + { + "name": "SelectionStartX", + "persistentUuid": "1a3d3f47-dd1b-4b11-a3ca-a65a6393730b", + "type": "number", + "value": 0 + }, + { + "name": "SelectionStartY", + "persistentUuid": "826ac7b6-2c18-4657-8256-fee2e836763e", + "type": "number", + "value": 0 + } + ], + "effects": [], + "behaviors": [], + "content": {}, + "animatable": { + "adaptCollisionMaskAutomatically": true, + "animations": [ + { + "name": "", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": true, + "image": "assets\\StartingSelectionBox.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 0, + "y": 0 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [ + [ + { + "x": 0, + "y": 0 + }, + { + "x": 16, + "y": 0 + }, + { + "x": 16, + "y": 16 + }, + { + "x": 0, + "y": 16 + } + ] + ] + } + ] + } + ] + } + ] + }, + "childrenContent": {} + }, + { + "assetStoreId": "", + "name": "Terrain", + "type": "Terrain3D::Terrain3D", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": false + } + ], + "content": { + "Relief": "Flat", + "Seed": 1, + "Resolution": "128", + "TextureSize": 300, + "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.075},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.075},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.075},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.075},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.075},{\"tool\":\"raise\",\"x\":0,\"y\":0,\"toX\":1,\"toY\":0,\"radius\":0.1587,\"height\":0.475},{\"tool\":\"raise\",\"x\":0,\"y\":1,\"toX\":1,\"toY\":1,\"radius\":0.1587,\"height\":0.475},{\"tool\":\"raise\",\"x\":0,\"y\":0,\"toX\":0,\"toY\":1,\"radius\":0.1587,\"height\":0.475},{\"tool\":\"raise\",\"x\":1,\"y\":0,\"toX\":1,\"toY\":1,\"radius\":0.1587,\"height\":0.475},{\"tool\":\"raise\",\"x\":0.2925,\"y\":0.2925,\"radius\":0.1025,\"height\":0.2125},{\"tool\":\"raise\",\"x\":0.2681,\"y\":0.6831,\"radius\":0.0928,\"height\":0.1875},{\"tool\":\"raise\",\"x\":0.5366,\"y\":0.2192,\"radius\":0.0781,\"height\":0.1625},{\"tool\":\"raise\",\"x\":0.3779,\"y\":0.5122,\"radius\":0.0684,\"height\":0.15},{\"tool\":\"raise\",\"x\":0.7808,\"y\":0.2681,\"radius\":0.0732,\"height\":0.1625},{\"tool\":\"raise\",\"x\":0.4268,\"y\":0.8174,\"radius\":0.0635,\"height\":0.1375},{\"tool\":\"smooth\",\"x\":0.5,\"y\":0.5,\"radius\":0.4639,\"strength\":0.25},{\"tool\":\"flatten\",\"x\":0.6343,\"y\":0.5488,\"radius\":0.2002,\"z\":0.075},{\"tool\":\"flatten\",\"x\":0.6343,\"y\":0.5488,\"radius\":0.2002,\"z\":0.075},{\"tool\":\"flatten\",\"x\":0.6343,\"y\":0.5488,\"radius\":0.2002,\"z\":0.075},{\"tool\":\"flatten\",\"x\":0.6343,\"y\":0.5488,\"radius\":0.2002,\"z\":0.075},{\"tool\":\"flatten\",\"x\":0.6318,\"y\":0.1338,\"toX\":0.6563,\"toY\":0.3779,\"radius\":0.0366,\"z\":0.0575},{\"tool\":\"flatten\",\"x\":0.6318,\"y\":0.1338,\"toX\":0.6563,\"toY\":0.3779,\"radius\":0.0366,\"z\":0.0575},{\"tool\":\"paint\",\"x\":0.6318,\"y\":0.1338,\"toX\":0.6563,\"toY\":0.3779,\"radius\":0.0427,\"layer\":4,\"strength\":0.9},{\"tool\":\"flatten\",\"x\":0.6563,\"y\":0.3779,\"toX\":0.6563,\"toY\":0.6953,\"radius\":0.0366,\"z\":0.0575},{\"tool\":\"flatten\",\"x\":0.6563,\"y\":0.3779,\"toX\":0.6563,\"toY\":0.6953,\"radius\":0.0366,\"z\":0.0575},{\"tool\":\"paint\",\"x\":0.6563,\"y\":0.3779,\"toX\":0.6563,\"toY\":0.6953,\"radius\":0.0427,\"layer\":4,\"strength\":0.9},{\"tool\":\"flatten\",\"x\":0.6563,\"y\":0.6953,\"toX\":0.6807,\"toY\":0.8662,\"radius\":0.0366,\"z\":0.0575},{\"tool\":\"flatten\",\"x\":0.6563,\"y\":0.6953,\"toX\":0.6807,\"toY\":0.8662,\"radius\":0.0366,\"z\":0.0575},{\"tool\":\"paint\",\"x\":0.6563,\"y\":0.6953,\"toX\":0.6807,\"toY\":0.8662,\"radius\":0.0427,\"layer\":4,\"strength\":0.9},{\"tool\":\"flatten\",\"x\":0.2119,\"y\":0.5684,\"radius\":0.0977,\"z\":0.075},{\"tool\":\"flatten\",\"x\":0.2119,\"y\":0.5684,\"radius\":0.0977,\"z\":0.075},{\"tool\":\"flatten\",\"x\":0.2119,\"y\":0.5684,\"radius\":0.0977,\"z\":0.075},{\"tool\":\"flatten\",\"x\":0.2119,\"y\":0.5684,\"radius\":0.0977,\"z\":0.075},{\"tool\":\"flatten\",\"x\":0.2119,\"y\":0.5684,\"radius\":0.0977,\"z\":0.075},{\"tool\":\"flatten\",\"x\":0.8174,\"y\":0.373,\"radius\":0.0537,\"z\":0.075},{\"tool\":\"flatten\",\"x\":0.8174,\"y\":0.373,\"radius\":0.0537,\"z\":0.075},{\"tool\":\"flatten\",\"x\":0.8174,\"y\":0.373,\"radius\":0.0537,\"z\":0.075},{\"tool\":\"flatten\",\"x\":0.8174,\"y\":0.373,\"radius\":0.0537,\"z\":0.075},{\"tool\":\"flatten\",\"x\":0.6147,\"y\":0.5703,\"toX\":0.6978,\"toY\":0.5703,\"radius\":0.022,\"z\":0.075},{\"tool\":\"flatten\",\"x\":0.6147,\"y\":0.5703,\"toX\":0.6978,\"toY\":0.5703,\"radius\":0.022,\"z\":0.075},{\"tool\":\"flatten\",\"x\":0.6147,\"y\":0.5703,\"toX\":0.6978,\"toY\":0.5703,\"radius\":0.022,\"z\":0.075},{\"tool\":\"paint\",\"x\":0.4756,\"y\":0.4634,\"toX\":0.3845,\"toY\":0.428,\"radius\":0.0176,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3845,\"y\":0.428,\"toX\":0.2888,\"toY\":0.4124,\"radius\":0.0176,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.2888,\"y\":0.4124,\"toX\":0.1968,\"toY\":0.4365,\"radius\":0.0176,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1968,\"y\":0.4365,\"toX\":0.1804,\"toY\":0.531,\"radius\":0.0176,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.1804,\"y\":0.531,\"toX\":0.1826,\"toY\":0.5977,\"radius\":0.0176,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.7197,\"y\":0.4023,\"toX\":0.7007,\"toY\":0.3066,\"radius\":0.0161,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.7007,\"y\":0.3066,\"toX\":0.6802,\"toY\":0.2112,\"radius\":0.0161,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.6802,\"y\":0.2112,\"toX\":0.6709,\"toY\":0.1704,\"radius\":0.0161,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.7808,\"y\":0.6587,\"toX\":0.8069,\"toY\":0.7429,\"radius\":0.0161,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8069,\"y\":0.7429,\"toX\":0.7119,\"toY\":0.7634,\"radius\":0.0161,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.7119,\"y\":0.7634,\"toX\":0.6145,\"toY\":0.7693,\"radius\":0.0161,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.6145,\"y\":0.7693,\"toX\":0.5188,\"toY\":0.7546,\"radius\":0.0161,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5188,\"y\":0.7546,\"toX\":0.4285,\"toY\":0.7197,\"radius\":0.0161,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.4285,\"y\":0.7197,\"toX\":0.3481,\"toY\":0.7712,\"radius\":0.0161,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3481,\"y\":0.7712,\"toX\":0.2544,\"toY\":0.7961,\"radius\":0.0161,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.2544,\"y\":0.7961,\"toX\":0.1826,\"toY\":0.8052,\"radius\":0.0161,\"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/TerrainStreamBed.jpg", + "Layer4NormalMap": "assets/TerrainStreamBedNormal.jpg" + } + }, + { + "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": "40;110;140" + } + }, + { + "assetStoreId": "4d23e44b0c659530a8fb0bb4c3f81b85b795fa4ee98e00ecbd6627b80f2c1ad8", + "name": "TownCenter", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 370.1, + "height": 422.1, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Town Center First Age Level 2.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 430, + "animations": [] + } + }, + { + "assetStoreId": "381108dda950be19b61c2225373afa7ced691f2395e24984a07d6420d24a41d5", + "name": "House", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 193.5, + "height": 192.7, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Houses First Age 1 Level 1.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 220, + "animations": [] + } + }, + { + "assetStoreId": "4e7edd3216e745d67da26c232d25897d6139a7996410741cdf25d6a0e69a2480", + "name": "HouseTall", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 150.5, + "height": 230, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Houses First Age 2 Level 1.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 178.3, + "animations": [] + } + }, + { + "assetStoreId": "ee0a6887761ecff9952bcc7a1c1244d537c521bda8b90516bf02026771c75b2d", + "name": "HouseSmall", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 178.2, + "height": 202.2, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Houses First Age 3 Level 1.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 210, + "animations": [] + } + }, + { + "assetStoreId": "2ab51c27abe8965cd83d8a28b3e4cd8559b6bb09045100aff56f3d365b7d3832", + "name": "WatchTower", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 280, + "height": 173.7, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Watch Tower First Age Level 1.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 158.2, + "animations": [] + } + }, + { + "assetStoreId": "68538e55a77730a581d4835ed9890e31608f05aa558a626d551bdf0ceb1c38b5", + "name": "Barracks", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 186.8, + "height": 280, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Barracks First Age Level 1.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 262.2, + "animations": [] + } + }, + { + "assetStoreId": "539f7ed0a1123a36a1afb2b71f58ee93f6dff565aa1c7a3b3bfb5cad27ed55a6", + "name": "Market", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 102.1, + "height": 225.4, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Market First Age Level 1.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 300, + "animations": [] + } + }, + { + "assetStoreId": "64344a6061c245bb4489f1dc34bb02d0213b7739a4647e2ea9187cf58c91e18b", + "name": "Storage", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 145.4, + "height": 280, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Storage First Age Level 1.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 269.4, + "animations": [] + } + }, + { + "assetStoreId": "b52e5535d208b4e9930e28d2ada8bf64daff379a13c9d0bf797e475129ea8caf", + "name": "Windmill", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 300, + "height": 103.3, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Windmill First Age.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 211.2, + "animations": [] + } + }, + { + "assetStoreId": "1177b7d18c052fce2ca48f72a08e481c9643fe7d91db2c6aa2c4f6035fed98dd", + "name": "Mine", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 240.5, + "height": 280, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Mine.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 264.2, + "animations": [] + } + }, + { + "assetStoreId": "1b03f597ade25021b7c1b96eaef4bbee6cf6d28ff7c139171a6033087b57c7df", + "name": "Farm", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 37.1, + "height": 83.3, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Farm First Age Level 1 Wheat.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 320, + "animations": [] + } + }, + { + "assetStoreId": "c147f0acf334f0241581105ff132938e96c64a197784eb150f03115fad35cabd", + "name": "Gold", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 83.1, + "height": 150, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Resource Gold 1.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 145.9, + "animations": [] + } + }, + { + "assetStoreId": "14ead40082edd2c94dbcbab58be07fe8808e6aa286a5cb91c90e972198373fb6", + "name": "GoldLarge", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 71.5, + "height": 170, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Resource Gold 2.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 166, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "Trees", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "Model": "assets/Resource Tree 1.glb", + "Size": 230, + "Density": 0.05, + "Seed": 1, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.6343,\"y\":0.5488,\"radius\":0.2197},{\"tool\":\"erase\",\"x\":0.6318,\"y\":0.1338,\"toX\":0.6563,\"toY\":0.3779,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.6563,\"y\":0.3779,\"toX\":0.6563,\"toY\":0.6953,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.6563,\"y\":0.6953,\"toX\":0.6807,\"toY\":0.8662,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.5313,\"y\":0.4766,\"radius\":0.0623},{\"tool\":\"erase\",\"x\":0.7813,\"y\":0.6406,\"radius\":0.0366},{\"tool\":\"erase\",\"x\":0.5313,\"y\":0.6406,\"radius\":0.0378},{\"tool\":\"erase\",\"x\":0.5078,\"y\":0.5625,\"radius\":0.031},{\"tool\":\"erase\",\"x\":0.7188,\"y\":0.4531,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.6016,\"y\":0.4531,\"radius\":0.0354},{\"tool\":\"erase\",\"x\":0.7891,\"y\":0.4609,\"radius\":0.0464},{\"tool\":\"erase\",\"x\":0.8047,\"y\":0.5469,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.5684,\"y\":0.7197,\"radius\":0.0488},{\"tool\":\"erase\",\"x\":0.4805,\"y\":0.6514,\"radius\":0.0488},{\"tool\":\"erase\",\"x\":0.4756,\"y\":0.7393,\"radius\":0.0355},{\"tool\":\"erase\",\"x\":0.8174,\"y\":0.373,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.2095,\"y\":0.5952,\"radius\":0.031},{\"tool\":\"erase\",\"x\":0.1826,\"y\":0.5391,\"radius\":0.0366},{\"tool\":\"erase\",\"x\":0.2412,\"y\":0.5488,\"radius\":0.0354},{\"tool\":\"erase\",\"x\":0.8339,\"y\":0.3103,\"radius\":0.0305},{\"tool\":\"erase\",\"x\":0.7618,\"y\":0.3398,\"radius\":0.0305},{\"tool\":\"erase\",\"x\":0.8539,\"y\":0.4355,\"radius\":0.0305}]", + "MaxSlope": 30, + "TrunkCollisions": true, + "TrunkDiameter": 40 + } + }, + { + "assetStoreId": "", + "name": "RoundTrees", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "Model": "assets/Resource Tree 2.glb", + "Size": 230, + "Density": 0.04, + "Seed": 2, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.6343,\"y\":0.5488,\"radius\":0.2197},{\"tool\":\"erase\",\"x\":0.6318,\"y\":0.1338,\"toX\":0.6563,\"toY\":0.3779,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.6563,\"y\":0.3779,\"toX\":0.6563,\"toY\":0.6953,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.6563,\"y\":0.6953,\"toX\":0.6807,\"toY\":0.8662,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.5313,\"y\":0.4766,\"radius\":0.0623},{\"tool\":\"erase\",\"x\":0.7813,\"y\":0.6406,\"radius\":0.0366},{\"tool\":\"erase\",\"x\":0.5313,\"y\":0.6406,\"radius\":0.0378},{\"tool\":\"erase\",\"x\":0.5078,\"y\":0.5625,\"radius\":0.031},{\"tool\":\"erase\",\"x\":0.7188,\"y\":0.4531,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.6016,\"y\":0.4531,\"radius\":0.0354},{\"tool\":\"erase\",\"x\":0.7891,\"y\":0.4609,\"radius\":0.0464},{\"tool\":\"erase\",\"x\":0.8047,\"y\":0.5469,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.5684,\"y\":0.7197,\"radius\":0.0488},{\"tool\":\"erase\",\"x\":0.4805,\"y\":0.6514,\"radius\":0.0488},{\"tool\":\"erase\",\"x\":0.4756,\"y\":0.7393,\"radius\":0.0355},{\"tool\":\"erase\",\"x\":0.8174,\"y\":0.373,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.2095,\"y\":0.5952,\"radius\":0.031},{\"tool\":\"erase\",\"x\":0.1826,\"y\":0.5391,\"radius\":0.0366},{\"tool\":\"erase\",\"x\":0.2412,\"y\":0.5488,\"radius\":0.0354},{\"tool\":\"erase\",\"x\":0.8339,\"y\":0.3103,\"radius\":0.0305},{\"tool\":\"erase\",\"x\":0.7618,\"y\":0.3398,\"radius\":0.0305},{\"tool\":\"erase\",\"x\":0.8539,\"y\":0.4355,\"radius\":0.0305}]", + "MaxSlope": 30, + "TrunkCollisions": true, + "TrunkDiameter": 40 + } + }, + { + "assetStoreId": "", + "name": "PineTrees", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "Model": "assets/Resource Pine Tree.glb", + "Size": 260, + "Density": 0.05, + "Seed": 3, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.6343,\"y\":0.5488,\"radius\":0.2197},{\"tool\":\"erase\",\"x\":0.6318,\"y\":0.1338,\"toX\":0.6563,\"toY\":0.3779,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.6563,\"y\":0.3779,\"toX\":0.6563,\"toY\":0.6953,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.6563,\"y\":0.6953,\"toX\":0.6807,\"toY\":0.8662,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.5313,\"y\":0.4766,\"radius\":0.0623},{\"tool\":\"erase\",\"x\":0.7813,\"y\":0.6406,\"radius\":0.0366},{\"tool\":\"erase\",\"x\":0.5313,\"y\":0.6406,\"radius\":0.0378},{\"tool\":\"erase\",\"x\":0.5078,\"y\":0.5625,\"radius\":0.031},{\"tool\":\"erase\",\"x\":0.7188,\"y\":0.4531,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.6016,\"y\":0.4531,\"radius\":0.0354},{\"tool\":\"erase\",\"x\":0.7891,\"y\":0.4609,\"radius\":0.0464},{\"tool\":\"erase\",\"x\":0.8047,\"y\":0.5469,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.5684,\"y\":0.7197,\"radius\":0.0488},{\"tool\":\"erase\",\"x\":0.4805,\"y\":0.6514,\"radius\":0.0488},{\"tool\":\"erase\",\"x\":0.4756,\"y\":0.7393,\"radius\":0.0355},{\"tool\":\"erase\",\"x\":0.8174,\"y\":0.373,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.2095,\"y\":0.5952,\"radius\":0.031},{\"tool\":\"erase\",\"x\":0.1826,\"y\":0.5391,\"radius\":0.0366},{\"tool\":\"erase\",\"x\":0.2412,\"y\":0.5488,\"radius\":0.0354},{\"tool\":\"erase\",\"x\":0.8339,\"y\":0.3103,\"radius\":0.0305},{\"tool\":\"erase\",\"x\":0.7618,\"y\":0.3398,\"radius\":0.0305},{\"tool\":\"erase\",\"x\":0.8539,\"y\":0.4355,\"radius\":0.0305}]", + "MaxSlope": 30, + "TrunkCollisions": true, + "TrunkDiameter": 36 + } + }, + { + "assetStoreId": "", + "name": "TreeGroups", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "Model": "assets/Resource Tree Group.glb", + "Size": 160, + "Density": 0.012, + "Seed": 4, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.6343,\"y\":0.5488,\"radius\":0.2197},{\"tool\":\"erase\",\"x\":0.6318,\"y\":0.1338,\"toX\":0.6563,\"toY\":0.3779,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.6563,\"y\":0.3779,\"toX\":0.6563,\"toY\":0.6953,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.6563,\"y\":0.6953,\"toX\":0.6807,\"toY\":0.8662,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.5313,\"y\":0.4766,\"radius\":0.0623},{\"tool\":\"erase\",\"x\":0.7813,\"y\":0.6406,\"radius\":0.0366},{\"tool\":\"erase\",\"x\":0.5313,\"y\":0.6406,\"radius\":0.0378},{\"tool\":\"erase\",\"x\":0.5078,\"y\":0.5625,\"radius\":0.031},{\"tool\":\"erase\",\"x\":0.7188,\"y\":0.4531,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.6016,\"y\":0.4531,\"radius\":0.0354},{\"tool\":\"erase\",\"x\":0.7891,\"y\":0.4609,\"radius\":0.0464},{\"tool\":\"erase\",\"x\":0.8047,\"y\":0.5469,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.5684,\"y\":0.7197,\"radius\":0.0488},{\"tool\":\"erase\",\"x\":0.4805,\"y\":0.6514,\"radius\":0.0488},{\"tool\":\"erase\",\"x\":0.4756,\"y\":0.7393,\"radius\":0.0355},{\"tool\":\"erase\",\"x\":0.8174,\"y\":0.373,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.2095,\"y\":0.5952,\"radius\":0.031},{\"tool\":\"erase\",\"x\":0.1826,\"y\":0.5391,\"radius\":0.0366},{\"tool\":\"erase\",\"x\":0.2412,\"y\":0.5488,\"radius\":0.0354},{\"tool\":\"erase\",\"x\":0.8339,\"y\":0.3103,\"radius\":0.0305},{\"tool\":\"erase\",\"x\":0.7618,\"y\":0.3398,\"radius\":0.0305},{\"tool\":\"erase\",\"x\":0.8539,\"y\":0.4355,\"radius\":0.0305}]", + "MaxSlope": 30, + "TrunkCollisions": true, + "TrunkDiameter": 120 + } + }, + { + "assetStoreId": "", + "name": "Rocks", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "Model": "assets/Resource Rock 1.glb", + "Size": 122, + "Density": 0.012, + "Seed": 5, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.6343,\"y\":0.5488,\"radius\":0.2197},{\"tool\":\"erase\",\"x\":0.6318,\"y\":0.1338,\"toX\":0.6563,\"toY\":0.3779,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.6563,\"y\":0.3779,\"toX\":0.6563,\"toY\":0.6953,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.6563,\"y\":0.6953,\"toX\":0.6807,\"toY\":0.8662,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.5313,\"y\":0.4766,\"radius\":0.0623},{\"tool\":\"erase\",\"x\":0.7813,\"y\":0.6406,\"radius\":0.0366},{\"tool\":\"erase\",\"x\":0.5313,\"y\":0.6406,\"radius\":0.0378},{\"tool\":\"erase\",\"x\":0.5078,\"y\":0.5625,\"radius\":0.031},{\"tool\":\"erase\",\"x\":0.7188,\"y\":0.4531,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.6016,\"y\":0.4531,\"radius\":0.0354},{\"tool\":\"erase\",\"x\":0.7891,\"y\":0.4609,\"radius\":0.0464},{\"tool\":\"erase\",\"x\":0.8047,\"y\":0.5469,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.5684,\"y\":0.7197,\"radius\":0.0488},{\"tool\":\"erase\",\"x\":0.4805,\"y\":0.6514,\"radius\":0.0488},{\"tool\":\"erase\",\"x\":0.4756,\"y\":0.7393,\"radius\":0.0355},{\"tool\":\"erase\",\"x\":0.8174,\"y\":0.373,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.2095,\"y\":0.5952,\"radius\":0.031},{\"tool\":\"erase\",\"x\":0.1826,\"y\":0.5391,\"radius\":0.0366},{\"tool\":\"erase\",\"x\":0.2412,\"y\":0.5488,\"radius\":0.0354},{\"tool\":\"erase\",\"x\":0.8339,\"y\":0.3103,\"radius\":0.0305},{\"tool\":\"erase\",\"x\":0.7618,\"y\":0.3398,\"radius\":0.0305},{\"tool\":\"erase\",\"x\":0.8539,\"y\":0.4355,\"radius\":0.0305}]", + "MaxSlope": 30, + "TrunkCollisions": true, + "TrunkDiameter": 80 + } + }, + { + "assetStoreId": "", + "name": "LargeRocks", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "Model": "assets/Resource Rock 2.glb", + "Size": 106, + "Density": 0.008, + "Seed": 6, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.6343,\"y\":0.5488,\"radius\":0.2197},{\"tool\":\"erase\",\"x\":0.6318,\"y\":0.1338,\"toX\":0.6563,\"toY\":0.3779,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.6563,\"y\":0.3779,\"toX\":0.6563,\"toY\":0.6953,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.6563,\"y\":0.6953,\"toX\":0.6807,\"toY\":0.8662,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.5313,\"y\":0.4766,\"radius\":0.0623},{\"tool\":\"erase\",\"x\":0.7813,\"y\":0.6406,\"radius\":0.0366},{\"tool\":\"erase\",\"x\":0.5313,\"y\":0.6406,\"radius\":0.0378},{\"tool\":\"erase\",\"x\":0.5078,\"y\":0.5625,\"radius\":0.031},{\"tool\":\"erase\",\"x\":0.7188,\"y\":0.4531,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.6016,\"y\":0.4531,\"radius\":0.0354},{\"tool\":\"erase\",\"x\":0.7891,\"y\":0.4609,\"radius\":0.0464},{\"tool\":\"erase\",\"x\":0.8047,\"y\":0.5469,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.5684,\"y\":0.7197,\"radius\":0.0488},{\"tool\":\"erase\",\"x\":0.4805,\"y\":0.6514,\"radius\":0.0488},{\"tool\":\"erase\",\"x\":0.4756,\"y\":0.7393,\"radius\":0.0355},{\"tool\":\"erase\",\"x\":0.8174,\"y\":0.373,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.2095,\"y\":0.5952,\"radius\":0.031},{\"tool\":\"erase\",\"x\":0.1826,\"y\":0.5391,\"radius\":0.0366},{\"tool\":\"erase\",\"x\":0.2412,\"y\":0.5488,\"radius\":0.0354},{\"tool\":\"erase\",\"x\":0.8339,\"y\":0.3103,\"radius\":0.0305},{\"tool\":\"erase\",\"x\":0.7618,\"y\":0.3398,\"radius\":0.0305},{\"tool\":\"erase\",\"x\":0.8539,\"y\":0.4355,\"radius\":0.0305}]", + "MaxSlope": 30, + "TrunkCollisions": true, + "TrunkDiameter": 100 + } + }, + { + "assetStoreId": "", + "name": "GrassTufts", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Model": "assets\\grass_multiple_blades.glb", + "Size": 24, + "Density": 0.02, + "Seed": 7, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.6318,\"y\":0.1338,\"toX\":0.6563,\"toY\":0.3779,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.6563,\"y\":0.3779,\"toX\":0.6563,\"toY\":0.6953,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.6563,\"y\":0.6953,\"toX\":0.6807,\"toY\":0.8662,\"radius\":0.0635},{\"tool\":\"erase\",\"x\":0.5313,\"y\":0.4766,\"radius\":0.0623},{\"tool\":\"erase\",\"x\":0.7813,\"y\":0.6406,\"radius\":0.0366},{\"tool\":\"erase\",\"x\":0.5313,\"y\":0.6406,\"radius\":0.0378},{\"tool\":\"erase\",\"x\":0.5078,\"y\":0.5625,\"radius\":0.031},{\"tool\":\"erase\",\"x\":0.7188,\"y\":0.4531,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.6016,\"y\":0.4531,\"radius\":0.0354},{\"tool\":\"erase\",\"x\":0.7891,\"y\":0.4609,\"radius\":0.0464},{\"tool\":\"erase\",\"x\":0.8047,\"y\":0.5469,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.5684,\"y\":0.7197,\"radius\":0.0488},{\"tool\":\"erase\",\"x\":0.4805,\"y\":0.6514,\"radius\":0.0488},{\"tool\":\"erase\",\"x\":0.4756,\"y\":0.7393,\"radius\":0.0355},{\"tool\":\"erase\",\"x\":0.8174,\"y\":0.373,\"radius\":0.0439},{\"tool\":\"erase\",\"x\":0.2095,\"y\":0.5952,\"radius\":0.031},{\"tool\":\"erase\",\"x\":0.1826,\"y\":0.5391,\"radius\":0.0366},{\"tool\":\"erase\",\"x\":0.2412,\"y\":0.5488,\"radius\":0.0354},{\"tool\":\"erase\",\"x\":0.8339,\"y\":0.3103,\"radius\":0.0305},{\"tool\":\"erase\",\"x\":0.7618,\"y\":0.3398,\"radius\":0.0305},{\"tool\":\"erase\",\"x\":0.8539,\"y\":0.4355,\"radius\":0.0305}]", + "CastShadow": false, + "DrawDistance": 3000, + "MaxSlope": 35 + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "RTSUnit" + }, + { + "objectName": "SelectionTool_Hidden" + }, + { + "objectName": "SelectionTool_3DSprite" + }, + { + "objectName": "Grass" + }, + { + "objectName": "Camera" + }, + { + "folderName": "World", + "children": [ + { + "objectName": "Terrain" + }, + { + "objectName": "Water" + } + ] + }, + { + "folderName": "Buildings", + "children": [ + { + "objectName": "TownCenter" + }, + { + "objectName": "House" + }, + { + "objectName": "HouseTall" + }, + { + "objectName": "HouseSmall" + }, + { + "objectName": "WatchTower" + }, + { + "objectName": "Barracks" + }, + { + "objectName": "Market" + }, + { + "objectName": "Storage" + }, + { + "objectName": "Windmill" + }, + { + "objectName": "Mine" + }, + { + "objectName": "Farm" + } + ] + }, + { + "folderName": "Nature", + "children": [ + { + "objectName": "Gold" + }, + { + "objectName": "GoldLarge" + }, + { + "objectName": "Trees" + }, + { + "objectName": "RoundTrees" + }, + { + "objectName": "PineTrees" + }, + { + "objectName": "TreeGroups" + }, + { + "objectName": "Rocks" + }, + { + "objectName": "LargeRocks" + }, + { + "objectName": "GrassTufts" + } + ] + } + ] + }, + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "FirstPersonCamera::LookFrom3DObjectEyes" + }, + "parameters": [ + "", + "Camera", + "Object3D", + "", + "" + ] + }, + { + "type": { + "value": "FirstPersonCamera::LookFrom3DObjectEyes" + }, + "parameters": [ + "", + "Camera", + "Object3D", + "\"Selection Draw Layer\"", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Touchscreen Check" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SystemInfo::HasTouchScreen" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "ControlType", + "=", + "\"Touchscreen\"" + ] + } + ] + } + ] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Touchscreen controls", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "ControlType", + "=", + "\"Touchscreen\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Panning", + "False", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "MouseButtonFromTextPressed" + }, + "parameters": [ + "", + "\"Left\"" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [ + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"UnitSelection\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Unit movement" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "MouseButtonFromTextReleased" + }, + "parameters": [ + "", + "\"Left\"" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::ForEach", + "object": "RTSUnit", + "conditions": [ + { + "type": { + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "RTSUnit", + "" + ] + }, + { + "type": { + "value": "CompareTimer" + }, + "parameters": [ + "", + "\"UnitSelection\"", + "<", + "0.2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberObjectVariable" + }, + "parameters": [ + "RTSUnit", + "DestinationX", + "=", + "CursorX() + RandomWithStep(-20*UnitCount,+20*UnitCount,40)" + ] + }, + { + "type": { + "value": "SetNumberObjectVariable" + }, + "parameters": [ + "RTSUnit", + "DestinationY", + "=", + "CursorY() + RandomWithStep(-20*UnitCount,+20*UnitCount,40)" + ] + }, + { + "type": { + "value": "NavMeshPathfinding::MoveTo" + }, + "parameters": [ + "RTSUnit", + "NavMeshCharacter", + "RTSUnit.DestinationX", + "RTSUnit.DestinationY", + "Terrain.GroundZ(RTSUnit.DestinationX, RTSUnit.DestinationY)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "UnitCount", + "+", + "1" + ] + } + ] + } + ], + "variables": [ + { + "name": "UnitCount", + "type": "number", + "value": 0 + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Unit selection" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CompareTimer" + }, + "parameters": [ + "", + "\"UnitSelection\"", + ">=", + "0.2" + ] + } + ], + "actions": [ + { + "type": { + "value": "RTSUnitSelection::RTSUnitSelection" + }, + "parameters": [ + "", + "RTSUnit", + "SelectionTool_Hidden", + "", + "CursorY()+2000", + "Add", + "Subtract", + "Left", + "" + ] + }, + { + "type": { + "value": "Hide" + }, + "parameters": [ + "SelectionTool_Hidden" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [ + { + "type": { + "value": "Create" + }, + "parameters": [ + "", + "SelectionTool_3DSprite", + "CursorX()", + "CursorY()", + "\"Selection Draw Layer\"" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetZ" + }, + "parameters": [ + "SelectionTool_3DSprite", + "Object3D", + "=", + "3" + ] + }, + { + "type": { + "value": "SetNumberObjectVariable" + }, + "parameters": [ + "SelectionTool_3DSprite", + "SelectionStartX", + "=", + "SelectionTool_3DSprite.X()" + ] + }, + { + "type": { + "value": "SetNumberObjectVariable" + }, + "parameters": [ + "SelectionTool_3DSprite", + "SelectionStartY", + "=", + "SelectionTool_3DSprite.Y()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "MouseButtonFromTextPressed" + }, + "parameters": [ + "", + "\"Left\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetXY" + }, + "parameters": [ + "SelectionTool_3DSprite", + "=", + "min(CursorX(), SelectionTool_3DSprite.SelectionStartX)", + "=", + "min(CursorY(), SelectionTool_3DSprite.SelectionStartY)" + ] + }, + { + "type": { + "value": "ResizableCapability::ResizableBehavior::SetSize" + }, + "parameters": [ + "SelectionTool_3DSprite", + "Resizable", + "abs(CursorX() - SelectionTool_3DSprite.SelectionStartX)", + "abs(CursorY() - SelectionTool_3DSprite.SelectionStartY)" + ] + } + ] + } + ] + } + ] + } + ], + "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": [ + "ControlType", + "=", + "\"Keyboard\"" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Unit movement" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "MouseButtonFromTextReleased" + }, + "parameters": [ + "", + "\"Right\"" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::ForEach", + "object": "RTSUnit", + "conditions": [ + { + "type": { + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "RTSUnit", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberObjectVariable" + }, + "parameters": [ + "RTSUnit", + "DestinationX", + "=", + "CursorX() + RandomWithStep(-20*UnitCount,+20*UnitCount,40)" + ] + }, + { + "type": { + "value": "SetNumberObjectVariable" + }, + "parameters": [ + "RTSUnit", + "DestinationY", + "=", + "CursorY() + RandomWithStep(-20*UnitCount,+20*UnitCount,40)" + ] + }, + { + "type": { + "value": "NavMeshPathfinding::MoveTo" + }, + "parameters": [ + "RTSUnit", + "NavMeshCharacter", + "RTSUnit.DestinationX", + "RTSUnit.DestinationY", + "Terrain.GroundZ(RTSUnit.DestinationX, RTSUnit.DestinationY)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "UnitCount", + "+", + "1" + ] + } + ] + } + ], + "variables": [ + { + "name": "UnitCount", + "type": "number", + "value": 0 + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Unit selection" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "RTSUnitSelection::RTSUnitSelection" + }, + "parameters": [ + "", + "RTSUnit", + "SelectionTool_Hidden", + "", + "CursorY()+2000", + "Add", + "Subtract", + "Left", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "MouseButtonFromTextPressed" + }, + "parameters": [ + "", + "\"Left\"" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [ + { + "type": { + "value": "Create" + }, + "parameters": [ + "", + "SelectionTool_3DSprite", + "CursorX()", + "CursorY()", + "\"Selection Draw Layer\"" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetZ" + }, + "parameters": [ + "SelectionTool_3DSprite", + "Object3D", + "=", + "3" + ] + }, + { + "type": { + "value": "SetNumberObjectVariable" + }, + "parameters": [ + "SelectionTool_3DSprite", + "SelectionStartX", + "=", + "SelectionTool_3DSprite.X()" + ] + }, + { + "type": { + "value": "SetNumberObjectVariable" + }, + "parameters": [ + "SelectionTool_3DSprite", + "SelectionStartY", + "=", + "SelectionTool_3DSprite.Y()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Hide" + }, + "parameters": [ + "SelectionTool_Hidden" + ] + }, + { + "type": { + "value": "SetXY" + }, + "parameters": [ + "SelectionTool_3DSprite", + "=", + "min(CursorX(), SelectionTool_3DSprite.SelectionStartX)", + "=", + "min(CursorY(), SelectionTool_3DSprite.SelectionStartY)" + ] + }, + { + "type": { + "value": "ResizableCapability::ResizableBehavior::SetSize" + }, + "parameters": [ + "SelectionTool_3DSprite", + "Resizable", + "abs(CursorX() - SelectionTool_3DSprite.SelectionStartX)", + "abs(CursorY() - SelectionTool_3DSprite.SelectionStartY)" + ] + } + ] + } + ] + } + ] + } + ], + "parameters": [] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Delete selection box" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneInstancesCount" + }, + "parameters": [ + "", + "SelectionTool_3DSprite", + ">", + "0" + ] + }, + { + "type": { + "inverted": true, + "value": "MouseButtonFromTextPressed" + }, + "parameters": [ + "", + "\"Left\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Delete" + }, + "parameters": [ + "SelectionTool_3DSprite", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Unit highlight" + }, + { + "type": "BuiltinCommonInstructions::ForEach", + "object": "RTSUnit", + "conditions": [], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "RTSUnit", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsPreSelected" + }, + "parameters": [ + "", + "RTSUnit", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::SetTint" + }, + "parameters": [ + "", + "RTSUnit", + "Object3D", + "\"141;204;206\"", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "RTSUnitSelection::IsPreSelected" + }, + "parameters": [ + "", + "RTSUnit", + "" + ] + }, + { + "type": { + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "RTSUnit", + "" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::SetTint" + }, + "parameters": [ + "", + "RTSUnit", + "Object3D", + "\"255;255;255\"", + "" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "The map is bigger than the screen: the arrow keys (or WASD) move the camera, which stays above the valley." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"Left\"" + ] + }, + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"a\"" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "MettreX" + }, + "parameters": [ + "Camera", + "-", + "900 * TimeDelta()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"Right\"" + ] + }, + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"d\"" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "MettreX" + }, + "parameters": [ + "Camera", + "+", + "900 * TimeDelta()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"Up\"" + ] + }, + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"w\"" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "MettreY" + }, + "parameters": [ + "Camera", + "-", + "900 * TimeDelta()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"Down\"" + ] + }, + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"s\"" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "MettreY" + }, + "parameters": [ + "Camera", + "+", + "900 * TimeDelta()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Group", + "name": "Two-finger camera drag", + "source": "", + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "parameters": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "On touch screens, dragging with two fingers moves the camera: the point of the ground between the fingers (the center of the pinch) stays between them." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "PinchGesture::IsPinching" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "Panning", + "True", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "PanAnchorX", + "=", + "PinchGesture::CurrentCenterX(\"\")" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "PanAnchorY", + "=", + "PinchGesture::CurrentCenterY(\"\")" + ] + }, + { + "type": { + "value": "Delete" + }, + "parameters": [ + "SelectionTool_3DSprite", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "MettreX" + }, + "parameters": [ + "Camera", + "+", + "PanAnchorX - PinchGesture::CurrentCenterX(\"\")" + ] + }, + { + "type": { + "value": "MettreY" + }, + "parameters": [ + "Camera", + "+", + "PanAnchorY - PinchGesture::CurrentCenterY(\"\")" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "The touch controls are back once the fingers are lifted (the frame after: lifting a finger is not a tap)." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "MouseButtonFromTextPressed", + "inverted": true + }, + "parameters": [ + "", + "\"Left\"" + ] + }, + { + "type": { + "value": "MouseButtonFromTextReleased", + "inverted": true + }, + "parameters": [ + "", + "\"Left\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "Panning", + "False", + "" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "The camera stays above the valley, and the layers look from it." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "MettreX" + }, + "parameters": [ + "Camera", + "=", + "clamp(Camera.X(), -1300, 1300)" + ] + }, + { + "type": { + "value": "MettreY" + }, + "parameters": [ + "Camera", + "=", + "clamp(Camera.Y(), -1000, 1900)" + ] + }, + { + "type": { + "value": "FirstPersonCamera::LookFrom3DObjectEyes" + }, + "parameters": [ + "", + "Camera", + "Object3D", + "", + "" + ] + }, + { + "type": { + "value": "FirstPersonCamera::LookFrom3DObjectEyes" + }, + "parameters": [ + "", + "Camera", + "Object3D", + "\"Selection Draw Layer\"", + "" + ] + } + ] + } + ], + "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::DirectionalLight", + "name": "3D Sun Light", + "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::HemisphereLight", + "name": "3D Ambient Hemisphere Light", + "doubleParameters": { + "elevation": 60, + "intensity": 0.33, + "rotation": 300 + }, + "stringParameters": { + "groundColor": "127;127;127", + "skyColor": "255;255;255", + "top": "Z+" + }, + "booleanParameters": {} + } + ] + }, + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "Selection Draw Layer", + "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": "Resizable", + "type": "ResizableCapability::ResizableBehavior" + }, + { + "name": "Scale", + "type": "ScalableCapability::ScalableBehavior" + }, + { + "name": "NavMeshCharacter", + "type": "NavMeshPathfinding::NavMeshCharacterBehavior", + "cellSize": 5, + "cellDepth": 2, + "slopeMaxAngle": 22, + "stairHeightMax": 20, + "walkableRadius": -1, + "speedScaleY": 1, + "walkableDepth": 150 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior" + } + ] + } + ], + "externalEvents": [], + "tests": [ + { + "description": "A left drag box selects one unit, and a move order (right click on keyboard, short left tap on touchscreen) sends it walking while the others stay put.", + "lastRunAt": 1787843173674, + "lastRunDurationMs": 678, + "lastRunFramesExecuted": 58, + "lastRunStatus": "passed", + "name": "Selecting a unit and ordering it to move", + "type": "gameplay", + "source": [ + "// Works with BOTH control schemes. The game picks one at scene start via", + "// ControlType (set from SystemInfo::HasTouchScreen()):", + "// - Keyboard: LEFT drag box selects, RIGHT click orders a move.", + "// - Touchscreen: LEFT drag box (held >= 0.2s) selects, short LEFT tap", + "// (< 0.2s) orders a move.", + "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('RTSUnit');", + "", + "const getUnitById = (id) => harness.getObjects('RTSUnit').find((one) => one.id === id);", + "", + "await harness.stepFrames(5);", + "", + "const controlType = harness.getSceneVariable('ControlType')?.value;", + "const isTouchscreen = controlType === 'Touchscreen';", + "console.log('controlType=' + controlType + ' isTouchscreen=' + isTouchscreen);", + "", + "const units = harness.getObjects('RTSUnit');", + "harness.assert(units.length > 1, 'There are several units on the map.');", + "const target = units[0];", + "const others = units.slice(1);", + "console.log(", + " 'units=' + units.length +", + " ' selecting the one at ' + Math.round(target.centerX) + ',' + Math.round(target.centerY)", + ");", + "", + "/**", + " * Drag a selection box with the LEFT button over a rectangle of the map. It", + " * is held long enough to be a selection and not a move order (the events use", + " * a 0.2 second threshold).", + " */", + "const dragSelectionBox = async (from, to, layerName) => {", + " harness.setMousePosition(from.x, from.y, layerName);", + " await harness.stepFrames(2);", + " harness.setMouseButtonPressed(true, 'left');", + " await harness.stepFrames(14);", + " const STEPS = 6;", + " for (let step = 1; step <= STEPS; step++) {", + " harness.setMousePosition(", + " from.x + ((to.x - from.x) * step) / STEPS,", + " from.y + ((to.y - from.y) * step) / STEPS,", + " layerName", + " );", + " await harness.stepFrames(1);", + " }", + " await harness.stepFrames(3);", + " harness.setMouseButtonPressed(false, 'left');", + " await harness.stepFrames(3);", + "};", + "", + "/** Order a move to a point, using the active control scheme. */", + "const orderMoveAt = async (sceneX, sceneY, layerName) => {", + " harness.setMousePosition(sceneX, sceneY, layerName);", + " await harness.stepFrames(2);", + " if (isTouchscreen) {", + " // Short LEFT tap (< 0.2s) = move order.", + " harness.setMouseButtonPressed(true, 'left');", + " await harness.stepFrames(2);", + " harness.setMouseButtonPressed(false, 'left');", + " await harness.stepFrames(3);", + " } else {", + " // RIGHT click = move order.", + " harness.setMouseButtonPressed(true, 'right');", + " await harness.stepFrames(2);", + " harness.setMouseButtonPressed(false, 'right');", + " await harness.stepFrames(3);", + " }", + "};", + "", + "// A box just around the first unit, small enough not to catch any of the", + "// others.", + "const margin = target.width / 2 + 10;", + "await dragSelectionBox(", + " { x: target.centerX - margin, y: target.centerY - margin },", + " { x: target.centerX + margin, y: target.centerY + margin },", + " target.layer", + ");", + "", + "const positionsBeforeOrder = new Map(", + " harness.getObjects('RTSUnit').map((one) => [one.id, one])", + ");", + "", + "// Order a move, well away from where the units are.", + "await orderMoveAt(target.centerX + 220, target.centerY + 160, target.layer);", + "", + "const movedTowardDestination = await harness.stepUntil(", + " () => {", + " const unit = getUnitById(target.id);", + " const before = positionsBeforeOrder.get(target.id);", + " return (", + " Math.hypot(unit.centerX - before.centerX, unit.centerY - before.centerY) >", + " 60", + " );", + " },", + " { maxFrames: 60 }", + ");", + "", + "const movedUnit = getUnitById(target.id);", + "const movedDistance = Math.hypot(", + " movedUnit.centerX - positionsBeforeOrder.get(target.id).centerX,", + " movedUnit.centerY - positionsBeforeOrder.get(target.id).centerY", + ");", + "let othersThatMoved = 0;", + "for (const other of others) {", + " const now = getUnitById(other.id);", + " const before = positionsBeforeOrder.get(other.id);", + " if (", + " now &&", + " before &&", + " Math.hypot(now.centerX - before.centerX, now.centerY - before.centerY) > 10", + " ) {", + " othersThatMoved++;", + " }", + "}", + "console.log(", + " 'selectedUnitMoved=' + Math.round(movedDistance) +", + " ' othersThatMoved=' + othersThatMoved", + ");", + "", + "harness.assert(", + " movedTowardDestination,", + " 'The selected unit walks to where it was ordered (it moved ' +", + " Math.round(movedDistance) + 'px).'", + ");", + "harness.assert(", + " othersThatMoved === 0,", + " 'The units that were not selected stay where they are (' +", + " othersThatMoved + ' of them moved).'", + ");" + ] + }, + { + "description": "A left drag box over all the units selects them, and one move order (right click on keyboard, short left tap on touchscreen) sends the whole group walking.", + "lastRunAt": 1787843175081, + "lastRunDurationMs": 647, + "lastRunFramesExecuted": 53, + "lastRunStatus": "passed", + "name": "Selecting every unit at once", + "type": "gameplay", + "source": [ + "// Works with BOTH control schemes. The game picks one at scene start via", + "// ControlType (set from SystemInfo::HasTouchScreen()):", + "// - Keyboard: LEFT drag box selects, RIGHT click orders a move.", + "// - Touchscreen: LEFT drag box (held >= 0.2s) selects, short LEFT tap", + "// (< 0.2s) orders a move.", + "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('RTSUnit');", + "", + "const getUnitById = (id) => harness.getObjects('RTSUnit').find((one) => one.id === id);", + "", + "await harness.stepFrames(5);", + "", + "const controlType = harness.getSceneVariable('ControlType')?.value;", + "const isTouchscreen = controlType === 'Touchscreen';", + "console.log('controlType=' + controlType + ' isTouchscreen=' + isTouchscreen);", + "", + "const units = harness.getObjects('RTSUnit');", + "harness.assert(units.length > 1, 'There are several units on the map.');", + "", + "// A box that covers every unit.", + "const layerName = units[0].layer;", + "const MARGIN = 60;", + "const from = {", + " x: Math.min(...units.map((one) => one.centerX)) - MARGIN,", + " y: Math.min(...units.map((one) => one.centerY)) - MARGIN,", + "};", + "const to = {", + " x: Math.max(...units.map((one) => one.centerX)) + MARGIN,", + " y: Math.max(...units.map((one) => one.centerY)) + MARGIN,", + "};", + "console.log(", + " 'units=' + units.length +", + " ' box=' + Math.round(from.x) + ',' + Math.round(from.y) +", + " ' -> ' + Math.round(to.x) + ',' + Math.round(to.y)", + ");", + "", + "harness.setMousePosition(from.x, from.y, layerName);", + "await harness.stepFrames(2);", + "harness.setMouseButtonPressed(true, 'left');", + "await harness.stepFrames(14);", + "const STEPS = 6;", + "for (let step = 1; step <= STEPS; step++) {", + " harness.setMousePosition(", + " from.x + ((to.x - from.x) * step) / STEPS,", + " from.y + ((to.y - from.y) * step) / STEPS,", + " layerName", + " );", + " await harness.stepFrames(1);", + "}", + "await harness.stepFrames(3);", + "harness.setMouseButtonPressed(false, 'left');", + "await harness.stepFrames(3);", + "", + "const positionsBeforeOrder = new Map(", + " harness.getObjects('RTSUnit').map((one) => [one.id, one])", + ");", + "", + "// Order a move to clear ground, using the active control scheme.", + "const destinationX = 300;", + "const destinationY = 300;", + "harness.setMousePosition(destinationX, destinationY, layerName);", + "await harness.stepFrames(2);", + "if (isTouchscreen) {", + " // Short LEFT tap (< 0.2s) = move order.", + " harness.setMouseButtonPressed(true, 'left');", + " await harness.stepFrames(2);", + " harness.setMouseButtonPressed(false, 'left');", + " await harness.stepFrames(3);", + "} else {", + " // RIGHT click = move order.", + " harness.setMouseButtonPressed(true, 'right');", + " await harness.stepFrames(2);", + " harness.setMouseButtonPressed(false, 'right');", + " await harness.stepFrames(3);", + "}", + "", + "let unitsThatMoved = 0;", + "await harness.stepUntil(", + " () => {", + " unitsThatMoved = 0;", + " for (const [id, before] of positionsBeforeOrder) {", + " const now = getUnitById(id);", + " if (", + " now &&", + " Math.hypot(now.centerX - before.centerX, now.centerY - before.centerY) >", + " 40", + " ) {", + " unitsThatMoved++;", + " }", + " }", + " return unitsThatMoved === positionsBeforeOrder.size;", + " },", + " { maxFrames: 180 }", + ");", + "", + "const distances = [];", + "for (const [id, before] of positionsBeforeOrder) {", + " const now = getUnitById(id);", + " distances.push(", + " now", + " ? Math.round(", + " Math.hypot(now.centerX - before.centerX, now.centerY - before.centerY)", + " )", + " : null", + " );", + "}", + "console.log(", + " 'unitsThatMoved=' + unitsThatMoved + ' of ' + positionsBeforeOrder.size +", + " ' distances=' + JSON.stringify(distances)", + ");", + "harness.assert(", + " unitsThatMoved === positionsBeforeOrder.size,", + " 'Every unit of the selection walks to where the group was ordered (' +", + " unitsThatMoved + ' of ' + positionsBeforeOrder.size + ' moved).'", + ");", + "", + "// A picture of what the game looks like once this has happened, kept with", + "// the test results.", + "await harness.takeScreenshot('the whole selection walking to where it was sent');" + ] + }, + { + "name": "Dragging with two fingers moves the camera", + "description": "Puts two fingers on the screen and drags them to the left: the camera moves right (the ground follows the fingers), without selecting or moving units.", + "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);", + "await harness.stepFrames(3);", + "const inputManager = harness.getCurrentRuntimeScene().getGame().getInputManager();", + "const camera = harness.getRuntimeObject(harness.getObjects('Camera')[0].id);", + "const startX = camera.getX(), startY = camera.getY();", + "const centerX = harness.getGameResolutionWidth() / 2, centerY = harness.getGameResolutionHeight() / 2;", + "// Two fingers, one after the other, then dragged to the left (screen coordinates).", + "inputManager.onTouchStart(1, centerX - 20, centerY);", + "await harness.stepFrames(1);", + "inputManager.onTouchStart(2, centerX + 20, centerY);", + "await harness.stepFrames(2);", + "for (let step = 1; step <= 10; step++) {", + " inputManager.onTouchMove(1, centerX - 20 - 5 * step, centerY);", + " inputManager.onTouchMove(2, centerX + 20 - 5 * step, centerY);", + " await harness.stepFrames(1);", + "}", + "inputManager.onTouchEnd(2);", + "await harness.stepFrames(1);", + "inputManager.onTouchEnd(1);", + "await harness.stepFrames(3);", + "console.log('camera moved by ' + (camera.getX() - startX).toFixed(0) + ';' + (camera.getY() - startY).toFixed(0));", + "harness.assert(camera.getX() - startX > 100, 'The camera moved right (' + (camera.getX() - startX).toFixed(0) + ').');", + "harness.assert(Math.abs(camera.getY() - startY) < 30, 'The camera did not move up or down (' + (camera.getY() - startY).toFixed(0) + ').');", + "harness.assert(harness.getObjects('SelectionTool_3DSprite').length === 0, 'No selection box was drawn.');", + "const moving = harness.getObjects('RTSUnit').filter((unit) => harness.getRuntimeObject(unit.id).getBehavior('NavMeshCharacter').pathFound());", + "harness.assert(moving.length === 0, 'No unit was sent anywhere (' + moving.length + ').');", + "harness.assert(harness.getSceneVariable('Panning').value === false, 'The touch controls are back once the fingers are lifted.');" + ] + }, + { + "name": "The arrow keys move the camera over the map", + "description": "Holds Right then Up: the camera moves, and stops at the edge of the valley.", + "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 layer = harness.getCurrentRuntimeScene().getLayer('');", + "await harness.stepFrames(2);", + "const startX = layer.getCameraX(), startY = layer.getCameraY();", + "harness.setKeyPressed('Right', true);", + "await harness.stepFrames(10, { dtMs: 50 });", + "harness.setKeyPressed('Right', false);", + "harness.assert(layer.getCameraX() - startX > 300, 'The camera moved right (' + (layer.getCameraX() - startX).toFixed(0) + ').');", + "harness.setKeyPressed('Up', true);", + "await harness.stepFrames(80, { dtMs: 50 });", + "harness.setKeyPressed('Up', false);", + "const camera = harness.getObjects('Camera')[0];", + "console.log('camera at ' + camera.x.toFixed(0) + ';' + camera.y.toFixed(0) + ', looking at ' + layer.getCameraX().toFixed(0) + ';' + layer.getCameraY().toFixed(0));", + "harness.assert(startY - layer.getCameraY() > 1000, 'The camera moved up.');", + "harness.assert(camera.y >= -1000 - 1, 'The camera stopped at the edge of the valley (y=' + camera.y.toFixed(0) + ').');" + ] + } + ], + "eventsFunctionsExtensions": [ + { + "author": "Slash, Tristan Rhodes, @VictrisGames", + "category": "Game mechanic", + "description": "How To Use:\n- The \"Allow player to select units\" action must run every frame to work properly\n- When a drag is in progress, all units inside the selection box will be \"PreSelected\"\n- When the drag is released, all \"PreSelected\" units will be changed to \"Selected\"\n- Hold the additive select key to add units to selection\n- Hold the subtractive select key to remove units from selection\n\nControl Group controls:\n- Ctrl + (1,2,3,4,5,6,7,9,0) assigns that Control Group to Selected units\n- (1,2,3,4,5,6,7,9,0) selects all units that are assigned to that Control Group\n\nSelection box:\n- When dragging, the selection area will be drawn by a Shape Painter object. \n- If a Shape Painter instance has not been added in the scene, it will be created automatically.\n- To change the appearance of the selection area, change the Shape Painter properties.\n- Reduce the fill opacity of the SelectionBox (shape painter object) so that units and terrain can be seen behind it.\n\nTips:\n- Use the \"Preselected\" and \"Selected\" conditions to highlight units that are being interacted with\n- Create an object group (i.e. \"SelectableUnits\") to control which types of objects can be selected\n- Use conditions to restrict which object instances can be selected. \n- Use the \"SelectedUnitID\" to design formations of units.\n- Shape painter object should be on the same layer as the units being selected\n- Always use ForEach() events when using the conditions in this extension because extension conditions do not currently perform object picking.", + "extensionNamespace": "", + "gdevelopVersion": ">=5.5.222", + "fullName": "RTS-like unit selection", + "helpPath": "https://victrisgames.itch.io/rts-like-unit-selection", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXBlbmNpbC1ib3gtb3V0bGluZSIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0xOSwxOVY1SDVWMTlIMTlNMTksM0EyLDIgMCAwLDEgMjEsNVYxOUMyMSwyMC4xMSAyMC4xLDIxIDE5LDIxSDVBMiwyIDAgMCwxIDMsMTlWNUEyLDIgMCAwLDEgNSwzSDE5TTE2LjcsOS4zNUwxNS43LDEwLjM1TDEzLjY1LDguM0wxNC42NSw3LjNDMTQuODYsNy4wOCAxNS4yMSw3LjA4IDE1LjQyLDcuM0wxNi43LDguNThDMTYuOTIsOC43OSAxNi45Miw5LjE0IDE2LjcsOS4zNU03LDE0Ljk0TDEzLjA2LDguODhMMTUuMTIsMTAuOTRMOS4wNiwxN0g3VjE0Ljk0WiIgLz48L3N2Zz4=", + "name": "RTSUnitSelection", + "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/pencil-box-outline.svg", + "shortDescription": "Select units by clicking on them or dragging a selection box.", + "dimension": "2D", + "version": "2.4.1", + "tags": [ + "mouse", + "cursor", + "pointer", + "rts", + "unit", + "select", + "selector", + "selection", + "multi-select", + "control group" + ], + "authorIds": [ + "gqDaZjCfevOOxBYkK6zlhtZnXCg1", + "jZp7TS5D4NeOcwG8NCSu2vIzVWI2" + ], + "dependencies": [], + "eventsFunctions": [ + { + "description": "Allow player to select units by clicking on them or dragging a selection box.", + "fullName": "Allow player to select units by clicking on them or dragging a selection box", + "functionType": "Action", + "name": "RTSUnitSelection", + "private": false, + "sentence": "Allow player to select _PARAM1_ with _PARAM7_ mouse button (or touch). Draw a selection box using _PARAM2_ on Layer: _PARAM3_ with Z order: _PARAM4_. Hold _PARAM5_ to add units and _PARAM6_ to remove units", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "disabled": false, + "folded": false, + "name": "RTSUnitSelection Extension", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "disabled": false, + "folded": false, + "name": "Setup shape painter for selection box", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Create shape painter (if one hasn't been added already)", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "NbObjet" + }, + "parameters": [ + "SelectionBox", + "=", + "0" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "Create" + }, + "parameters": [ + "", + "SelectionBox", + "0", + "0", + "Layer" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Enforce correct properties of shape painter", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "PrimitiveDrawing::AreCoordinatesRelative" + }, + "parameters": [ + "SelectionBox" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "PrimitiveDrawing::UseRelativeCoordinates" + }, + "parameters": [ + "SelectionBox", + "no" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "PrimitiveDrawing::ClearBetweenFrames" + }, + "parameters": [ + "SelectionBox" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "PrimitiveDrawing::ClearBetweenFrames" + }, + "parameters": [ + "SelectionBox", + "yes" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "Zorder", + "!=", + "0" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "SetZOrder" + }, + "parameters": [ + "SelectionBox", + "=", + "Zorder" + ], + "subInstructions": [] + } + ], + "events": [] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "disabled": false, + "folded": false, + "name": "Unit selection", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Allow using any of the 3 mouse buttons", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::And" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "inverted": false, + "value": "MouseButtonPressed" + }, + "parameters": [ + "", + "Left" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "MouseButtonSelect", + "=", + "\"Left\"" + ], + "subInstructions": [] + } + ] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::And" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "inverted": false, + "value": "MouseButtonPressed" + }, + "parameters": [ + "", + "Right" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "MouseButtonSelect", + "=", + "\"Right\"" + ], + "subInstructions": [] + } + ] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::And" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "inverted": false, + "value": "MouseButtonPressed" + }, + "parameters": [ + "", + "Middle" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "MouseButtonSelect", + "=", + "\"Middle\"" + ], + "subInstructions": [] + } + ] + } + ] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Save initial location of click", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "ObjectVariableAsBoolean" + }, + "parameters": [ + "SelectionBox", + "__RTSUnitSelection.ClickStarted", + "False" + ], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Save the starting position ", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "inverted": false, + "value": "ModVarObjet" + }, + "parameters": [ + "SelectionBox", + "__RTSUnitSelection.StartX", + "=", + "MouseX(Layer,0)" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "ModVarObjet" + }, + "parameters": [ + "SelectionBox", + "__RTSUnitSelection.StartY", + "=", + "MouseY(Layer,0)" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Unselect all objects if Control or Shift is not being pressed", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "AdditiveSelectKey" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": true, + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "SubtractiveSelectKey" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Change state", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "inverted": false, + "value": "SetObjectVariableAsBoolean" + }, + "parameters": [ + "SelectionBox", + "__RTSUnitSelection.ClickStarted", + "True" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": " Single click select", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "IsCursorOnObject" + }, + "parameters": [ + "Objects", + "", + "", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetPreSelected" + }, + "parameters": [ + "", + "Objects", + "True", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Detect dragging", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "CursorX" + }, + "parameters": [ + "", + "!=", + "SelectionBox.Variable(__RTSUnitSelection.StartX)", + "Layer", + "0" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "CursorY" + }, + "parameters": [ + "", + "!=", + "SelectionBox.Variable(__RTSUnitSelection.StartY)", + "Layer", + "0" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "SetObjectVariableAsBoolean" + }, + "parameters": [ + "SelectionBox", + "__RTSUnitSelection.Dragging", + "True" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Actions to take while holding down the cursor", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "ObjectVariableAsBoolean" + }, + "parameters": [ + "SelectionBox", + "__RTSUnitSelection.Dragging", + "True" + ], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Save the current mouse location", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "inverted": false, + "value": "ModVarObjet" + }, + "parameters": [ + "SelectionBox", + "__RTSUnitSelection.EndX", + "=", + "MouseX(Layer,0)" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "ModVarObjet" + }, + "parameters": [ + "SelectionBox", + "__RTSUnitSelection.EndY", + "=", + "MouseY(Layer,0)" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Calculate the min and max values of the selection area", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "inverted": false, + "value": "ModVarObjet" + }, + "parameters": [ + "SelectionBox", + "RTSUnitSelection.MinX", + "=", + "min(SelectionBox.Variable(__RTSUnitSelection.StartX), SelectionBox.Variable(__RTSUnitSelection.EndX))" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "ModVarObjet" + }, + "parameters": [ + "SelectionBox", + "RTSUnitSelection.MaxX", + "=", + "max(SelectionBox.Variable(__RTSUnitSelection.StartX), SelectionBox.Variable(__RTSUnitSelection.EndX))" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "ModVarObjet" + }, + "parameters": [ + "SelectionBox", + "RTSUnitSelection.MinY", + "=", + "min(SelectionBox.Variable(__RTSUnitSelection.StartY), SelectionBox.Variable(__RTSUnitSelection.EndY))" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "ModVarObjet" + }, + "parameters": [ + "SelectionBox", + "RTSUnitSelection.MaxY", + "=", + "max(SelectionBox.Variable(__RTSUnitSelection.StartY), SelectionBox.Variable(__RTSUnitSelection.EndY))" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Draw selection area", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "inverted": false, + "value": "PrimitiveDrawing::Rectangle" + }, + "parameters": [ + "SelectionBox", + "SelectionBox.Variable(RTSUnitSelection.MinX)", + "SelectionBox.Variable(RTSUnitSelection.MinY)", + "SelectionBox.Variable(RTSUnitSelection.MaxX)", + "SelectionBox.Variable(RTSUnitSelection.MaxY)" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Check if objects are OUTSIDE selection area", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "inverted": false, + "value": "CenterX" + }, + "parameters": [ + "Objects", + "<", + "SelectionBox.Variable(RTSUnitSelection.MinX)" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "CenterX" + }, + "parameters": [ + "Objects", + ">", + "SelectionBox.Variable(RTSUnitSelection.MaxX)" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "CenterY" + }, + "parameters": [ + "Objects", + "<", + "SelectionBox.Variable(RTSUnitSelection.MinY)" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "CenterY" + }, + "parameters": [ + "Objects", + ">", + "SelectionBox.Variable(RTSUnitSelection.MaxY)" + ], + "subInstructions": [] + } + ] + }, + { + "type": { + "inverted": true, + "value": "IsCursorOnObject" + }, + "parameters": [ + "Objects", + "", + "", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetPreSelected" + }, + "parameters": [ + "", + "Objects", + "False", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Check if objects are INSIDE selection area", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "CenterY" + }, + "parameters": [ + "Objects", + ">=", + "SelectionBox.Variable(RTSUnitSelection.MinY)" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "CenterX" + }, + "parameters": [ + "Objects", + "<=", + "SelectionBox.Variable(RTSUnitSelection.MaxX)" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "CenterX" + }, + "parameters": [ + "Objects", + ">=", + "SelectionBox.Variable(RTSUnitSelection.MinX)" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "CenterY" + }, + "parameters": [ + "Objects", + "<=", + "SelectionBox.Variable(RTSUnitSelection.MaxY)" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetPreSelected" + }, + "parameters": [ + "", + "Objects", + "True", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Reset Drawing state when cursor is released", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::And" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "inverted": false, + "value": "MouseButtonReleased" + }, + "parameters": [ + "", + "Left" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "MouseButtonSelect", + "=", + "\"Left\"" + ], + "subInstructions": [] + } + ] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::And" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "inverted": false, + "value": "MouseButtonReleased" + }, + "parameters": [ + "", + "Right" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "MouseButtonSelect", + "=", + "\"Right\"" + ], + "subInstructions": [] + } + ] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::And" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "inverted": false, + "value": "MouseButtonReleased" + }, + "parameters": [ + "", + "Middle" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "MouseButtonSelect", + "=", + "\"Middle\"" + ], + "subInstructions": [] + } + ] + } + ] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 27, + "g": 2, + "r": 208, + "textB": 255, + "textG": 255, + "textR": 255 + }, + "comment": "This \"ForEach(Object)\" is required because extension conditions do not perform object picking (hopefully that will be added in the future)", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Deselect objects when a Selected object gets clicked on", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsPreSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": true, + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "AdditiveSelectKey" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetPreSelected" + }, + "parameters": [ + "", + "Objects", + "False", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsPreSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "SubtractiveSelectKey" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetPreSelected" + }, + "parameters": [ + "", + "Objects", + "False", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Convert PreSelected objects into Selected", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsPreSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "True", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetPreSelected" + }, + "parameters": [ + "", + "Objects", + "False", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Reset variables", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "inverted": false, + "value": "SetObjectVariableAsBoolean" + }, + "parameters": [ + "SelectionBox", + "__RTSUnitSelection.Dragging", + "False" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "SetObjectVariableAsBoolean" + }, + "parameters": [ + "SelectionBox", + "__RTSUnitSelection.ClickStarted", + "False" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "inverted": false, + "value": "ModVarObjet" + }, + "parameters": [ + "SelectionBox", + "__RTSUnitSelection.StartX", + "=", + "0" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "ModVarObjet" + }, + "parameters": [ + "SelectionBox", + "__RTSUnitSelection.StartY", + "=", + "0" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "inverted": false, + "value": "ModVarObjet" + }, + "parameters": [ + "SelectionBox", + "__RTSUnitSelection.EndX", + "=", + "0" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "ModVarObjet" + }, + "parameters": [ + "SelectionBox", + "__RTSUnitSelection.EndY", + "=", + "0" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "codeOnly": false, + "defaultValue": "", + "description": "Units", + "longDescription": "Object (or object group) that can be Selected", + "name": "Objects", + "optional": false, + "supplementaryInformation": "", + "type": "objectList" + }, + { + "codeOnly": false, + "defaultValue": "", + "description": "Selection box", + "longDescription": "Edit shape painter properties to change the appearance of this selection box", + "name": "SelectionBox", + "optional": false, + "supplementaryInformation": "PrimitiveDrawing::Drawer", + "type": "objectList" + }, + { + "codeOnly": false, + "defaultValue": "", + "description": "Layer (of selection box)", + "longDescription": "Must be the same layer as the units being selected", + "name": "Layer", + "optional": false, + "supplementaryInformation": "", + "type": "layer" + }, + { + "codeOnly": false, + "defaultValue": "", + "description": "Z order (of selection box)", + "longDescription": "Z order of the selection box", + "name": "Zorder", + "optional": false, + "supplementaryInformation": "", + "type": "expression" + }, + { + "codeOnly": false, + "defaultValue": "", + "description": "Additive select key", + "longDescription": "Hold this key to add units to selection", + "name": "AdditiveSelectKey", + "optional": false, + "supplementaryInformation": "", + "type": "key" + }, + { + "codeOnly": false, + "defaultValue": "", + "description": "Subtractive select key", + "longDescription": "Hold this key to remove units from selection", + "name": "SubtractiveSelectKey", + "optional": false, + "supplementaryInformation": "", + "type": "key" + }, + { + "codeOnly": false, + "defaultValue": "", + "description": "Mouse button used to select units", + "longDescription": "", + "name": "MouseButtonSelect", + "optional": false, + "supplementaryInformation": "", + "type": "mouse" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the unit is \"Preselected\".", + "fullName": "Is unit \"Preselected\"", + "functionType": "Condition", + "name": "IsPreSelected", + "private": false, + "sentence": "_PARAM1_ is \"Preselected\"", + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "ObjectVariableAsBoolean" + }, + "parameters": [ + "Object", + "__RTSUnitSelection.PreSelected", + "False" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "SetReturnBoolean" + }, + "parameters": [ + "False" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "ObjectVariableAsBoolean" + }, + "parameters": [ + "Object", + "__RTSUnitSelection.PreSelected", + "True" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ], + "subInstructions": [] + } + ], + "events": [] + } + ], + "parameters": [ + { + "codeOnly": false, + "defaultValue": "", + "description": "Object", + "longDescription": "", + "name": "Object", + "optional": false, + "supplementaryInformation": "", + "type": "objectList" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the unit is \"Selected\".", + "fullName": "Is unit \"Selected\"", + "functionType": "Condition", + "name": "IsSelected", + "private": false, + "sentence": "_PARAM1_ is \"Selected\"", + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "ObjectVariableAsBoolean" + }, + "parameters": [ + "Object", + "__RTSUnitSelection.Selected", + "False" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "SetReturnBoolean" + }, + "parameters": [ + "False" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "ObjectVariableAsBoolean" + }, + "parameters": [ + "Object", + "__RTSUnitSelection.Selected", + "True" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ], + "subInstructions": [] + } + ], + "events": [] + } + ], + "parameters": [ + { + "codeOnly": false, + "defaultValue": "", + "description": "Object", + "longDescription": "", + "name": "Object", + "optional": false, + "supplementaryInformation": "", + "type": "objectList" + } + ], + "objectGroups": [] + }, + { + "description": "Set unit as \"Preselected\".", + "fullName": "Set unit as \"Preselected\"", + "functionType": "Action", + "name": "SetPreSelected", + "private": false, + "sentence": "Set _PARAM1_ as \"Preselected\": _PARAM2_", + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "SetObjectVariableAsBoolean" + }, + "parameters": [ + "Object", + "__RTSUnitSelection.PreSelected", + "True" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "SetObjectVariableAsBoolean" + }, + "parameters": [ + "Object", + "__RTSUnitSelection.PreSelected", + "False" + ], + "subInstructions": [] + } + ], + "events": [] + } + ], + "parameters": [ + { + "codeOnly": false, + "defaultValue": "", + "description": "Object", + "longDescription": "", + "name": "Object", + "optional": false, + "supplementaryInformation": "", + "type": "objectList" + }, + { + "codeOnly": false, + "defaultValue": "", + "description": "Value", + "longDescription": "", + "name": "Value", + "optional": false, + "supplementaryInformation": "", + "type": "trueorfalse" + } + ], + "objectGroups": [] + }, + { + "description": "Set unit as \"Selected\".", + "fullName": "Set unit as \"Selected\"", + "functionType": "Action", + "name": "SetSelected", + "private": false, + "sentence": "Set _PARAM1_ as \"Selected\": _PARAM2_", + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "SetObjectVariableAsBoolean" + }, + "parameters": [ + "Object", + "__RTSUnitSelection.Selected", + "True" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "SetObjectVariableAsBoolean" + }, + "parameters": [ + "Object", + "__RTSUnitSelection.Selected", + "False" + ], + "subInstructions": [] + } + ], + "events": [] + } + ], + "parameters": [ + { + "codeOnly": false, + "defaultValue": "", + "description": "Object", + "longDescription": "", + "name": "Object", + "optional": false, + "supplementaryInformation": "", + "type": "objectList" + }, + { + "codeOnly": false, + "defaultValue": "", + "description": "Value", + "longDescription": "", + "name": "Value", + "optional": false, + "supplementaryInformation": "", + "type": "trueorfalse" + } + ], + "objectGroups": [] + }, + { + "description": "Assign a unique ID to each \"Selected\" unit. This should be ran every time there is a change in the number of \"Selected\" units.", + "fullName": "Assign a unique ID to each \"Selected\" unit", + "functionType": "Action", + "name": "AssignUnitID", + "private": false, + "sentence": "Assign a unique ID to _PARAM1_ that are \"Selected\"", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "disabled": false, + "folded": false, + "name": "Assign unique IDs", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Reset counter", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "inverted": false, + "value": "ModVarScene" + }, + "parameters": [ + "__RTSUnitSelection.TotalSelectedUnits", + "=", + "0" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 27, + "g": 2, + "r": 208, + "textB": 255, + "textG": 255, + "textR": 255 + }, + "comment": "These \"ForEach(Object)\" loops are required because extension conditions do not perform object picking (hopefully that will be added in the future)", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Object", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Clean up old unit IDs", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "inverted": false, + "value": "ModVarObjet" + }, + "parameters": [ + "Object", + "__RTSUnitSelection.SelectedUnitID", + "=", + "0" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Assign a unique number to each unit (this could be used to make formations)", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Object", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "ModVarScene" + }, + "parameters": [ + "__RTSUnitSelection.TotalSelectedUnits", + "+", + "1" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "ModVarObjet" + }, + "parameters": [ + "Object", + "__RTSUnitSelection.SelectedUnitID", + "=", + "Variable(__RTSUnitSelection.TotalSelectedUnits)" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "codeOnly": false, + "defaultValue": "", + "description": "Units", + "longDescription": "", + "name": "Object", + "optional": false, + "supplementaryInformation": "", + "type": "objectList" + } + ], + "objectGroups": [] + }, + { + "description": "Provides the total number of _PARAM1_ that are currently \"Selected\".", + "fullName": "Total number of \"Selected\" units", + "functionType": "Expression", + "name": "TotalSelectedUnits", + "private": false, + "sentence": "", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "disabled": false, + "folded": false, + "name": "Count Selected units", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Reset counter", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "inverted": false, + "value": "ModVarScene" + }, + "parameters": [ + "__RTSUnitSelection.TotalSelectedUnits", + "=", + "0" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 27, + "g": 2, + "r": 208, + "textB": 255, + "textG": 255, + "textR": 255 + }, + "comment": "These \"ForEach(Object)\" loops are required because extension conditions do not perform object picking (hopefully that will be added in the future)", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Object", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Object", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "ModVarScene" + }, + "parameters": [ + "__RTSUnitSelection.TotalSelectedUnits", + "+", + "1" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "inverted": false, + "value": "SetReturnNumber" + }, + "parameters": [ + "Variable(__RTSUnitSelection.TotalSelectedUnits)" + ], + "subInstructions": [] + } + ], + "events": [] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "codeOnly": false, + "defaultValue": "", + "description": "Unit", + "longDescription": "", + "name": "Object", + "optional": false, + "supplementaryInformation": "", + "type": "objectList" + } + ], + "objectGroups": [] + }, + { + "description": "Enable control groups using default controls.", + "fullName": "Enable control groups using default controls", + "functionType": "Action", + "name": "RTSControlGroups", + "private": false, + "sentence": "Assign _PARAM1_ to control groups using default controls (Ctrl + number)", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "disabled": false, + "folded": false, + "name": "Control groups using default controls", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "disabled": false, + "folded": false, + "name": "Assign control group", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "LControl" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "RControl" + ], + "subInstructions": [] + } + ] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 27, + "g": 2, + "r": 208, + "textB": 255, + "textG": 255, + "textR": 255 + }, + "comment": "These \"ForEach(Object)\" loops are required because extension conditions do not perform object picking (hopefully that will be added in the future)", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "Num1" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Remove control group from non-selected objects", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "1", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::AssignControlGroup" + }, + "parameters": [ + "", + "Objects", + "0", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Set the control group ID on selected objects", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "1", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::AssignControlGroup" + }, + "parameters": [ + "", + "Objects", + "1", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "Num2" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Remove control group from non-selected objects", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "2", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::AssignControlGroup" + }, + "parameters": [ + "", + "Objects", + "0", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Set the control group ID on selected objects", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "2", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::AssignControlGroup" + }, + "parameters": [ + "", + "Objects", + "2", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "Num3" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Remove control group from non-selected objects", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "3", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::AssignControlGroup" + }, + "parameters": [ + "", + "Objects", + "0", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Set the control group ID on selected objects", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "3", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::AssignControlGroup" + }, + "parameters": [ + "", + "Objects", + "3", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "Num4" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Remove control group from non-selected objects", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "4", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::AssignControlGroup" + }, + "parameters": [ + "", + "Objects", + "0", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Set the control group ID on selected objects", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "4", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::AssignControlGroup" + }, + "parameters": [ + "", + "Objects", + "4", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "Num5" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Remove control group from non-selected objects", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "5", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::AssignControlGroup" + }, + "parameters": [ + "", + "Objects", + "0", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Set the control group ID on selected objects", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "5", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::AssignControlGroup" + }, + "parameters": [ + "", + "Objects", + "5", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "Num6" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Remove control group from non-selected objects", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "6", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::AssignControlGroup" + }, + "parameters": [ + "", + "Objects", + "0", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Set the control group ID on selected objects", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "6", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::AssignControlGroup" + }, + "parameters": [ + "", + "Objects", + "6", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "Num7" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Remove control group from non-selected objects", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "7", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::AssignControlGroup" + }, + "parameters": [ + "", + "Objects", + "0", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Set the control group ID on selected objects", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "7", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::AssignControlGroup" + }, + "parameters": [ + "", + "Objects", + "7", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "Num8" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Remove control group from non-selected objects", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "8", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::AssignControlGroup" + }, + "parameters": [ + "", + "Objects", + "0", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Set the control group ID on selected objects", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "8", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::AssignControlGroup" + }, + "parameters": [ + "", + "Objects", + "8", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "Num9" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Remove control group from non-selected objects", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "9", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::AssignControlGroup" + }, + "parameters": [ + "", + "Objects", + "0", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Set the control group ID on selected objects", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "9", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::AssignControlGroup" + }, + "parameters": [ + "", + "Objects", + "9", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "Num0" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Remove control group from non-selected objects", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "10", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::AssignControlGroup" + }, + "parameters": [ + "", + "Objects", + "0", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Set the control group ID on selected objects", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsSelected" + }, + "parameters": [ + "", + "Objects", + "" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "10", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::AssignControlGroup" + }, + "parameters": [ + "", + "Objects", + "10", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "disabled": false, + "folded": false, + "name": "Select units based on control group", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "KeyPressed" + }, + "parameters": [ + "", + "RControl" + ], + "subInstructions": [] + }, + { + "type": { + "inverted": true, + "value": "KeyPressed" + }, + "parameters": [ + "", + "LControl" + ], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 27, + "g": 2, + "r": 208, + "textB": 255, + "textG": 255, + "textR": 255 + }, + "comment": "These \"ForEach(Object)\" loops are required because extension conditions do not perform object picking (hopefully that will be added in the future)", + "comment2": "" + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "Num1" + ], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "1", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "False", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "1", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "True", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "Num2" + ], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "2", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "False", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "2", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "True", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "Num3" + ], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "3", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "False", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "3", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "True", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "Num4" + ], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "4", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "False", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "4", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "True", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "Num5" + ], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "5", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "False", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "5", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "True", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "Num6" + ], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "6", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "False", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "6", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "True", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "Num7" + ], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "7", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "False", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "7", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "True", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "Num8" + ], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "8", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "False", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "8", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "True", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "Num9" + ], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "9", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "False", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "9", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "True", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "KeyPressed" + }, + "parameters": [ + "", + "Num0" + ], + "subInstructions": [] + } + ], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::ForEach", + "object": "Objects", + "conditions": [], + "actions": [], + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "10", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "False", + "" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::IsAssignedToControlGroup" + }, + "parameters": [ + "", + "Objects", + "10", + "" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "RTSUnitSelection::SetSelected" + }, + "parameters": [ + "", + "Objects", + "True", + "" + ], + "subInstructions": [] + } + ], + "events": [] + } + ] + } + ] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "codeOnly": false, + "defaultValue": "", + "description": "Units", + "longDescription": "Object (or object group) that will be assigned to a control group", + "name": "Objects", + "optional": false, + "supplementaryInformation": "", + "type": "objectList" + } + ], + "objectGroups": [] + }, + { + "description": "Control group this unit is assigned to.", + "fullName": "Control group this unit is assigned to", + "functionType": "Expression", + "name": "ControlGroupID", + "private": false, + "sentence": "", + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "inverted": false, + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Variable(__RTSUnitSelection.ControlGroupID)" + ], + "subInstructions": [] + } + ], + "events": [] + } + ], + "parameters": [ + { + "codeOnly": false, + "defaultValue": "", + "description": "Unit", + "longDescription": "", + "name": "Object", + "optional": false, + "supplementaryInformation": "", + "type": "objectList" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a unit is assigned to a control group.", + "fullName": "Check if a unit is assigned to a control group", + "functionType": "Condition", + "name": "IsAssignedToControlGroup", + "private": false, + "sentence": "_PARAM1_ is assigned to control group _PARAM2_", + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "RTSUnitSelection::ControlGroupID(Object)", + "=", + "ControlGroupID" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ], + "subInstructions": [] + } + ], + "events": [] + }, + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": false, + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "RTSUnitSelection::ControlGroupID(Object)", + "!=", + "ControlGroupID" + ], + "subInstructions": [] + } + ], + "actions": [ + { + "type": { + "inverted": false, + "value": "SetReturnBoolean" + }, + "parameters": [ + "False" + ], + "subInstructions": [] + } + ], + "events": [] + } + ], + "parameters": [ + { + "codeOnly": false, + "defaultValue": "", + "description": "Object", + "longDescription": "", + "name": "Object", + "optional": false, + "supplementaryInformation": "", + "type": "objectList" + }, + { + "codeOnly": false, + "defaultValue": "", + "description": "Control group ID", + "longDescription": "", + "name": "ControlGroupID", + "optional": false, + "supplementaryInformation": "", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Assign unit to a control group.", + "fullName": "Assign unit to a control group", + "functionType": "Action", + "name": "AssignControlGroup", + "private": false, + "sentence": "Assign _PARAM1_ to control group _PARAM2_", + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "inverted": false, + "value": "ModVarObjet" + }, + "parameters": [ + "Object", + "__RTSUnitSelection.ControlGroupID", + "=", + "Value" + ], + "subInstructions": [] + } + ], + "events": [] + } + ], + "parameters": [ + { + "codeOnly": false, + "defaultValue": "", + "description": "Object", + "longDescription": "", + "name": "Object", + "optional": false, + "supplementaryInformation": "", + "type": "objectList" + }, + { + "codeOnly": false, + "defaultValue": "", + "description": "Control group ID", + "longDescription": "", + "name": "Value", + "optional": false, + "supplementaryInformation": "", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Unit ID of a selected unit.", + "fullName": "Unit ID of a selected unit", + "functionType": "Expression", + "name": "SelectedUnitID", + "private": false, + "sentence": "", + "events": [ + { + "disabled": false, + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "inverted": false, + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Variable(__RTSUnitSelection.SelectedUnitID)" + ], + "subInstructions": [] + } + ], + "events": [] + } + ], + "parameters": [ + { + "codeOnly": false, + "defaultValue": "", + "description": "Unit", + "longDescription": "", + "name": "Object", + "optional": false, + "supplementaryInformation": "", + "type": "objectList" + } + ], + "objectGroups": [] + } + ], + "eventsBasedBehaviors": [] + }, + { + "author": "", + "category": "Camera", + "extensionNamespace": "", + "fullName": "First person 3D camera", + "gdevelopVersion": ">=5.5.222", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQoJLnN0MXtmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEwO30NCjwvc3R5bGU+DQo8cGF0aCBjbGFzcz0ic3QwIiBkPSJNMjcsMTZjMCwwLTQuOSw3LTExLDdTNSwxNiw1LDE2czQuOS03LDExLTdTMjcsMTYsMjcsMTZ6Ii8+DQo8Y2lyY2xlIGNsYXNzPSJzdDAiIGN4PSIxNiIgY3k9IjE2IiByPSIzIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSIzLDkgMywzIDksMyAiLz4NCjxwb2x5bGluZSBjbGFzcz0ic3QwIiBwb2ludHM9IjksMjkgMywyOSAzLDIzICIvPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMjksMjMgMjksMjkgMjMsMjkgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSIyMywzIDI5LDMgMjksOSAiLz4NCjwvc3ZnPg0K", + "name": "FirstPersonCamera", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/Line Hero Pack/Master/SVG/Security and Protection/e8248ffd504c314e6a9d9d560c3a0fdad3398a5f125ae6a4c0654d4e68c86376_Security and Protection_security_protection_eye_scan_lock.svg", + "shortDescription": "Move the camera to look though objects eyes.", + "dimension": "3D", + "version": "1.0.5", + "description": [ + "Move the camera to look though objects eyes.", + "", + "This extension can be used with the \"Mouse pointer lock\" or \"Gamepad\" extension to build a first person camera.", + "", + "The first person example uses this extension ([open the project online](https://editor.gdevelop.io/?project=example://3d-first-person))." + ], + "origin": { + "identifier": "FirstPersonCamera", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d", + "camera", + "fps" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2", + "m8kleQHonagHWsvILDhyJhgVhuF2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [ + { + "description": "Move the camera to look though the object 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 of _PARAM2_ to look though _PARAM1_ eyes", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "CenterCameraOnObject" + }, + "parameters": [ + "", + "Object", + "", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "Object.Z()", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationX" + }, + "parameters": [ + "", + "=", + "- Object.RotationY() + 90", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationY" + }, + "parameters": [ + "", + "=", + "Object.RotationX()", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraAngle" + }, + "parameters": [ + "", + "=", + "Object.Angle() + 90", + "Layer", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Scene3D::Cube3DObject", + "type": "objectList" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera to look though the object 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": "LookFrom3DObjectEyes", + "sentence": "Move the camera of _PARAM3_ to look though _PARAM1_ eyes", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "CenterCameraOnObject" + }, + "parameters": [ + "", + "Object", + "", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "Object.Object3D::Z()", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationX" + }, + "parameters": [ + "", + "=", + "- Object.Object3D::RotationY() + 90", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationY" + }, + "parameters": [ + "", + "=", + "Object.Object3D::RotationX()", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraAngle" + }, + "parameters": [ + "", + "=", + "Object.Angle() + 90", + "Layer", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "3D Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + } + ], + "eventsBasedBehaviors": [], + "eventsBasedObjects": [] + }, + { + "author": "", + "category": "Visual effect", + "dimension": "", + "extensionNamespace": "", + "fullName": "Advanced 3D tween", + "gdevelopVersion": ">=5.5.222", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXRlbm5pcy1iYWxsIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTEyLDJDMTQuNSwyIDE2Ljc1LDIuOSAxOC41LDQuNEMxNi4zNiw2LjIzIDE1LDguOTYgMTUsMTJDMTUsMTUuMDQgMTYuMzYsMTcuNzcgMTguNSwxOS42QzE2Ljc1LDIxLjEgMTQuNSwyMiAxMiwyMkM5LjUsMjIgNy4yNSwyMS4xIDUuNSwxOS42QzcuNjQsMTcuNzcgOSwxNS4wNCA5LDEyQzksOC45NiA3LjY0LDYuMjMgNS41LDQuNEM3LjI1LDIuOSA5LjUsMiAxMiwyTTIyLDEyQzIyLDE0LjMyIDIxLjIxLDE2LjQ1IDE5Ljg4LDE4LjE1QzE4LjEyLDE2LjY4IDE3LDE0LjQ3IDE3LDEyQzE3LDkuNTMgMTguMTIsNy4zMiAxOS44OCw1Ljg1QzIxLjIxLDcuNTUgMjIsOS42OCAyMiwxMk0yLDEyQzIsOS42OCAyLjc5LDcuNTUgNC4xMiw1Ljg1QzUuODgsNy4zMiA3LDkuNTMgNywxMkM3LDE0LjQ3IDUuODgsMTYuNjggNC4xMiwxOC4xNUMyLjc5LDE2LjQ1IDIsMTQuMzIgMiwxMloiIC8+PC9zdmc+", + "name": "Tween3D", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/30bc03f9cf5f2f42960246a605352a6937ae6b603f0177396a83193849a4724a_tennis-ball.svg", + "shortDescription": "Smoothly squash, strentch, tint and wobble 3D models.", + "version": "0.3.1", + "description": [ + "Change 3D models proportions over time to make smooth animations like wobbling.", + "", + "It's used in this example:", + "* a 3D Bomberman-like game ([open the project online](https://editor.gdevelop.io/?project=example://3d-bomber-bunny))" + ], + "origin": { + "identifier": "Tween3D", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d", + "tween", + "smooth", + "interpolation", + "wobble", + "color" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2", + "dt0tRnf2kHWJnjkrpnzTzNj9Yc63" + ], + "changelog": [ + { + "version": "0.1.0", + "breaking": "- The Tween behavior is now required by the 3D Tween behavior. Add the Tween behavior on any object that uses the 3D Tween behavior." + } + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onFirstSceneLoaded", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "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.__tween3DExtension) {", + " return;", + "}", + "", + "class Tint {", + " /**", + " * Materials by material `id`.", + " * @type {Map}", + " */", + " materials;", + " usageCount = 0;", + "", + " /**", + " * @param materials {Map}", + " */", + " constructor(materials) {", + " this.materials = materials;", + " }", + "", + " /**", + " * @param object {gdjs.RuntimeObject3D}", + " */", + " applyTo(object) {", + " object.get3DRendererObject().traverse((mesh) => {", + " if (mesh.material) {", + " const material = mesh.material;", + " const originalId = material.originalId || material.id;", + " const tintedMaterial = this.materials.get(originalId);", + " if (tintedMaterial) {", + " mesh.material = tintedMaterial;", + " }", + " }", + " });", + " }", + "", + " /**", + " * @param tintColor {number}", + " * @param originalTint {Tint}", + " */", + " setColor(tintColor, originalTint) {", + " const originalMaterials = originalTint.materials;", + " for (const [materialId, material] of this.materials) {", + " const originalMaterial = originalMaterials.get(materialId);", + " if (!originalMaterial) {", + " // A new material may have been changed by another extension.", + " continue;", + " }", + " const originalColor = originalMaterial.color;", + "", + " const color = material.color;", + " color.setHex(tintColor);", + " color.r *= originalColor.r;", + " color.g *= originalColor.g;", + " color.b *= originalColor.b;", + " }", + " }", + "", + " dispose() {", + " for (const material of this.materials.values()) {", + " material.dispose();", + " }", + " }", + "}", + "", + "class TintManager {", + " /**", + " * Tints by color hex.", + " * @type {Map}", + " */", + " tints = new Map();", + " /**", + " * Materials by material `id`.", + " * @type {Tint}", + " */", + " originalTint;", + " /**", + " * @type {Array}", + " */", + " recycledTints = [];", + "", + " /**", + " * @param {gdjs.RuntimeObject3D} object", + " */", + " constructor(object) {", + " const materials = new Map();", + " object.get3DRendererObject().traverse((mesh) => {", + " if (mesh.material && mesh.material.color) {", + " const material = mesh.material;", + " materials.set(material.id, material);", + " }", + " });", + " this.originalTint = new Tint(materials);", + " }", + "", + " /**", + " * @param {gdjs.RuntimeObject3D} object", + " * @param {number} color", + " */", + " applyTint(object, color) {", + " const previousColor = this.getTint(object);", + " if (color === previousColor) {", + " return;", + " }", + " if (previousColor !== 0xffffff) {", + " const previousTint = this.tints.get(previousColor);", + " previousTint.usageCount--;", + " if (previousTint.usageCount === 0) {", + " this.tints.delete(previousColor);", + " this.recycledTints.push(previousTint);", + " }", + " }", + " let tint = color === 0xffffff ? this.originalTint : this.tints.get(color);", + " if (!tint) {", + " tint = this.createTint(object, color);", + " this.tints.set(color, tint);", + " }", + " tint.applyTo(object);", + " tint.usageCount++;", + " object.__tint3DExtension_tint = color;", + " }", + "", + " /**", + " * @param {gdjs.RuntimeObject3D} object", + " * @return {number}", + " */", + " getTint(object) {", + " return object.__tint3DExtension_tint === undefined ? 0xffffff : object.__tint3DExtension_tint;", + " }", + "", + " /**", + " * @param {gdjs.RuntimeObject3D} object", + " * @param {number} color", + " */", + " createTint(object, color) {", + " let tint;", + " if (this.recycledTints.length > 0) {", + " tint = this.recycledTints.pop();", + " tint.usageCount = 0;", + " }", + " else {", + " const materials = new Map();", + " for (const [originalId, material] of this.originalTint.materials) {", + " const tintedMaterial = material.clone();", + " tintedMaterial.originalId = originalId;", + " materials.set(originalId, tintedMaterial);", + " }", + " tint = new Tint(materials);", + " }", + " tint.setColor(color, this.originalTint);", + " return tint;", + " }", + "", + " dispose() {", + " for (const tint of this.tints.values()) {", + " tint.dispose();", + " }", + " this.tints.clear();", + " for (const tint of this.recycledTints) {", + " tint.dispose();", + " }", + " this.recycledTints.length = 0;", + " }", + "}", + "", + "gdjs.registerObjectDeletedFromSceneCallback(function (runtimeScene, deletedObject) {", + " const extension = runtimeScene.__tween3DExtension;", + " if (!extension) {", + " return;", + " }", + " const managers = extension.tintManagers;", + " const manager = managers.get(deletedObject.getName());", + " if (manager) {", + " manager.applyTint(deletedObject, 0xffffff);", + " }", + "});", + "", + "", + "/**", + " * @param {number} r", + " * @param {number} g", + " * @param {number} b", + " * @return {number[]}", + " */", + "const rgbToHsl = (r, g, b) => {", + " r /= 255;", + " g /= 255;", + " b /= 255;", + " let v = Math.max(r, g, b),", + " c = v - Math.min(r, g, b),", + " f = 1 - Math.abs(v + v - c - 1);", + " let h =", + " c &&", + " (v === r ? (g - b) / c : v === g ? 2 + (b - r) / c : 4 + (r - g) / c);", + " return [", + " Math.round(60 * (h < 0 ? h + 6 : h)),", + " Math.round((f ? c / f : 0) * 100),", + " Math.round(((v + v - c) / 2) * 100),", + " ];", + "};", + "", + "/**", + " * @param {number} h", + " * @param {number} s", + " * @param {number} l", + " * @return {number[]}", + " */", + "const hslToRgb = (h, s, l) => {", + " h = h %= 360;", + " if (h < 0) {", + " h += 360;", + " }", + " s = s / 100;", + " l = l / 100;", + " const a = s * Math.min(l, 1 - l);", + " const f = (n = 0, k = (n + h / 30) % 12) =>", + " l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);", + " return [", + " Math.round(f(0) * 255),", + " Math.round(f(8) * 255),", + " Math.round(f(4) * 255),", + " ];", + "};", + "", + "gdjs.__tween3DExtension = {", + " TintManager,", + " rgbToHsl,", + " hslToRgb,", + "};", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Change the tint of an object. The default color is white.", + "fullName": "Tint color", + "functionType": "Action", + "group": "Color", + "name": "SetTint", + "sentence": "Change tint of _PARAM1_ to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const color = gdjs.rgbOrHexStringToNumber(eventsFunctionContext.getArgument(\"Color\") || \"255; 255;255\");", + "", + "if (!runtimeScene.__tween3DExtension) {", + " runtimeScene.__tween3DExtension = {", + " tintManagers: new Map()", + " };", + "}", + "const { tintManagers } = runtimeScene.__tween3DExtension;", + "for (const object of objects) {", + " let manager = tintManagers.get(object.getName());", + " if (!manager) {", + " const { TintManager } = gdjs.__tween3DExtension;", + " manager = new TintManager(object);", + " tintManagers.set(object.getName(), manager);", + " }", + " manager.applyTint(object, color);", + "}", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "Object capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Color", + "name": "Color", + "type": "color" + } + ], + "objectGroups": [] + }, + { + "description": "Change the tint of an object.", + "fullName": "Tint color (by number)", + "functionType": "Action", + "group": "Color", + "name": "SetTintNumber", + "private": true, + "sentence": "Change tint of _PARAM1_ to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const color = eventsFunctionContext.getArgument(\"Color\");", + "", + "if (!runtimeScene.__tween3DExtension) {", + " runtimeScene.__tween3DExtension = {", + " tintManagers: new Map()", + " };", + "}", + "const { tintManagers } = runtimeScene.__tween3DExtension;", + "for (const object of objects) {", + " let manager = tintManagers.get(object.getName());", + " if (!manager) {", + " const { TintManager } = gdjs.__tween3DExtension;", + " manager = new TintManager(object);", + " tintManagers.set(object.getName(), manager);", + " }", + " manager.applyTint(object, color);", + "}", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "Object capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Color", + "name": "Color", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Blend two RGB colors by applying a weighted mean.", + "fullName": "Blend RGB colors", + "functionType": "Expression", + "name": "RgbMean", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { rgbToHsl, hslToRgb } = gdjs.__tween3DExtension;", + "", + "const [r1, g1, b1] = gdjs.hexNumberToRGBArray(eventsFunctionContext.getArgument(\"Color1\"));", + "const [r2, g2, b2] = gdjs.hexNumberToRGBArray(eventsFunctionContext.getArgument(\"Color2\"));", + "const ratio = eventsFunctionContext.getArgument(\"Ratio\");", + "", + "const [h1, s1, l1] = rgbToHsl(r1, g1, b1);", + "const [h2, s2, l2] = rgbToHsl(r2, g2, b2);", + "", + "const h = h1 + ratio * (h2 - h1);", + "const s = s1 + ratio * (s2 - s1);", + "const l = l1 + ratio * (l2 - l1);", + "", + "const [r, g, b] = hslToRgb(h, s, l);", + "", + "eventsFunctionContext.returnValue = gdjs.rgbToHexNumber(r, g, b);", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "First RGB color", + "name": "Color1", + "type": "expression" + }, + { + "description": "Second RGB color", + "name": "Color2", + "type": "expression" + }, + { + "description": "Ratio", + "longDescription": "Range: 0 to 1, where 0 gives the first color and 1 gives the second color", + "name": "Ratio", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "fullName": "RGB to number", + "functionType": "Expression", + "name": "RgbToNumber", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "eventsFunctionContext.returnValue = gdjs.rgbOrHexStringToNumber(eventsFunctionContext.getArgument(\"Color\"));", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Color", + "name": "Color", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onSceneUnloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (!runtimeScene.__tween3DExtension) {", + " return;", + "}", + "const { tintManagers } = runtimeScene.__tween3DExtension;", + "", + "for (const manager of tintManagers.values()) {", + " manager.dispose();", + "}", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "the stretch on Z axis. The object volume is conserved. A stretch of 2 makes the object appear 2 times longer on Z in proportion to the dimensions on X and Y.", + "fullName": "Stretch on Z axis", + "functionType": "ExpressionAndCondition", + "name": "StretchZ", + "private": true, + "sentence": "the stretch on Z axis of _PARAM1_ (it no longer does anything, use the one from 3D tween behavior instead)", + "events": [], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Scalable capacity", + "name": "Scale", + "supplementaryInformation": "ScalableCapability::ScalableBehavior", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Stretch on Z axis", + "functionType": "ActionWithOperator", + "getterName": "StretchZ", + "name": "SetStretchZ", + "private": true, + "sentence": "", + "events": [], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Scalable capacity", + "name": "Scale", + "supplementaryInformation": "ScalableCapability::ScalableBehavior", + "type": "behavior" + }, + { + "description": "Stretch ratio", + "name": "StretchRatio", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the stretch on Y axis. The object volume is conserved. A stretch of 2 makes the object appear 2 times longer on Y in proportion to the dimensions on X and Z.", + "fullName": "Stretch on Y axis", + "functionType": "ExpressionAndCondition", + "name": "StretchY", + "private": true, + "sentence": "the stretch on Y axis of _PARAM1_ (it no longer does anything, use the one from 3D tween behavior instead)", + "events": [], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Scalable capacity", + "name": "Scale", + "supplementaryInformation": "ScalableCapability::ScalableBehavior", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Stretch on Z axis", + "functionType": "ActionWithOperator", + "getterName": "StretchY", + "name": "SetStretchY", + "private": true, + "sentence": "", + "events": [], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Scalable capacity", + "name": "Scale", + "supplementaryInformation": "ScalableCapability::ScalableBehavior", + "type": "behavior" + }, + { + "description": "Stretch ratio", + "name": "StretchRatio", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the scale volume.", + "fullName": "Scale volume", + "functionType": "ExpressionAndCondition", + "name": "ScaleVolume", + "private": true, + "sentence": "the scale volume of _PARAM1_ (it no longer does anything, use the one from 3D tween behavior instead)", + "events": [], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Scalable capacity", + "name": "Scale", + "supplementaryInformation": "ScalableCapability::ScalableBehavior", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Stretch on Z axis", + "functionType": "ActionWithOperator", + "getterName": "ScaleVolume", + "name": "SetScaleVolume", + "private": true, + "sentence": "", + "events": [], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Scalable capacity", + "name": "Scale", + "supplementaryInformation": "ScalableCapability::ScalableBehavior", + "type": "behavior" + }, + { + "description": "Stretch ratio", + "name": "StretchRatio", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Exponentially tween bewteen 2 values according to an easing function.", + "fullName": "Ease exponentially", + "functionType": "Expression", + "name": "EaseExp", + "private": true, + "sentence": "the exponent-tweened value of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "exp(Tween::Ease(Easing, log(InitialValue), log(TargetedValue), Weighting))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Easing", + "name": "Easing", + "type": "string" + }, + { + "description": "Initial value", + "name": "InitialValue", + "type": "expression" + }, + { + "description": "Targeted value", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Weighting", + "name": "Weighting", + "type": "expression" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onFirstSceneLoaded" + }, + { + "functionName": "DefineHelperClasses" + }, + { + "folderName": "Color", + "children": [ + { + "functionName": "SetTint" + }, + { + "functionName": "SetTintNumber" + } + ] + }, + { + "functionName": "RgbMean" + }, + { + "functionName": "RgbToNumber" + }, + { + "functionName": "onSceneUnloading" + }, + { + "functionName": "StretchZ" + }, + { + "functionName": "SetStretchZ" + }, + { + "functionName": "StretchY" + }, + { + "functionName": "SetStretchY" + }, + { + "functionName": "ScaleVolume" + }, + { + "functionName": "SetScaleVolume" + }, + { + "functionName": "EaseExp" + } + ] + }, + "eventsBasedBehaviors": [ + { + "description": "Smoothly squash, strentch and tint 3D models.", + "fullName": "Advanced 3D tween", + "helpPath": "", + "iconUrl": "", + "name": "Tween3D", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the stretch on Z axis. The object volume is conserved. A stretch of 2 makes the object appear 2 times longer on Z in proportion to the dimensions on X and Y.", + "fullName": "Stretch on Z axis", + "functionType": "ExpressionAndCondition", + "group": "Size", + "name": "StretchZ", + "sentence": "the stretch on Z axis", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Object3D::ScaleZ() / sqrt(Object.Scale::X() * Object.Scale::Y())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Stretch on Z axis", + "functionType": "ActionWithOperator", + "getterName": "StretchZ", + "group": "Size", + "name": "SetStretchZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CompareArgumentAsNumber" + }, + "parameters": [ + "\"Value\"", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "ModVarScene" + }, + "parameters": [ + "_Tween3D.Volume", + "=", + "Object.Behavior::ScaleVolume()" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetScaleZ" + }, + "parameters": [ + "Object", + "Object3D", + "=", + "pow(Variable(_Tween3D.Volume) * Value * Value, 1/3)" + ] + }, + { + "type": { + "value": "ModVarScene" + }, + "parameters": [ + "_Tween3D.SurfaceRatio", + "=", + "sqrt((Variable(_Tween3D.Volume) / Object.Object3D::ScaleZ()) / (Object.Scale::X() * Object.Scale::Y()))" + ] + }, + { + "type": { + "value": "ScalableCapability::ScalableBehavior::SetX" + }, + "parameters": [ + "Object", + "Scale", + "*", + "Variable(_Tween3D.SurfaceRatio)" + ] + }, + { + "type": { + "value": "ScalableCapability::ScalableBehavior::SetY" + }, + "parameters": [ + "Object", + "Scale", + "*", + "Variable(_Tween3D.SurfaceRatio)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Scalable capacity", + "name": "Scale", + "supplementaryInformation": "ScalableCapability::ScalableBehavior", + "type": "behavior" + }, + { + "description": "Stretch ratio", + "name": "StretchRatio", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the stretch on Y axis. The object volume is conserved. A stretch of 2 makes the object appear 2 times longer on Y in proportion to the dimensions on X and Z.", + "fullName": "Stretch on Y axis", + "functionType": "ExpressionAndCondition", + "group": "Size", + "name": "StretchY", + "sentence": "the stretch on Y axis", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Scale::Y() / sqrt(Object.Scale::X() * Object.Object3D::ScaleZ())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Stretch on Z axis", + "functionType": "ActionWithOperator", + "getterName": "StretchY", + "group": "Size", + "name": "SetStretchY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CompareArgumentAsNumber" + }, + "parameters": [ + "\"Value\"", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "ModVarScene" + }, + "parameters": [ + "_Tween3D.Volume", + "=", + "Object.Behavior::ScaleVolume()" + ] + }, + { + "type": { + "value": "ScalableCapability::ScalableBehavior::SetY" + }, + "parameters": [ + "Object", + "Scale", + "=", + "pow(Variable(_Tween3D.Volume) * Value * Value, 1/3)" + ] + }, + { + "type": { + "value": "ModVarScene" + }, + "parameters": [ + "_Tween3D.SurfaceRatio", + "=", + "sqrt((Variable(_Tween3D.Volume) / Object.Object3D::ScaleZ()) / (Object.Scale::X() * Object.Scale::Y()))" + ] + }, + { + "type": { + "value": "ScalableCapability::ScalableBehavior::SetX" + }, + "parameters": [ + "Object", + "Scale", + "*", + "Variable(_Tween3D.SurfaceRatio)" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetScaleZ" + }, + "parameters": [ + "Object", + "Object3D", + "*", + "Variable(_Tween3D.SurfaceRatio)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Scalable capacity", + "name": "Scale", + "supplementaryInformation": "ScalableCapability::ScalableBehavior", + "type": "behavior" + }, + { + "description": "Stretch ratio", + "name": "StretchRatio", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the scale volume.", + "fullName": "Scale volume", + "functionType": "ExpressionAndCondition", + "group": "Size", + "name": "ScaleVolume", + "sentence": "the scale volume", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Scale::X() * Object.Scale::Y() * Object.Object3D::ScaleZ()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Stretch on Z axis", + "functionType": "ActionWithOperator", + "getterName": "ScaleVolume", + "group": "Size", + "name": "SetScaleVolume", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CompareArgumentAsNumber" + }, + "parameters": [ + "\"Value\"", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "ModVarScene" + }, + "parameters": [ + "_Tween3D.Ratio", + "=", + "pow(Value / Object.Behavior::ScaleVolume(), 1 / 3)" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetScaleZ" + }, + "parameters": [ + "Object", + "Object3D", + "*", + "Variable(_Tween3D.Ratio)" + ] + }, + { + "type": { + "value": "ScalableCapability::ScalableBehavior::SetY" + }, + "parameters": [ + "Object", + "Scale", + "*", + "Variable(_Tween3D.Ratio)" + ] + }, + { + "type": { + "value": "ScalableCapability::ScalableBehavior::SetX" + }, + "parameters": [ + "Object", + "Scale", + "*", + "Variable(_Tween3D.Ratio)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Scalable capacity", + "name": "Scale", + "supplementaryInformation": "ScalableCapability::ScalableBehavior", + "type": "behavior" + }, + { + "description": "Stretch ratio", + "name": "StretchRatio", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the color of a 3d object.", + "fullName": "Color of a 3d object", + "functionType": "Expression", + "group": "Color", + "name": "Tint", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (!runtimeScene.__tween3DExtension) {", + " runtimeScene.__tween3DExtension = {", + " tintManagers: new Map()", + " };", + "}", + "const { tintManagers } = runtimeScene.__tween3DExtension;", + "if (objects.length === 0) {", + " eventsFunctionContext.returnValue = 0xffffff;", + "}", + "else {", + " const object = objects[0];", + " let manager = tintManagers.get(object.getName());", + " if (!manager) {", + " const { TintManager } = gdjs.__tween3DExtension;", + " manager = new TintManager(object);", + " tintManagers.set(object.getName(), manager);", + " }", + " eventsFunctionContext.returnValue = manager.getTint(object);", + "}", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Tweens an object stretch Z from its current value to a new one. The stretch can never be 0 or less.", + "fullName": "Tween stretch Z", + "functionType": "Action", + "group": "Size", + "name": "TweenStretchZ", + "sentence": "Tween the stretch Z of _PARAM0_ to _PARAM3_ over _PARAM4_ seconds with easing _PARAM5_ as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::StartTween" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "TargetedValue", + "Duration", + "Easing", + "\"StretchZ\"", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Stretch on Z axis", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + } + ], + "objectGroups": [] + }, + { + "description": "Tweens an object stretch Y from its current value to a new one. The stretch can never be 0 or less.", + "fullName": "Tween stretch Y", + "functionType": "Action", + "group": "Size", + "name": "TweenStretchY", + "sentence": "Tween the stretch Y of _PARAM0_ to _PARAM3_ over _PARAM4_ seconds with easing _PARAM5_ as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::StartTween" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "TargetedValue", + "Duration", + "Easing", + "\"StretchY\"", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Stretch on Y axis", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + } + ], + "objectGroups": [] + }, + { + "description": "Tweens an object scale volume from its current value to a new one. The scale volume can never be 0 or less.", + "fullName": "Tween scale volume", + "functionType": "Action", + "group": "Size", + "name": "TweenScaleVolume", + "sentence": "Tween the scale volume of _PARAM0_ to _PARAM3_ over _PARAM4_ seconds with easing _PARAM5_ as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::StartTween" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "TargetedValue", + "Duration", + "Easing", + "\"ScaleVolume\"", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Scale volume", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + } + ], + "objectGroups": [] + }, + { + "description": "Tweens the object color from its current value to a new one.", + "fullName": "Tween object color", + "functionType": "Action", + "group": "Color", + "name": "TweenTint", + "sentence": "Tween the color of _PARAM0_ to _PARAM3_ over _PARAM4_ seconds with easing _PARAM5_ as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::StartTween" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "Tween3D::RgbToNumber(TargetedValue)", + "Duration", + "Easing", + "\"Tint\"", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Color", + "name": "TargetedValue", + "type": "color" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + } + ], + "objectGroups": [] + }, + { + "description": "Tweens an object Z position from its current value to a new one.", + "fullName": "Tween Z position (deprecated)", + "functionType": "Action", + "group": "Position", + "name": "TweenPositionZ", + "private": true, + "sentence": "Tween the Z position of object _PARAM0_ to _PARAM3_ over _PARAM4_ seconds with easing _PARAM5_ as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween::AddObjectPositionZTween2" + }, + "parameters": [ + "Object", + "Tween", + "Object3D", + "TweenIdentifier", + "TargetedValue", + "Easing", + "Duration", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Rotation X in degree", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + } + ], + "objectGroups": [] + }, + { + "description": "Tweens an object depth from its current value to a new one.", + "fullName": "Tween depth (Z size) (deprecated)", + "functionType": "Action", + "group": "Position", + "name": "TweenDepth", + "private": true, + "sentence": "Tween the depth of object _PARAM0_ to _PARAM3_ over _PARAM4_ seconds with easing _PARAM5_ as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween::AddObjectDepthTween2" + }, + "parameters": [ + "Object", + "Tween", + "Object3D", + "TweenIdentifier", + "TargetedValue", + "Easing", + "Duration", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Rotation X in degree", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + } + ], + "objectGroups": [] + }, + { + "description": "Tweens an object X rotation from its current value to a new one.", + "fullName": "Tween X rotation (deprecated)", + "functionType": "Action", + "group": "Angle", + "name": "TweenRotationX", + "private": true, + "sentence": "Tween the X rotation of object _PARAM0_ to _PARAM3_ over _PARAM4_ seconds with easing _PARAM5_ as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween::TweenBehavior::AddObjectRotationXTween" + }, + "parameters": [ + "Object", + "Tween", + "Object3D", + "TweenIdentifier", + "TargetedValue", + "Easing", + "Duration", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Rotation X in degree", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + } + ], + "objectGroups": [] + }, + { + "description": "Tweens an object Y rotation from its current value to a new one.", + "fullName": "Tween rotation Y (deprecated)", + "functionType": "Action", + "group": "Angle", + "name": "TweenRotationY", + "private": true, + "sentence": "Tween the Y rotation of object _PARAM0_ to _PARAM3_ over _PARAM4_ seconds with easing _PARAM5_ as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween::TweenBehavior::AddObjectRotationYTween" + }, + "parameters": [ + "Object", + "Tween", + "Object3D", + "TweenIdentifier", + "TargetedValue", + "Easing", + "Duration", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Rotation Y in degree", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ModVarScene" + }, + "parameters": [ + "_Tween3D.Index", + "=", + "0" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "Object.VariableChildCount(_Tween3D.Tweens)", + "conditions": [], + "actions": [ + { + "type": { + "value": "ModVarSceneTxt" + }, + "parameters": [ + "_Tween3D.Identifier", + "=", + "Object.Behavior::TweenIdentifier(Variable(_Tween3D.Index))" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::IsPlaying" + }, + "parameters": [ + "Object", + "Behavior", + "VariableString(_Tween3D.Identifier)", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetTime" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "min(Object.Behavior::Time(VariableString(_Tween3D.Identifier)) + TimeDelta() * LayerTimeScale(Object.Layer()), Object.Behavior::Duration(VariableString(_Tween3D.Identifier)))", + "VariableString(_Tween3D.Identifier)", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Type" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "\"StretchZ\"", + "VariableString(_Tween3D.Identifier)", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetStretchZ" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Object.Behavior::ExponentTweenedValue(VariableString(_Tween3D.Identifier))", + "Object3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Type" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "\"StretchY\"", + "VariableString(_Tween3D.Identifier)", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetStretchY" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Object.Behavior::ExponentTweenedValue(VariableString(_Tween3D.Identifier))", + "Object3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Type" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "\"ScaleVolume\"", + "VariableString(_Tween3D.Identifier)", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetScaleVolume" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Object.Behavior::ExponentTweenedValue(VariableString(_Tween3D.Identifier))", + "Object3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Type" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "\"Tint\"", + "VariableString(_Tween3D.Identifier)", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::SetTintNumber" + }, + "parameters": [ + "Object", + "Object", + "Object3D", + "Object.Behavior::TweenedColor(VariableString(_Tween3D.Identifier))", + "" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ModVarScene" + }, + "parameters": [ + "_Tween3D.Index", + "+", + "1" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the tweened value", + "fullName": "Tweened value", + "functionType": "ExpressionAndCondition", + "name": "TweenedValue", + "private": true, + "sentence": "the value of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Tween::Ease(Object.Behavior::Easing(VariableString(_Tween3D.Identifier)), Object.Behavior::InitialValue(VariableString(_Tween3D.Identifier)), Object.Behavior::TargetedValue(VariableString(_Tween3D.Identifier)), Object.Behavior::Progress(VariableString(_Tween3D.Identifier)))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "the tweened color", + "fullName": "Tweened color", + "functionType": "ExpressionAndCondition", + "name": "TweenedColor", + "private": true, + "sentence": "the color value of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Tween3D::RgbMean(Object.Behavior::InitialValue(VariableString(_Tween3D.Identifier)), Object.Behavior::TargetedValue(VariableString(_Tween3D.Identifier)), \nTween::Ease(Object.Behavior::Easing(VariableString(_Tween3D.Identifier)), 0, 1, Object.Behavior::Progress(VariableString(_Tween3D.Identifier))))\n\n" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "the exponent-tweened value", + "fullName": "Tweened value (exponent)", + "functionType": "ExpressionAndCondition", + "name": "ExponentTweenedValue", + "private": true, + "sentence": "the exponent-tweened value of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Tween3D::EaseExp(Object.Behavior::Easing(VariableString(_Tween3D.Identifier)), Object.Behavior::InitialValue(VariableString(_Tween3D.Identifier)), Object.Behavior::TargetedValue(VariableString(_Tween3D.Identifier)), Object.Behavior::Progress(VariableString(_Tween3D.Identifier)))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "fullName": "Start tween", + "functionType": "Action", + "name": "StartTween", + "private": true, + "sentence": "Tween the _PARAM6_ of object _PARAM0_ to _PARAM3_ over _PARAM4_ seconds with _PARAM5_ easing as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "Tween3D::Tween3D::Exists" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::InvalidateTweenIdentifiers" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetTime" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "0", + "TweenIdentifier", + "" + ] + }, + { + "type": { + "value": "Tween3D::Tween3D::SetType" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Type", + "TweenIdentifier", + "" + ] + }, + { + "type": { + "value": "Tween3D::Tween3D::SetDuration" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Duration", + "TweenIdentifier", + "" + ] + }, + { + "type": { + "value": "Tween3D::Tween3D::SetEasing" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Easing", + "TweenIdentifier", + "" + ] + }, + { + "type": { + "value": "Tween3D::Tween3D::SetTargetedValue" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "TargetedValue", + "TweenIdentifier", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Type", + "=", + "\"StretchZ\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetInitialValue" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Object.Behavior::StretchZ()", + "TweenIdentifier", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Type", + "=", + "\"StretchY\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetInitialValue" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Object.Behavior::StretchY()", + "TweenIdentifier", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Type", + "=", + "\"ScaleVolume\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetInitialValue" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Object.Behavior::ScaleVolume()", + "TweenIdentifier", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Type", + "=", + "\"Tint\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetInitialValue" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Object.Behavior::Tint()", + "TweenIdentifier", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Stretch", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + }, + { + "description": "Type", + "name": "Type", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "the tween type", + "fullName": "Type", + "functionType": "ExpressionAndCondition", + "name": "Type", + "private": true, + "sentence": "the type of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "Object.VariableString(_Tween3D.Tweens[TweenIdentifier].Type)" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Type", + "name": "SetType", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ModVarObjetTxt" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens[TweenIdentifier].Type", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "the elapsed time", + "fullName": "Time", + "functionType": "ExpressionAndCondition", + "name": "Time", + "private": true, + "sentence": "the elapsed time of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Variable(_Tween3D.Tweens[TweenIdentifier].Time)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Time", + "name": "SetTime", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ModVarObjet" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens[TweenIdentifier].Time", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "the tween duration", + "fullName": "Duration", + "functionType": "ExpressionAndCondition", + "name": "Duration", + "private": true, + "sentence": "the duration of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Variable(_Tween3D.Tweens[TweenIdentifier].Duration)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Duration", + "name": "SetDuration", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ModVarObjet" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens[TweenIdentifier].Duration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "the tween easing", + "fullName": "Easing", + "functionType": "ExpressionAndCondition", + "name": "Easing", + "private": true, + "sentence": "the easing of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "Object.VariableString(_Tween3D.Tweens[TweenIdentifier].Easing)" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Easing", + "name": "SetEasing", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ModVarObjetTxt" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens[TweenIdentifier].Easing", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "the tween initial value", + "fullName": "Initial value", + "functionType": "ExpressionAndCondition", + "name": "InitialValue", + "private": true, + "sentence": "the initial value of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Variable(_Tween3D.Tweens[TweenIdentifier].InitialValue)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "InitialValue", + "name": "SetInitialValue", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ModVarObjet" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens[TweenIdentifier].InitialValue", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "the tween targeted value", + "fullName": "Targeted value", + "functionType": "ExpressionAndCondition", + "name": "TargetedValue", + "private": true, + "sentence": "the targeted value of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Variable(_Tween3D.Tweens[TweenIdentifier].TargetedValue)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "TargetedValue", + "name": "SetTargetedValue", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ModVarObjet" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens[TweenIdentifier].TargetedValue", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the tween is paused", + "fullName": "Paused", + "functionType": "Condition", + "name": "IsPaused", + "private": true, + "sentence": "The tween _PARAM2_ of _PARAM0_ is paused", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ObjectVariableAsBoolean" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens[TweenIdentifier].IsPaused", + "True" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "Change if the tween is paused", + "fullName": "Paused", + "functionType": "Action", + "name": "SetPaused", + "private": true, + "sentence": "The tween _PARAM2_ of _PARAM0_ is paused: _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsPaused", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetObjectVariableAsBoolean" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens[TweenIdentifier].IsPaused", + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "IsPaused", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetObjectVariableAsBoolean" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens[TweenIdentifier].IsPaused", + "False" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Paused", + "name": "IsPaused", + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Stop the running tween animation.", + "fullName": "Stop a tween", + "functionType": "Action", + "name": "StopTween", + "sentence": "Stop the tween _PARAM2_ on _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Exists" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetTime" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Object.Behavior::Duration(TweenIdentifier)", + "TweenIdentifier", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "JumpToEnd", + "True", + "" + ] + } + ], + "actions": [] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Jump to end", + "name": "JumpToEnd", + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Pause the running tween animation.", + "fullName": "Pause a tween", + "functionType": "Action", + "name": "PauseTween", + "sentence": "Pause the tween _PARAM2_ on _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Exists" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetPaused" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "yes", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "Resume the tween animation.", + "fullName": "Resume a tween", + "functionType": "Action", + "name": "ResumeTween", + "sentence": "Resume the tween _PARAM2_ on _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Exists" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetPaused" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "no", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "Remove the tween animation from the object.", + "fullName": "Remove a tween", + "functionType": "Action", + "name": "RemoveTween", + "sentence": "Remove the tween _PARAM2_ from _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Exists" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ObjectVariableRemoveChild" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens", + "TweenIdentifier" + ] + }, + { + "type": { + "value": "Tween3D::Tween3D::InvalidateTweenIdentifiers" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the tween animation exists.", + "fullName": "Tween exists", + "functionType": "Condition", + "name": "Exists", + "sentence": "Tween _PARAM2_ on _PARAM0_ exists", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ObjectVariableChildExists" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens", + "TweenIdentifier" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the tween animation is currently playing.", + "fullName": "Tween is playing", + "functionType": "Condition", + "name": "IsPlaying", + "sentence": "Tween _PARAM2_ on _PARAM0_ is playing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Exists" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "Tween3D::Tween3D::HasFinished" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "Tween3D::Tween3D::IsPaused" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the tween animation has finished playing.", + "fullName": "Tween finished playing", + "functionType": "Condition", + "name": "HasFinished", + "sentence": "Tween _PARAM2_ on _PARAM0_ has finished playing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Progress" + }, + "parameters": [ + "Object", + "Behavior", + ">=", + "1", + "TweenIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "the progress of a tween (between 0.0 and 1.0).", + "fullName": "Progress of a tween", + "functionType": "ExpressionAndCondition", + "name": "Progress", + "sentence": "the progress of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Exists" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Behavior::Time(TweenIdentifier) / Object.Behavior::Duration(TweenIdentifier)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "Return the tween identifier for a given index.", + "fullName": "Tween identifier", + "functionType": "StringExpression", + "name": "TweenIdentifier", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];", + "", + "const childIndex = eventsFunctionContext.getArgument(\"ChildIndex\");", + "", + "object.__Tween3D = object.__Tween3D || {};", + "object.__Tween3D.variableIdentifiers =", + " object.__Tween3D.variableIdentifiers", + " || Object.keys(object.getVariables().get(\"_Tween3D\").getChild(\"Tweens\").getAllChildren());", + "", + "eventsFunctionContext.returnValue = object.__Tween3D.variableIdentifiers[childIndex];", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Child index", + "name": "ChildIndex", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Invalidate tween identifiers.", + "fullName": "Invalidate tween identifiers", + "functionType": "Action", + "name": "InvalidateTweenIdentifiers", + "private": true, + "sentence": "Invalidate tween identifiers of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];", + "", + "object.__Tween3D = object.__Tween3D || {};", + "object.__Tween3D.variableIdentifiers = null;", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "folderName": "Size", + "children": [ + { + "functionName": "StretchZ" + }, + { + "functionName": "SetStretchZ" + }, + { + "functionName": "StretchY" + }, + { + "functionName": "SetStretchY" + }, + { + "functionName": "ScaleVolume" + }, + { + "functionName": "SetScaleVolume" + }, + { + "functionName": "TweenStretchZ" + }, + { + "functionName": "TweenStretchY" + }, + { + "functionName": "TweenScaleVolume" + } + ] + }, + { + "folderName": "Color", + "children": [ + { + "functionName": "Tint" + }, + { + "functionName": "TweenTint" + } + ] + }, + { + "folderName": "Position", + "children": [ + { + "functionName": "TweenPositionZ" + }, + { + "functionName": "TweenDepth" + } + ] + }, + { + "folderName": "Angle", + "children": [ + { + "functionName": "TweenRotationX" + }, + { + "functionName": "TweenRotationY" + } + ] + }, + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "TweenedValue" + }, + { + "functionName": "TweenedColor" + }, + { + "functionName": "ExponentTweenedValue" + }, + { + "functionName": "StartTween" + }, + { + "functionName": "Type" + }, + { + "functionName": "SetType" + }, + { + "functionName": "Time" + }, + { + "functionName": "SetTime" + }, + { + "functionName": "Duration" + }, + { + "functionName": "SetDuration" + }, + { + "functionName": "Easing" + }, + { + "functionName": "SetEasing" + }, + { + "functionName": "InitialValue" + }, + { + "functionName": "SetInitialValue" + }, + { + "functionName": "TargetedValue" + }, + { + "functionName": "SetTargetedValue" + }, + { + "functionName": "IsPaused" + }, + { + "functionName": "SetPaused" + }, + { + "functionName": "StopTween" + }, + { + "functionName": "PauseTween" + }, + { + "functionName": "ResumeTween" + }, + { + "functionName": "RemoveTween" + }, + { + "functionName": "Exists" + }, + { + "functionName": "IsPlaying" + }, + { + "functionName": "HasFinished" + }, + { + "functionName": "Progress" + }, + { + "functionName": "TweenIdentifier" + }, + { + "functionName": "InvalidateTweenIdentifiers" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D capability", + "extraInformation": [ + "Scene3D::Base3DBehavior" + ], + "choices": [], + "name": "Object3D" + }, + { + "value": "", + "type": "Behavior", + "label": "Scalable capacity", + "extraInformation": [ + "ScalableCapability::ScalableBehavior" + ], + "choices": [], + "name": "Scale" + }, + { + "value": "", + "type": "Behavior", + "label": "Tween", + "extraInformation": [ + "Tween::TweenBehavior" + ], + "choices": [], + "name": "Tween" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Object3D" + }, + { + "propertyName": "Scale" + }, + { + "propertyName": "Tween" + } + ] + } + }, + { + "description": "Squash and stretch a 3D model in loop.", + "fullName": "3D wobble", + "helpPath": "", + "iconUrl": "", + "name": "Wobble", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween3D::Wobble::UpdateScaleVolume" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + }, + { + "type": { + "value": "Tween3D::Wobble::UpdateStretch" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Tween", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "ScaleMinTweenTime", + "<", + "ScaleMinTweenDuration" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMinTweenTime", + "+", + "Object.Behavior::TimeDelta()" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMin", + "=", + "Tween3D::EaseExp(ScaleMinTweenEasing, ScaleMinTweenInitialValue, ScaleMinTweenTarget, ScaleMinTweenTime / ScaleMinTweenDuration)" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "ScaleMinTweenTime", + ">=", + "ScaleMinTweenDuration" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMin", + "=", + "ScaleMinTweenTarget" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "ScaleMaxTweenTime", + "<", + "ScaleMaxTweenDuration" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMaxTweenTime", + "+", + "Object.Behavior::TimeDelta()" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMax", + "=", + "Tween3D::EaseExp(ScaleMaxTweenEasing, ScaleMaxTweenInitialValue, ScaleMaxTweenTarget, ScaleMaxTweenTime / ScaleMaxTweenDuration)" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "ScaleMaxTweenTime", + ">=", + "ScaleMaxTweenDuration" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMin", + "=", + "ScaleMinTweenTarget" + ] + } + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Wobble", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween3D::Wobble::UpdateScaleVolume" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + }, + { + "type": { + "value": "Tween3D::Wobble::UpdateStretch" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Angle", + "+", + "mod(Object.Behavior::TimeDelta() * 2 * Pi()\n / PeriodDuration, 2 * Pi())" + ] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the minimum scale volume of the object.", + "fullName": "Minimum scale volume", + "functionType": "ExpressionAndCondition", + "group": "3D wobble scale configuration", + "name": "ScaleMin", + "sentence": "the minimum scale volume", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ScaleMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ScaleMin", + "group": "3D wobble scale configuration", + "name": "SetScaleMin", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMin", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the maximum scale volume of the object.", + "fullName": "Maximum scale volume", + "functionType": "ExpressionAndCondition", + "group": "3D wobble scale configuration", + "name": "ScaleMax", + "sentence": "the maximum scale volume", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ScaleMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ScaleMax", + "group": "3D wobble scale configuration", + "name": "SetScaleMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Tweens the minimum scale volume from its current value to a new one. The scale volume can never be 0 or less.", + "fullName": "Tween minimum scale volume", + "functionType": "Action", + "group": "3D wobble scale configuration", + "name": "TweenScaleMin", + "sentence": "Tween the minimum scale volume of object _PARAM0_ to _PARAM2_ over _PARAM3_ seconds with _PARAM4_ easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMinTweenInitialValue", + "=", + "Object.Behavior::ScaleMin()" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMinTweenTime", + "=", + "0" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMinTweenTarget", + "=", + "TargetedValue" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMinTweenDuration", + "=", + "Duration" + ] + }, + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "ScaleMinTweenEasing", + "=", + "Easing" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + }, + { + "description": "Minimum scale volume", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + } + ], + "objectGroups": [] + }, + { + "description": "Tweens the maximum scale volume from its current value to a new one. The scale volume can never be 0 or less.", + "fullName": "Tween maximum scale volume", + "functionType": "Action", + "group": "3D wobble scale configuration", + "name": "TweenScaleMax", + "sentence": "Tween the maximum scale volume of object _PARAM0_ to _PARAM2_ over _PARAM3_ seconds with _PARAM4_ easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMaxTweenInitialValue", + "=", + "Object.Behavior::ScaleMax()" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMaxTweenTime", + "=", + "0" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMaxTweenTarget", + "=", + "TargetedValue" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMaxTweenDuration", + "=", + "Duration" + ] + }, + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "ScaleMaxTweenEasing", + "=", + "Easing" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + }, + { + "description": "Maximum scale volume", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + } + ], + "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": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Update scale volume", + "functionType": "Action", + "name": "UpdateScaleVolume", + "private": true, + "sentence": "Update scale volume of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetScaleVolume" + }, + "parameters": [ + "Object", + "Tween3D", + "=", + "exp(log(ScaleMin) + (log(ScaleMax) - log(ScaleMin)) * sin(Angle + ScalePeriodOffset / PeriodDuration * 2 * Pi()))", + "Object3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Update stretch", + "functionType": "Action", + "name": "UpdateStretch", + "private": true, + "sentence": "Update stretch of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Top", + "=", + "\"Z+\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetStretchZ" + }, + "parameters": [ + "Object", + "Tween3D", + "=", + "exp(log(StretchMin) + (log(StretchMax) - log(StretchMin)) * sin(Angle + StretchPeriodOffset / PeriodDuration * 2 * Pi()))", + "Object3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Top", + "=", + "\"Y-\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetStretchY" + }, + "parameters": [ + "Object", + "Tween3D", + "=", + "exp(log(StretchMin) + (log(StretchMax) - log(StretchMin)) * sin(Angle + StretchPeriodOffset / PeriodDuration * 2 * Pi()))", + "Object3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the cycle duration of the object.", + "fullName": "Cycle duration", + "functionType": "ExpressionAndCondition", + "group": "3D wobble configuration", + "name": "PeriodDuration", + "sentence": "the cycle duration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "PeriodDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "PeriodDuration", + "group": "3D wobble configuration", + "name": "SetPeriodDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "PeriodDuration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the scale cycle offset of the object.", + "fullName": "Scale cycle offset", + "functionType": "ExpressionAndCondition", + "group": "3D wobble scale configuration", + "name": "ScalePeriodOffset", + "sentence": "the scale cycle offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ScalePeriodOffset" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ScalePeriodOffset", + "group": "3D wobble scale configuration", + "name": "SetScalePeriodOffset", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScalePeriodOffset", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the minimum stretch of the object.", + "fullName": "Minimum stretch", + "functionType": "ExpressionAndCondition", + "group": "3D wobble stretch configuration", + "name": "StretchMin", + "sentence": "the minimum stretch", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "StretchMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "StretchMin", + "group": "3D wobble stretch configuration", + "name": "SetStretchMin", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StretchMin", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the maximum stretch of the object.", + "fullName": "Maximum stretch", + "functionType": "ExpressionAndCondition", + "group": "3D wobble stretch configuration", + "name": "StretchMax", + "sentence": "the maximum stretch", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "StretchMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "StretchMax", + "group": "3D wobble stretch configuration", + "name": "SetStretchMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StretchMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the stretch cycle offset of the object.", + "fullName": "Stretch cycle offset", + "functionType": "ExpressionAndCondition", + "group": "3D wobble stretch configuration", + "name": "StretchPeriodOffset", + "sentence": "the stretch cycle offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "StretchPeriodOffset" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "StretchPeriodOffset", + "group": "3D wobble stretch configuration", + "name": "SetStretchPeriodOffset", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StretchPeriodOffset", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPreEvents" + }, + { + "folderName": "3D wobble scale configuration", + "children": [ + { + "functionName": "ScaleMin" + }, + { + "functionName": "SetScaleMin" + }, + { + "functionName": "ScaleMax" + }, + { + "functionName": "SetScaleMax" + }, + { + "functionName": "TweenScaleMin" + }, + { + "functionName": "TweenScaleMax" + }, + { + "functionName": "ScalePeriodOffset" + }, + { + "functionName": "SetScalePeriodOffset" + } + ] + }, + { + "functionName": "TimeDelta" + }, + { + "functionName": "UpdateScaleVolume" + }, + { + "functionName": "UpdateStretch" + }, + { + "folderName": "3D wobble configuration", + "children": [ + { + "functionName": "PeriodDuration" + }, + { + "functionName": "SetPeriodDuration" + } + ] + }, + { + "folderName": "3D wobble stretch configuration", + "children": [ + { + "functionName": "StretchMin" + }, + { + "functionName": "SetStretchMin" + }, + { + "functionName": "StretchMax" + }, + { + "functionName": "SetStretchMax" + }, + { + "functionName": "StretchPeriodOffset" + }, + { + "functionName": "SetStretchPeriodOffset" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "0.75", + "type": "Number", + "unit": "Dimensionless", + "label": "Minimum scale volume", + "group": "Scale", + "name": "ScaleMin" + }, + { + "value": "1", + "type": "Number", + "unit": "Dimensionless", + "label": "Maximum scale volume", + "group": "Scale", + "name": "ScaleMax" + }, + { + "value": "1", + "type": "Number", + "unit": "Second", + "label": "Cycle duration", + "name": "PeriodDuration" + }, + { + "value": "0", + "type": "Number", + "unit": "Second", + "label": "Scale cycle offset", + "group": "Scale", + "name": "ScalePeriodOffset" + }, + { + "value": "1", + "type": "Number", + "unit": "Dimensionless", + "label": "Minimum stretch", + "group": "Stretch", + "name": "StretchMin" + }, + { + "value": "1.25", + "type": "Number", + "unit": "Dimensionless", + "label": "Maximum stretch", + "group": "Stretch", + "name": "StretchMax" + }, + { + "value": "-0.25", + "type": "Number", + "unit": "Second", + "label": "Stretch cycle offset", + "group": "Stretch", + "name": "StretchPeriodOffset" + }, + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "", + "hidden": true, + "name": "Angle" + }, + { + "value": "0", + "type": "Number", + "unit": "Dimensionless", + "label": "", + "hidden": true, + "name": "ScaleMinTweenTarget" + }, + { + "value": "0", + "type": "Number", + "unit": "Second", + "label": "", + "hidden": true, + "name": "ScaleMinTweenDuration" + }, + { + "value": "", + "type": "String", + "label": "", + "hidden": true, + "name": "ScaleMinTweenEasing" + }, + { + "value": "0", + "type": "Number", + "unit": "Dimensionless", + "label": "", + "hidden": true, + "name": "ScaleMaxTweenTarget" + }, + { + "value": "0", + "type": "Number", + "unit": "Second", + "label": "", + "hidden": true, + "name": "ScaleMaxTweenDuration" + }, + { + "value": "", + "type": "String", + "label": "", + "hidden": true, + "name": "ScaleMaxTweenEasing" + }, + { + "value": "0", + "type": "Number", + "unit": "Second", + "label": "", + "hidden": true, + "name": "ScaleMinTweenTime" + }, + { + "value": "0", + "type": "Number", + "unit": "Second", + "label": "", + "hidden": true, + "name": "ScaleMaxTweenTime" + }, + { + "value": "0", + "type": "Number", + "label": "", + "hidden": true, + "name": "ScaleMinTweenInitialValue" + }, + { + "value": "0", + "type": "Number", + "label": "", + "hidden": true, + "name": "ScaleMaxTweenInitialValue" + }, + { + "value": "Z+", + "type": "Choice", + "label": "Top orientation", + "choices": [ + { + "label": "Z+", + "value": "Z+" + }, + { + "label": "Y-", + "value": "Y-" + } + ], + "name": "Top" + }, + { + "value": "", + "type": "Behavior", + "label": "3D tween", + "extraInformation": [ + "Tween3D::Tween3D" + ], + "choices": [], + "name": "Tween3D" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "folderName": "Scale", + "children": [ + { + "propertyName": "ScaleMin" + }, + { + "propertyName": "ScaleMax" + }, + { + "propertyName": "ScalePeriodOffset" + } + ] + }, + { + "propertyName": "PeriodDuration" + }, + { + "folderName": "Stretch", + "children": [ + { + "propertyName": "StretchMin" + }, + { + "propertyName": "StretchMax" + }, + { + "propertyName": "StretchPeriodOffset" + } + ] + }, + { + "propertyName": "Angle" + }, + { + "propertyName": "ScaleMinTweenTarget" + }, + { + "propertyName": "ScaleMinTweenDuration" + }, + { + "propertyName": "ScaleMinTweenEasing" + }, + { + "propertyName": "ScaleMaxTweenTarget" + }, + { + "propertyName": "ScaleMaxTweenDuration" + }, + { + "propertyName": "ScaleMaxTweenEasing" + }, + { + "propertyName": "ScaleMinTweenTime" + }, + { + "propertyName": "ScaleMaxTweenTime" + }, + { + "propertyName": "ScaleMinTweenInitialValue" + }, + { + "propertyName": "ScaleMaxTweenInitialValue" + }, + { + "propertyName": "Top" + }, + { + "propertyName": "Tween3D" + } + ] + } + } + ], + "eventsBasedObjects": [] + }, + { + "author": "", + "category": "General", + "dimension": "", + "extensionNamespace": "", + "fullName": "3D sprite", + "gdevelopVersion": ">=5.5.222", + "helpPath": "/objects/sprite/", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLWdob3N0LW91dGxpbmUiIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTIgMkM3LjAzIDIgMyA2LjAzIDMgMTFWMjJMNiAxOUw5IDIyTDEyIDE5TDE1IDIyTDE4IDE5TDIxIDIyVjExQzIxIDYuMDMgMTYuOTcgMiAxMiAyTTE5IDE3LjE3TDE4IDE2LjE3TDE2LjU5IDE3LjU5TDE1IDE5LjE3TDEzLjQxIDE3LjU5TDEyIDE2LjE3TDEwLjU5IDE3LjU5TDkgMTkuMTdMNy40MSAxNy41OUw2IDE2LjE3TDUgMTcuMTdWMTFDNSA3LjE0IDguMTQgNCAxMiA0UzE5IDcuMTQgMTkgMTFWMTcuMTdNMTEgMTBDMTEgMTEuMTEgMTAuMTEgMTIgOSAxMlM3IDExLjExIDcgMTAgNy45IDggOSA4IDExIDguOSAxMSAxME0xNyAxMEMxNyAxMS4xMSAxNi4xMSAxMiAxNSAxMlMxMyAxMS4xMSAxMyAxMCAxMy45IDggMTUgOCAxNyA4LjkgMTcgMTBaIiAvPjwvc3ZnPg==", + "name": "Sprite3D", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/3bd33ec77ed20d3f631c6c92884728b375aa2879174bc9f289c864d0e0383d59_ghost-outline.svg", + "shortDescription": "An animated sprite in 3D.", + "version": "0.1.2", + "description": "The 3D sprite is an object that can be moved and rotated in 3D.", + "origin": { + "identifier": "Sprite3D", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d" + ], + "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": [ + "if (gdjs.__sprite3DExtension) {", + " return;", + "}", + "", + "const vertexColors = [];", + "", + "class Sprite3DRenderer {", + " /** @type {gdjs.CustomRuntimeObject} */", + " object;", + " /** @type {THREE.Mesh} */", + " mesh;", + "", + " /**", + " * @param object {gdjs.CustomRuntimeObject}", + " */", + " constructor(object) {", + " this.object = object;", + "", + " const geometry = new THREE.PlaneGeometry(1, -1);", + " const animationFrame = object.getAnimator().getCurrentFrame();", + " if (animationFrame) {", + " const material = animationFrame.texture;", + " this.mesh = new THREE.Mesh(geometry, material);", + " this.mesh.rotation.order = 'ZYX';", + " object.get3DRendererObject().add(this.mesh);", + " // Ensure a forward compatibility when vertexColors will be set to true", + " // in the engine to allow to tint 3D sprites.", + " vertexColors.length = geometry.attributes.position.count * 3;", + " vertexColors.fill(1);", + " geometry.setAttribute(", + " 'color',", + " new THREE.BufferAttribute(new Float32Array(vertexColors), 3)", + " );", + " }", + " this.updateFrame();", + " object.getAnimator().setOnFrameChangeCallback(() => this.updateFrame());", + " }", + "", + " updateFrame() {", + " const frame = this.object.getAnimator().getCurrentFrame();", + " if (!frame) {", + " return;", + " }", + " const material = frame.texture;", + "", + " const image = material.map.image;", + " const width = image.width;", + " const height = image.height;", + " const origin = frame.origin;", + " this.mesh.position.set(-origin.x + width / 2, -origin.y + height / 2, 0);", + " this.mesh.scale.set(width, height, 1);", + "", + " const center = frame.center;", + " this.object.setRotationCenter(center.x - origin.x, center.y - origin.y);", + "", + " this.mesh.material = material;", + "", + " const hitBoxes = this.object._untransformedHitBoxes;", + " if (frame.hasCustomCollisionMask) {", + " let i = 0;", + " for (let len = frame.customCollisionMask.length; i < len; ++i) {", + " const polygonData = frame.customCollisionMask[i];", + "", + " // Add a polygon, if necessary (Avoid recreating a polygon if it already exists).", + " if (i >= hitBoxes.length) {", + " hitBoxes.push(new gdjs.Polygon());", + " }", + " let j = 0;", + " for (const len2 = polygonData.length; j < len2; ++j) {", + " const pointData = polygonData[j];", + "", + " // Add a point, if necessary (Avoid recreating a point if it already exists).", + " if (j >= hitBoxes[i].vertices.length) {", + " hitBoxes[i].vertices.push([0, 0]);", + " }", + " hitBoxes[i].vertices[j][0] = pointData.x - origin.x;", + " hitBoxes[i].vertices[j][1] = pointData.y - origin.y;", + " }", + " hitBoxes[i].vertices.length = j;", + " }", + " hitBoxes.length = i;", + " }", + " else if (hitBoxes.length === 0) {", + " const hitBox = new gdjs.Polygon();", + " hitBoxes.push(hitBox);", + " const vertices = hitBox.vertices;", + " vertices.push([-origin.x, -origin.y]);", + " vertices.push([-origin.x + width, -origin.y]);", + " vertices.push([-origin.x + width, -origin.y + height]);", + " vertices.push([-origin.x, -origin.y + height]);", + " }", + "", + " const aabb = this.object._unrotatedAABB;", + " aabb.min[0] = -origin.x;", + " aabb.min[1] = -origin.y;", + " aabb.max[0] = -origin.x + width;", + " aabb.max[1] = -origin.y + height;", + "", + " this.object._isUntransformedHitBoxesDirty = false;", + " }", + "}", + "", + "gdjs.__sprite3DExtension = {", + " Sprite3DRenderer", + "};", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "DefineHelperClasses" + } + ] + }, + "eventsBasedBehaviors": [], + "eventsBasedObjects": [ + { + "areaMaxX": 64, + "areaMaxY": 64, + "areaMaxZ": 64, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "defaultName": "", + "description": "An animated sprite in 3D.", + "fullName": "3D sprite", + "helpPath": "", + "iconUrl": "", + "is3D": true, + "isAnimatable": true, + "isUsingLegacyInstancesRenderer": true, + "name": "Sprite3D", + "previewIconUrl": "", + "objects": [], + "objectsFolderStructure": { + "folderName": "__ROOT" + }, + "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": "Sprite3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject} */\r", + "const object = objects[0];\r", + "\r", + "object.__sprite3DExtension = {};\r", + "object.__sprite3DExtension.sprite3DRenderer = new gdjs.__sprite3DExtension.Sprite3DRenderer(object)\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Sprite3D::Sprite3D", + "type": "object" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + } + ] + }, + "propertyDescriptors": [], + "propertiesFolderStructure": { + "folderName": "__ROOT" + }, + "variants": [] + } + ] + }, + { + "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" + } + ] + } + ] + }, + { + "author": "", + "category": "Input", + "extensionNamespace": "", + "gdevelopVersion": ">=5.5.222", + "fullName": "Pinching gesture", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLWdlc3R1cmUtcGluY2giIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTMsOUExLDEgMCAwLDEgMTQsOEExLDEgMCAwLDEgMTUsOVYxMy40N0wxNi4yMSwxMy42TDIxLjE1LDE1LjhDMjEuNjcsMTYuMDQgMjIsMTYuNTYgMjIsMTcuMTRWMjEuNUMyMS45NywyMi4zMiAyMS4zMiwyMi45NyAyMC41LDIzSDE0QzEzLjYyLDIzIDEzLjI2LDIyLjg1IDEzLDIyLjU3TDguMSwxOC4zN0w4Ljg0LDE3LjZDOS4wMywxNy4zOSA5LjMsMTcuMjggOS41OCwxNy4yOEg5LjhMMTMsMTlWOU0xNCw1QzE1LjQyLDUgMTYuNzQsNS43NiAxNy40NSw3QzE4LjU2LDguOSAxNy45MSwxMS4zNSAxNiwxMi40NlYxMS4yM0MxNi42NCwxMC42NyAxNyw5Ljg1IDE3LDlBMywzIDAgMCwwIDE0LDZBMywzIDAgMCwwIDExLDlDMTEsOS44NSAxMS4zNiwxMC42NyAxMiwxMS4yM1YxMi40NkMxMC43NywxMS43NSAxMCwxMC40MyAxMCw5QTQsNCAwIDAsMSAxNCw1TTQsOUw3LDEySDVWMTVIM1YxMkgxTDQsOU00LDdMMSw0SDNWMUg1VjRIN0w0LDdNOSwxNEM5LjczLDE0IDEwLjQxLDE0LjE5IDExLDE0LjU0VjE1Ljc2QzEwLjQ3LDE1LjI5IDkuNzcsMTUgOSwxNUEzLDMgMCAwLDAgNiwxOEM2LDE5IDYuNSwxOS44NyA3LjIyLDIwLjQyTDkuMzEsMjJIOUE0LDQgMCAwLDEgNSwxOEE0LDQgMCAwLDEgOSwxNFoiIC8+PC9zdmc+", + "name": "PinchGesture", + "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/gesture-pinch.svg", + "shortDescription": "Move the camera or objects with pinching gestures.", + "dimension": "2D", + "version": "1.2.2", + "description": [ + "This extension allows to:", + "- Move, rotate and/or zoom the camera according to pinch gestures", + "- Move, rotate and optionally scale objects according to pinch gestures", + "- Handle more specific needs with various expressions", + "", + "Limitations:", + "- Only one pinching gesture is detected at a time" + ], + "origin": { + "identifier": "PinchGesture", + "name": "gdevelop-extension-store" + }, + "tags": [ + "touch", + "pinch", + "gesture", + "zoom", + "rotate", + "camera", + "puzzle" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [ + { + "folded": true, + "name": "IsCameraPinchEnabled", + "type": "boolean", + "value": false + }, + { + "folded": true, + "name": "CameraPinchLayer", + "type": "string", + "value": "" + }, + { + "folded": true, + "name": "CameraPinchConstraint", + "type": "string", + "value": "" + }, + { + "folded": true, + "name": "BeginningCameraCenterX", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "BeginningCameraCenterY", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "BeginningCameraAngle", + "type": "number", + "value": 0 + }, + { + "name": "BeginningCameraZoom", + "type": "number", + "value": 0 + }, + { + "name": "BeginningDistance", + "type": "number", + "value": 0 + }, + { + "name": "BeginningAngle", + "type": "number", + "value": 0 + }, + { + "name": "BeginningCenterX", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "BeginningCenterY", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "CurrentAngle", + "type": "number", + "value": 0 + }, + { + "name": "CurrentDistance", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "CurrentCenterX", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "CurrentCenterY", + "type": "number", + "value": 0 + }, + { + "name": "TouchCount", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "FirstTouchId", + "type": "number", + "value": 0 + }, + { + "name": "SecondTouchId", + "type": "number", + "value": 0 + }, + { + "name": "FirstTouchX", + "type": "number", + "value": 0 + }, + { + "name": "FirstTouchY", + "type": "number", + "value": 0 + }, + { + "name": "SecondTouchX", + "type": "number", + "value": 0 + }, + { + "name": "SecondTouchY", + "type": "number", + "value": 0 + }, + { + "name": "ZOrderMaximum", + "type": "number", + "value": 0 + } + ], + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onSceneLoaded", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "PinchGesture::SetCameraPinchLayer" + }, + "parameters": [ + "", + "\"\"", + "" + ] + }, + { + "type": { + "value": "PinchGesture::SetCameraPinchConstraint" + }, + "parameters": [ + "", + "\"No constraint\"", + "" + ] + }, + { + "type": { + "value": "PinchGesture::EnableCameraPinch" + }, + "parameters": [ + "", + "no", + "" + ] + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Enable or disable camera pinch.", + "fullName": "Enable or disable camera pinch", + "functionType": "Action", + "group": "Camera pinch", + "name": "EnableCameraPinch", + "sentence": "Enable camera pinch: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsCameraPinchEnabled", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Enable", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsCameraPinchEnabled", + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Enable camera pinch", + "name": "Enable", + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Check if camera pinch is enabled.", + "fullName": "Camera pinch is enabled", + "functionType": "Condition", + "group": "Camera pinch", + "name": "IsCameraPinchEnabled", + "sentence": "Camera pinch is enabled", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsCameraPinchEnabled", + "True" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Choose the layer to move with pinch gestures.", + "fullName": "Camera pinch layer", + "functionType": "Action", + "group": "Camera pinch", + "name": "SetCameraPinchLayer", + "sentence": "Choose the layer _PARAM1_ to move with pinch gestures", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "CameraPinchLayer", + "=", + "Layer" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Change the camera pinch constraint.", + "fullName": "Camera pinch constraints", + "functionType": "Action", + "group": "Camera pinch", + "name": "SetCameraPinchConstraint", + "sentence": "Change the camera pinch constraint to _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "CameraPinchConstraint", + "=", + "Constraint" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Constraint", + "name": "Constraint", + "supplementaryInformation": "[\"No constraint\",\"Zoom only\",\"Rotation only\",\"Zoom and rotation only\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Pinch the camera of a layer.", + "fullName": "Pinch camera", + "functionType": "Action", + "group": "Camera pinch", + "name": "PinchCamera", + "private": true, + "sentence": "Pinch the camera of layer: _PARAM1_ with constraint: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "PinchGesture::IsPinching" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Save the initial state because the pinch is calculated from here." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "BeginningCameraCenterX", + "=", + "CameraCenterX(Layer)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "BeginningCameraCenterY", + "=", + "CameraCenterY(Layer)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "BeginningCameraAngle", + "=", + "CameraAngle(Layer)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "BeginningCameraZoom", + "=", + "CameraZoom(Layer)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Backup the camera state to revert pinch effect according to constraints." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "BackupCameraCenterX", + "=", + "CameraCenterX(Layer)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "BackupCameraCenterY", + "=", + "CameraCenterY(Layer)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "BackupCameraAngle", + "=", + "CameraAngle(Layer)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "BackupCameraZoom", + "=", + "CameraZoom(Layer)" + ] + } + ], + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Pinch the camera", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "=", + "BeginningCameraCenterX", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "=", + "BeginningCameraCenterY", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraAngle" + }, + "parameters": [ + "", + "=", + "BeginningCameraAngle", + "Layer", + "" + ] + }, + { + "type": { + "value": "ZoomCamera" + }, + "parameters": [ + "", + "BeginningCameraZoom", + "Layer", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DeltaX", + "=", + "PinchGesture::InversedX(BeginningCameraCenterX, BeginningCameraCenterY, Layer) - BeginningCameraCenterX" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DeltaY", + "=", + "PinchGesture::InversedY(BeginningCameraCenterX, BeginningCameraCenterY, Layer) - BeginningCameraCenterY" + ] + }, + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "=", + "BeginningCameraCenterX + DeltaX", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "=", + "BeginningCameraCenterY + DeltaY", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraAngle" + }, + "parameters": [ + "", + "=", + "BeginningCameraAngle - PinchGesture::Rotation()", + "Layer", + "" + ] + }, + { + "type": { + "value": "ZoomCamera" + }, + "parameters": [ + "", + "BeginningCameraZoom * PinchGesture::Scaling()", + "Layer", + "" + ] + } + ], + "variables": [ + { + "folded": true, + "name": "DeltaX", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "DeltaY", + "type": "number", + "value": 0 + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Apply constraints", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Constraint", + "!=", + "\"No constraint\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "=", + "BackupCameraCenterX", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "=", + "BackupCameraCenterY", + "Layer", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Constraint", + "=", + "\"Zoom only\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetCameraAngle" + }, + "parameters": [ + "", + "=", + "BackupCameraAngle", + "Layer", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareStrings" + }, + "parameters": [ + "Constraint", + "=", + "\"Rotation only\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "ZoomCamera" + }, + "parameters": [ + "", + "BackupCameraZoom", + "Layer", + "" + ] + } + ] + } + ] + } + ], + "parameters": [] + } + ], + "variables": [ + { + "folded": true, + "name": "BackupCameraCenterX", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "BackupCameraCenterY", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "BackupCameraAngle", + "type": "number", + "value": 0 + }, + { + "folded": true, + "name": "BackupCameraZoom", + "type": "number", + "value": 0 + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Layer", + "name": "Layer", + "type": "layer" + }, + { + "description": "Constraint", + "name": "Constraint", + "supplementaryInformation": "[\"No constraint\",\"Zoom only\",\"Rotation only\",\"Zoom and rotation only\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onScenePreEvents", + "sentence": "", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Touch end", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchCount", + "=", + "1" + ] + }, + { + "type": { + "value": "HasTouchEnded" + }, + "parameters": [ + "", + "FirstTouchId" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchCount", + "=", + "0" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchCount", + "=", + "2" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasTouchEnded" + }, + "parameters": [ + "", + "SecondTouchId" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchCount", + "-", + "1" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasTouchEnded" + }, + "parameters": [ + "", + "FirstTouchId" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchCount", + "-", + "1" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "The 2nd touch becomes the 1st one." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchCount", + "=", + "1" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FirstTouchId", + "=", + "SecondTouchId" + ] + } + ] + } + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Touch start", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Pinch with more than 2 touches is not handled." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasAnyTouchStarted" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchCount", + "<", + "2" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Index", + "=", + "0" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "StartedTouchCount()", + "conditions": [], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "2nd touch started: save the 2 touches locations" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchCount", + "=", + "1" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchCount", + "=", + "2" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "SecondTouchId", + "=", + "StartedTouchId(Index)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "SecondTouchX", + "=", + "PinchGesture::TouchCanvasX(StartedTouchId(Index))" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "SecondTouchY", + "=", + "PinchGesture::TouchCanvasY(StartedTouchId(Index))" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FirstTouchX", + "=", + "PinchGesture::TouchCanvasX(FirstTouchId)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FirstTouchY", + "=", + "PinchGesture::TouchCanvasY(FirstTouchId)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "BeginningAngle", + "=", + "AngleBetweenPositions\n(\n FirstTouchX,\n FirstTouchY,\n SecondTouchX,\n SecondTouchY\n)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "BeginningDistance", + "=", + "DistanceBetweenPositions\n(\n FirstTouchX,\n FirstTouchY,\n SecondTouchX,\n SecondTouchY\n)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "BeginningCenterX", + "=", + "(FirstTouchX + SecondTouchX) / 2" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "BeginningCenterY", + "=", + "(FirstTouchY + SecondTouchY) / 2" + ] + } + ], + "events": [ + { + "disabled": true, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "DebuggerTools::ConsoleLog" + }, + "parameters": [ + "\"distance: \" + BeginningDistance", + "", + "" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "It's important to do this case after because TouchCount is incremented in this loop (0 to 1 and 1 to 2 must not happen with the same touch event)." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchCount", + "=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FirstTouchId", + "=", + "StartedTouchId(Index)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TouchCount", + "=", + "1" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Index", + "+", + "1" + ] + } + ] + } + ] + } + ], + "variables": [ + { + "folded": true, + "name": "Index", + "type": "number", + "value": 0 + } + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Pinching", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchCount", + "=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CurrentAngle", + "=", + "AngleBetweenPositions\n(\n PinchGesture::TouchCanvasX(FirstTouchId),\n PinchGesture::TouchCanvasY(FirstTouchId),\n PinchGesture::TouchCanvasX(SecondTouchId),\n PinchGesture::TouchCanvasY(SecondTouchId)\n)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CurrentDistance", + "=", + "DistanceBetweenPositions\n(\n PinchGesture::TouchCanvasX(FirstTouchId),\n PinchGesture::TouchCanvasY(FirstTouchId),\n PinchGesture::TouchCanvasX(SecondTouchId),\n PinchGesture::TouchCanvasY(SecondTouchId)\n)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CurrentCenterX", + "=", + "(PinchGesture::TouchCanvasX(FirstTouchId) + PinchGesture::TouchCanvasX(SecondTouchId)) / 2" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CurrentCenterY", + "=", + "(PinchGesture::TouchCanvasY(FirstTouchId) + PinchGesture::TouchCanvasY(SecondTouchId)) / 2" + ] + } + ] + } + ], + "parameters": [] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "PinchGesture::IsCameraPinchEnabled" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PinchGesture::PinchCamera" + }, + "parameters": [ + "", + "CameraPinchLayer", + "CameraPinchConstraint", + "" + ] + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Check if a touch is pinching, if 2 touches are pressed.", + "fullName": "Touch is pinching", + "functionType": "Condition", + "name": "IsPinching", + "sentence": "Touch is pinching", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchCount", + "=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Return the scaling of the pinch gesture from its beginning.", + "fullName": "Pinch scaling", + "functionType": "Expression", + "name": "Scaling", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "CurrentDistance / BeginningDistance" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [], + "objectGroups": [] + }, + { + "description": "Return the rotation of the pinch gesture from its beginning (in degrees).", + "fullName": "Pinch rotation", + "functionType": "Expression", + "name": "Rotation", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "CurrentAngle - BeginningAngle" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [], + "objectGroups": [] + }, + { + "description": "Return the X position of the pinch center at the beginning of the gesture.", + "fullName": "Pinch beginning center X", + "functionType": "Expression", + "name": "BeginningCenterX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "PinchGesture::TransformToSceneX(BeginningCenterX, BeginningCenterY, Layer)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Layer", + "name": "Layer", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Y position of the pinch center at the beginning of the gesture.", + "fullName": "Pinch beginning center Y", + "functionType": "Expression", + "name": "BeginningCenterY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "PinchGesture::TransformToSceneY(BeginningCenterX, BeginningCenterY, Layer)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Layer", + "name": "Layer", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Return the X position of the pinch center.", + "fullName": "Pinch center X", + "functionType": "Expression", + "name": "CurrentCenterX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "PinchGesture::TransformToSceneX(CurrentCenterX, CurrentCenterY, Layer)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Layer", + "name": "Layer", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Y position of the pinch center.", + "fullName": "Pinch center Y", + "functionType": "Expression", + "name": "CurrentCenterY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "PinchGesture::TransformToSceneY(CurrentCenterX, CurrentCenterY, Layer)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Layer", + "name": "Layer", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Return the horizontal translation of the pinch gesture from its beginning.", + "fullName": "Pinch translation X", + "functionType": "Expression", + "name": "TranslationX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "PinchGesture::CurrentCenterX(Layer) - PinchGesture::BeginningCenterX(Layer)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Return the vertical translation of the pinch gesture from its beginning.", + "fullName": "Pinch translation Y", + "functionType": "Expression", + "name": "TranslationY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "PinchGesture::CurrentCenterY(Layer) - PinchGesture::BeginningCenterY(Layer)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Return the new X position of a point after the pinch gesture.", + "fullName": "Transform X position", + "functionType": "Expression", + "name": "TransformedX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "PinchGesture::CurrentCenterX(Layer) + PinchGesture::Scaling() * PinchGesture::RotatedVectorX(\n PositionX - PinchGesture::BeginningCenterX(Layer),\n PositionY - PinchGesture::BeginningCenterY(Layer),\n PinchGesture::Rotation()\n)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Position X before the pinch", + "name": "PositionX", + "type": "expression" + }, + { + "description": "Position Y before the pinch", + "name": "PositionY", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Return the new Y position of a point after the pinch gesture.", + "fullName": "Transform Y position", + "functionType": "Expression", + "name": "TransformedY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "PinchGesture::CurrentCenterY(Layer) + PinchGesture::Scaling() * PinchGesture::RotatedVectorY(\n PositionX - PinchGesture::BeginningCenterX(Layer),\n PositionY - PinchGesture::BeginningCenterY(Layer),\n PinchGesture::Rotation()\n)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Position X before the pinch", + "name": "PositionX", + "type": "expression" + }, + { + "description": "Position Y before the pinch", + "name": "PositionY", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Return the original X position of a point before the pinch gesture.", + "fullName": "Inversed transform X position", + "functionType": "Expression", + "name": "InversedX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "PinchGesture::BeginningCenterX(Layer) + PinchGesture::RotatedVectorX(\n PositionX - PinchGesture::CurrentCenterX(Layer),\n PositionY - PinchGesture::CurrentCenterY(Layer),\n -PinchGesture::Rotation()\n) / PinchGesture::Scaling()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Position X after the pinch", + "name": "PositionX", + "type": "expression" + }, + { + "description": "Position Y after the pinch", + "name": "PositionY", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Return the original Y position of a point before the pinch gesture.", + "fullName": "Inversed transform Y position", + "functionType": "Expression", + "name": "InversedY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "PinchGesture::BeginningCenterY(Layer) + PinchGesture::RotatedVectorY(\n PositionX - PinchGesture::CurrentCenterX(Layer),\n PositionY - PinchGesture::CurrentCenterY(Layer),\n -PinchGesture::Rotation()\n) / PinchGesture::Scaling()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Position X after the pinch", + "name": "PositionX", + "type": "expression" + }, + { + "description": "Position Y after the pinch", + "name": "PositionY", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Return the X coordinate of a position transformed from the scene to the canvas according to a layer.", + "fullName": "Transform X to canvas", + "functionType": "Expression", + "name": "TransformToCanvasX", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const x = eventsFunctionContext.getArgument(\"PositionX\");\r", + "const y = eventsFunctionContext.getArgument(\"PositionY\");\r", + "const layer = runtimeScene.getLayer(eventsFunctionContext.getArgument(\"Layer\"));\r", + "\r", + "eventsFunctionContext.returnValue = layer.convertInverseCoords(x, y)[0];" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Position X", + "name": "PositionX", + "type": "expression" + }, + { + "description": "Position Y", + "name": "PositionY", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Y coordinate of a position transformed from the scene to the canvas according to a layer.", + "fullName": "Transform Y to canvas", + "functionType": "Expression", + "name": "TransformToCanvasY", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const x = eventsFunctionContext.getArgument(\"PositionX\");\r", + "const y = eventsFunctionContext.getArgument(\"PositionY\");\r", + "const layer = runtimeScene.getLayer(eventsFunctionContext.getArgument(\"Layer\"));\r", + "\r", + "eventsFunctionContext.returnValue = layer.convertInverseCoords(x, y)[1];" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Position X", + "name": "PositionX", + "type": "expression" + }, + { + "description": "Position Y", + "name": "PositionY", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Return the X coordinate of a position transformed from the canvas to the scene according to a layer.", + "fullName": "Transform X to scene", + "functionType": "Expression", + "name": "TransformToSceneX", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const x = eventsFunctionContext.getArgument(\"PositionX\");\r", + "const y = eventsFunctionContext.getArgument(\"PositionY\");\r", + "const layer = runtimeScene.getLayer(eventsFunctionContext.getArgument(\"Layer\"));\r", + "\r", + "eventsFunctionContext.returnValue = layer.convertCoords(x, y, 0, [0, 0])[0];" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Position X", + "name": "PositionX", + "type": "expression" + }, + { + "description": "Position Y", + "name": "PositionY", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Y coordinate of a position transformed from the canvas to the scene according to a layer.", + "fullName": "Transform Y to scene", + "functionType": "Expression", + "name": "TransformToSceneY", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const x = eventsFunctionContext.getArgument(\"PositionX\");\r", + "const y = eventsFunctionContext.getArgument(\"PositionY\");\r", + "const layer = runtimeScene.getLayer(eventsFunctionContext.getArgument(\"Layer\"));\r", + "\r", + "eventsFunctionContext.returnValue = layer.convertCoords(x, y, 0, [0, 0])[1];" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Position X", + "name": "PositionX", + "type": "expression" + }, + { + "description": "Position Y", + "name": "PositionY", + "type": "expression" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Return the touch X on the canvas.", + "fullName": "Touch X on canvas", + "functionType": "Expression", + "name": "TouchCanvasX", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "PinchGesture::TransformToCanvasX(TouchX(TouchId), TouchY(TouchId), \"\")" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Touch identifier", + "name": "TouchId", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the touch Y on the canvas.", + "fullName": "Touch Y on canvas", + "functionType": "Expression", + "name": "TouchCanvasY", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "PinchGesture::TransformToCanvasY(TouchX(TouchId), TouchY(TouchId), \"\")" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Touch identifier", + "name": "TouchId", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the X coordinate of a vector after a rotation", + "fullName": "Rotated vector X", + "functionType": "Expression", + "name": "RotatedVectorX", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "cos(ToRad(Angle)) * VectorX - sin(ToRad(Angle)) * VectorY" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Vector X", + "name": "VectorX", + "type": "expression" + }, + { + "description": "Vector Y", + "name": "VectorY", + "type": "expression" + }, + { + "description": "Angle (in degrees)", + "name": "Angle", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the Y coordinate of a vector after a rotation", + "fullName": "Rotated vector Y", + "functionType": "Expression", + "name": "RotatedVectorY", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "sin(ToRad(Angle)) * VectorX + cos(ToRad(Angle)) * VectorY" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Vector X", + "name": "VectorX", + "type": "expression" + }, + { + "description": "Vector Y", + "name": "VectorY", + "type": "expression" + }, + { + "description": "Angle (in degrees)", + "name": "Angle", + "type": "expression" + } + ], + "objectGroups": [] + } + ], + "eventsBasedBehaviors": [ + { + "description": "Move objects by holding 2 touches on them.", + "fullName": "Pinchable object", + "name": "Pichable", + "objectType": "", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "PinchGesture::IsPinching" + }, + "parameters": [ + "", + "" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Only the objects with the highest Z-Order will be actually pinched (see doStepPostEvents)." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CollisionPoint" + }, + "parameters": [ + "Object", + "PinchGesture::CurrentCenterX(Object.Layer())", + "PinchGesture::CurrentCenterY(Object.Layer())" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsCandidate", + "True", + "" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ZOrderMaximum", + "=", + "max(ZOrderMaximum, Object.ZOrder())" + ] + }, + { + "type": { + "value": "PinchGesture::Pichable::SetPropertyBeginningCenterX" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Object.CenterX()" + ] + }, + { + "type": { + "value": "PinchGesture::Pichable::SetPropertyBeginningCenterY" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Object.CenterY()" + ] + }, + { + "type": { + "value": "PinchGesture::Pichable::SetPropertyBeginningAngle" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Object.Angle()" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "BeginningWidth", + "=", + "Object.Width()" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "BeginningHeight", + "=", + "Object.Height()" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Move the object according to the pinch gesture." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsPinched", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetCenterX" + }, + "parameters": [ + "Object", + "=", + "PinchGesture::TransformedX(BeginningCenterX, BeginningCenterY, Object.Layer())" + ] + }, + { + "type": { + "value": "SetCenterY" + }, + "parameters": [ + "Object", + "=", + "PinchGesture::TransformedY(BeginningCenterX, BeginningCenterY, Object.Layer())" + ] + }, + { + "type": { + "value": "SetAngle" + }, + "parameters": [ + "Object", + "=", + "BeginningAngle + PinchGesture::Rotation()" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "LockSize", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ResizableCapability::ResizableBehavior::SetWidth" + }, + "parameters": [ + "Object", + "Resizable", + "=", + "BeginningWidth * PinchGesture::Scaling()" + ] + }, + { + "type": { + "value": "ResizableCapability::ResizableBehavior::SetHeight" + }, + "parameters": [ + "Object", + "Resizable", + "=", + "BeginningHeight * PinchGesture::Scaling()" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "PinchGesture::IsPinching" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsPinched", + "False", + "" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ZOrderMaximum", + "=", + "0" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "PinchGesture::Pichable", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Only the objects with the highest Z-Order are actually pinched.\nIf all objects have the same Z-Order, they can all be picked at once." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsCandidate", + "True", + "" + ] + }, + { + "type": { + "value": "ZOrder" + }, + "parameters": [ + "Object", + ">=", + "ZOrderMaximum" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsPinched", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsCandidate", + "False", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "PinchGesture::Pichable", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the object is being pinched.", + "fullName": "Is being pinched", + "functionType": "Condition", + "name": "IsPinched", + "sentence": "_PARAM0_ is being pinched", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsPinched", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "PinchGesture::Pichable", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Abort the pinching of this object.", + "fullName": "Abort pinching", + "functionType": "Action", + "name": "AbortPinching", + "sentence": "Abort the pinching of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsPinched", + "False", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "PinchGesture::Pichable", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Resizable capability", + "description": "", + "group": "", + "extraInformation": [ + "ResizableCapability::ResizableBehavior" + ], + "name": "Resizable" + }, + { + "value": "", + "type": "Boolean", + "label": "Lock object size", + "description": "", + "group": "", + "extraInformation": [], + "name": "LockSize" + }, + { + "value": "", + "type": "Boolean", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "IsCandidate" + }, + { + "value": "", + "type": "Boolean", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "IsPinched" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "BeginningCenterX" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "BeginningCenterY" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "BeginningAngle" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "BeginningWidth" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "BeginningHeight" + } + ], + "sharedPropertyDescriptors": [] + } + ], + "eventsBasedObjects": [] + } + ], + "externalLayouts": [] +} diff --git a/examples/starting-expanded-3d-rts-unit-selection/theme-slots.json b/examples/starting-expanded-3d-rts-unit-selection/theme-slots.json new file mode 100644 index 000000000..f3cc8a846 --- /dev/null +++ b/examples/starting-expanded-3d-rts-unit-selection/theme-slots.json @@ -0,0 +1,67 @@ +{ + "version": 1, + "objects": { + "scene:Game Scene/RTSUnit": "character.player", + "scene:Game Scene/Grass": "nature.grass", + "scene:Game Scene/TownCenter": "building.tower", + "scene:Game Scene/House": "building.tower", + "scene:Game Scene/HouseTall": "building.tower", + "scene:Game Scene/HouseSmall": "building.tower", + "scene:Game Scene/Barracks": "building.tower", + "scene:Game Scene/Market": "building.tower", + "scene:Game Scene/Storage": "building.tower", + "scene:Game Scene/Trees": { + "Model": "nature.tree" + }, + "scene:Game Scene/RoundTrees": { + "Model": "nature.tree" + }, + "scene:Game Scene/PineTrees": { + "Model": "nature.tree" + }, + "scene:Game Scene/TreeGroups": { + "Model": "nature.tree" + }, + "scene:Game Scene/Rocks": { + "Model": "nature.rock.small" + }, + "scene:Game Scene/LargeRocks": { + "Model": "nature.rock.big" + }, + "scene:Game Scene/GrassTufts": { + "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" + } + } + }, + "effects": {}, + "ignoredObjects": [ + "scene:Game Scene/Camera", + "object:Water3D::Water3D/AreaPlaceholder", + "scene:Game Scene/WatchTower", + "scene:Game Scene/Windmill", + "scene:Game Scene/Mine", + "scene:Game Scene/Farm", + "scene:Game Scene/Gold", + "scene:Game Scene/GoldLarge", + "object:Terrain3D::Terrain3D/AreaPlaceholder", + "object:Terrain3D::TerrainGrass/AreaPlaceholder", + "object:Terrain3D::ScatteredModels/AreaPlaceholder" + ] +} diff --git a/examples/starting-expanded-3d-top-down-rpg/README.md b/examples/starting-expanded-3d-top-down-rpg/README.md new file mode 100644 index 000000000..bbf7dcd44 --- /dev/null +++ b/examples/starting-expanded-3d-top-down-rpg/README.md @@ -0,0 +1,3 @@ +# 3D RPG Expanded + +A 3D top-down RPG-style game with an angled down camera view, in a village by a river made with a 3D terrain, with paths between hills, forests and a bridge to cross the river. The player walks up and down the slopes with keyboard or touch screen controls, and is stopped by the buildings, the trees and the river. When near an NPC, the player can press an action button to open a dialog box with text and two choice buttons. The camera follows the player. diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/8BitOperatorPlus8Regular_Edited.fnt b/examples/starting-expanded-3d-top-down-rpg/assets/8BitOperatorPlus8Regular_Edited.fnt new file mode 100644 index 000000000..def784a82 --- /dev/null +++ b/examples/starting-expanded-3d-top-down-rpg/assets/8BitOperatorPlus8Regular_Edited.fnt @@ -0,0 +1,95 @@ +info face="8-bitOperatorPlus8-Regular" size=23 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=10,10,10,10 spacing=5,5 +common lineHeight=23 base=14 scaleW=392 scaleH=387 pages=1 packed=0 +page id=0 file="8BitOperatorPlus8Regular_Edited.png" +chars count=91 +char id=32 x=0 y=0 width=0 height=0 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=15 +char id=33 x=364 y=111 width=26 height=38 xoffset=-10 yoffset=-11.98 xadvance=6 page=0 chnl=15 +char id=34 x=288 y=152 width=34 height=30 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=35 x=52 y=0 width=36 height=36 xoffset=-10 yoffset=-9.98 xadvance=16 page=0 chnl=15 +char id=36 x=210 y=86 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=37 x=0 y=339 width=36 height=36 xoffset=-10 yoffset=-9.98 xadvance=16 page=0 chnl=15 +char id=38 x=210 y=129 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=39 x=364 y=76 width=28 height=30 xoffset=-10 yoffset=-11.98 xadvance=8 page=0 chnl=15 +char id=40 x=327 y=203 width=30 height=38 xoffset=-10 yoffset=-11.98 xadvance=10 page=0 chnl=15 +char id=41 x=327 y=246 width=30 height=38 xoffset=-10 yoffset=-11.98 xadvance=10 page=0 chnl=15 +char id=42 x=0 y=210 width=38 height=34 xoffset=-10 yoffset=-9.98 xadvance=18 page=0 chnl=15 +char id=43 x=288 y=78 width=34 height=34 xoffset=-10 yoffset=-9.98 xadvance=14 page=0 chnl=15 +char id=44 x=364 y=41 width=28 height=30 xoffset=-10 yoffset=-1.99 xadvance=8 page=0 chnl=15 +char id=45 x=327 y=0 width=32 height=26 xoffset=-10 yoffset=-5.99 xadvance=12 page=0 chnl=15 +char id=46 x=364 y=236 width=26 height=26 xoffset=-10 yoffset=0 xadvance=6 page=0 chnl=15 +char id=47 x=288 y=222 width=32 height=40 xoffset=-10 yoffset=-11.98 xadvance=12 page=0 chnl=15 +char id=48 x=52 y=86 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=49 x=327 y=31 width=30 height=38 xoffset=-10 yoffset=-11.98 xadvance=10 page=0 chnl=15 +char id=50 x=52 y=129 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=51 x=52 y=172 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=52 x=52 y=215 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=53 x=52 y=258 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=54 x=52 y=301 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=55 x=52 y=344 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=56 x=93 y=0 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=57 x=93 y=43 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=58 x=364 y=197 width=26 height=34 xoffset=-10 yoffset=-7.98 xadvance=6 page=0 chnl=15 +char id=59 x=364 y=0 width=28 height=36 xoffset=-10 yoffset=-7.98 xadvance=8 page=0 chnl=15 +char id=61 x=288 y=117 width=34 height=30 xoffset=-10 yoffset=-7.98 xadvance=14 page=0 chnl=15 +char id=63 x=210 y=43 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=64 x=0 y=294 width=36 height=40 xoffset=-10 yoffset=-11.98 xadvance=16 page=0 chnl=15 +char id=65 x=93 y=258 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=66 x=93 y=301 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=67 x=93 y=344 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=68 x=132 y=0 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=69 x=132 y=43 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=70 x=132 y=86 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=71 x=132 y=129 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=72 x=132 y=172 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=73 x=327 y=160 width=30 height=38 xoffset=-10 yoffset=-11.98 xadvance=10 page=0 chnl=15 +char id=74 x=132 y=215 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=75 x=132 y=258 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=76 x=132 y=301 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=77 x=0 y=46 width=38 height=38 xoffset=-10 yoffset=-11.98 xadvance=18 page=0 chnl=15 +char id=78 x=132 y=344 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=79 x=171 y=0 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=80 x=171 y=43 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=81 x=0 y=249 width=36 height=40 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=82 x=171 y=86 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=83 x=171 y=129 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=84 x=171 y=172 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=85 x=171 y=215 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=86 x=171 y=258 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=87 x=0 y=89 width=38 height=38 xoffset=-10 yoffset=-11.98 xadvance=18 page=0 chnl=15 +char id=88 x=171 y=301 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=89 x=171 y=344 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=90 x=210 y=0 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=91 x=327 y=289 width=30 height=38 xoffset=-10 yoffset=-11.98 xadvance=10 page=0 chnl=15 +char id=93 x=327 y=332 width=30 height=38 xoffset=-10 yoffset=-11.98 xadvance=10 page=0 chnl=15 +char id=94 x=288 y=187 width=34 height=30 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=95 x=288 y=353 width=32 height=26 xoffset=-12 yoffset=2 xadvance=8 page=0 chnl=15 +char id=97 x=249 y=41 width=34 height=34 xoffset=-10 yoffset=-7.98 xadvance=14 page=0 chnl=15 +char id=98 x=93 y=86 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=99 x=249 y=80 width=34 height=34 xoffset=-10 yoffset=-7.98 xadvance=14 page=0 chnl=15 +char id=100 x=93 y=129 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=101 x=249 y=119 width=34 height=34 xoffset=-10 yoffset=-7.98 xadvance=14 page=0 chnl=15 +char id=102 x=288 y=267 width=32 height=38 xoffset=-10 yoffset=-11.98 xadvance=12 page=0 chnl=15 +char id=103 x=210 y=258 width=34 height=36 xoffset=-10 yoffset=-7.98 xadvance=14 page=0 chnl=15 +char id=104 x=93 y=172 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=105 x=327 y=74 width=30 height=38 xoffset=-10 yoffset=-11.98 xadvance=10 page=0 chnl=15 +char id=106 x=52 y=41 width=34 height=40 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=107 x=93 y=215 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=108 x=327 y=117 width=30 height=38 xoffset=-10 yoffset=-11.98 xadvance=10 page=0 chnl=15 +char id=109 x=0 y=132 width=38 height=34 xoffset=-10 yoffset=-7.98 xadvance=18 page=0 chnl=15 +char id=110 x=249 y=158 width=34 height=34 xoffset=-10 yoffset=-7.98 xadvance=14 page=0 chnl=15 +char id=111 x=249 y=197 width=34 height=34 xoffset=-10 yoffset=-7.98 xadvance=14 page=0 chnl=15 +char id=112 x=210 y=299 width=34 height=36 xoffset=-10 yoffset=-7.98 xadvance=14 page=0 chnl=15 +char id=113 x=210 y=340 width=34 height=36 xoffset=-10 yoffset=-7.98 xadvance=14 page=0 chnl=15 +char id=114 x=249 y=236 width=34 height=34 xoffset=-10 yoffset=-7.98 xadvance=14 page=0 chnl=15 +char id=115 x=249 y=275 width=34 height=34 xoffset=-10 yoffset=-7.98 xadvance=14 page=0 chnl=15 +char id=116 x=288 y=310 width=32 height=38 xoffset=-10 yoffset=-11.98 xadvance=12 page=0 chnl=15 +char id=117 x=249 y=314 width=34 height=34 xoffset=-10 yoffset=-7.98 xadvance=14 page=0 chnl=15 +char id=118 x=249 y=353 width=34 height=34 xoffset=-10 yoffset=-7.98 xadvance=14 page=0 chnl=15 +char id=119 x=0 y=171 width=38 height=34 xoffset=-10 yoffset=-7.98 xadvance=18 page=0 chnl=15 +char id=120 x=288 y=0 width=34 height=34 xoffset=-10 yoffset=-7.98 xadvance=14 page=0 chnl=15 +char id=121 x=249 y=0 width=34 height=36 xoffset=-10 yoffset=-7.98 xadvance=14 page=0 chnl=15 +char id=122 x=288 y=39 width=34 height=34 xoffset=-10 yoffset=-7.98 xadvance=14 page=0 chnl=15 +char id=123 x=210 y=172 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=124 x=364 y=154 width=26 height=38 xoffset=-10 yoffset=-11.98 xadvance=6 page=0 chnl=15 +char id=125 x=210 y=215 width=34 height=38 xoffset=-10 yoffset=-11.98 xadvance=14 page=0 chnl=15 +char id=8470 x=0 y=0 width=47 height=41 xoffset=-13 yoffset=-8 xadvance=22 page=0 chnl=15 diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/8BitOperatorPlus8Regular_Edited.png b/examples/starting-expanded-3d-top-down-rpg/assets/8BitOperatorPlus8Regular_Edited.png new file mode 100644 index 000000000..84c5b5f00 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/8BitOperatorPlus8Regular_Edited.png differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/A button.png b/examples/starting-expanded-3d-top-down-rpg/assets/A button.png new file mode 100644 index 000000000..644bce1a3 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/A button.png differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/Blacksmith.glb b/examples/starting-expanded-3d-top-down-rpg/assets/Blacksmith.glb new file mode 100644 index 000000000..223a3aaa9 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/Blacksmith.glb differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/Border.png b/examples/starting-expanded-3d-top-down-rpg/assets/Border.png new file mode 100644 index 000000000..fc209e345 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/Border.png differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/Border2.png b/examples/starting-expanded-3d-top-down-rpg/assets/Border2.png new file mode 100644 index 000000000..5d5ab796a Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/Border2.png differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/Common Tree 1.glb b/examples/starting-expanded-3d-top-down-rpg/assets/Common Tree 1.glb new file mode 100644 index 000000000..59315370a Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/Common Tree 1.glb differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/DirtParticle.png b/examples/starting-expanded-3d-top-down-rpg/assets/DirtParticle.png new file mode 100644 index 000000000..07db099a3 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/DirtParticle.png differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/Dock Long.glb b/examples/starting-expanded-3d-top-down-rpg/assets/Dock Long.glb new file mode 100644 index 000000000..5935de719 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/Dock Long.glb differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/Flat dark joystick border.png b/examples/starting-expanded-3d-top-down-rpg/assets/Flat dark joystick border.png new file mode 100644 index 000000000..b4e83e112 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/Flat dark joystick border.png differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/Flat dark joystick thumb.png b/examples/starting-expanded-3d-top-down-rpg/assets/Flat dark joystick thumb.png new file mode 100644 index 000000000..8acf179f8 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/Flat dark joystick thumb.png differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/House 1.glb b/examples/starting-expanded-3d-top-down-rpg/assets/House 1.glb new file mode 100644 index 000000000..f9ec5d0ef Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/House 1.glb differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/House 2.glb b/examples/starting-expanded-3d-top-down-rpg/assets/House 2.glb new file mode 100644 index 000000000..8e80ed548 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/House 2.glb differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/Inn.glb b/examples/starting-expanded-3d-top-down-rpg/assets/Inn.glb new file mode 100644 index 000000000..d7eab11ef Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/Inn.glb differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/Market Stand 1.glb b/examples/starting-expanded-3d-top-down-rpg/assets/Market Stand 1.glb new file mode 100644 index 000000000..37b842e0c Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/Market Stand 1.glb differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/Pine Tree 2.glb b/examples/starting-expanded-3d-top-down-rpg/assets/Pine Tree 2.glb new file mode 100644 index 000000000..7612550e6 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/Pine Tree 2.glb differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/Rock Moss 2.glb b/examples/starting-expanded-3d-top-down-rpg/assets/Rock Moss 2.glb new file mode 100644 index 000000000..3526661d1 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/Rock Moss 2.glb differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/StartingCameraBody.png b/examples/starting-expanded-3d-top-down-rpg/assets/StartingCameraBody.png new file mode 100644 index 000000000..ca3bca425 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/StartingCameraBody.png differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/StartingCameraEye.png b/examples/starting-expanded-3d-top-down-rpg/assets/StartingCameraEye.png new file mode 100644 index 000000000..e4e6e9c56 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/StartingCameraEye.png differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/StartingCameraTop.png b/examples/starting-expanded-3d-top-down-rpg/assets/StartingCameraTop.png new file mode 100644 index 000000000..e90865197 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/StartingCameraTop.png differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/StartingWall.png b/examples/starting-expanded-3d-top-down-rpg/assets/StartingWall.png new file mode 100644 index 000000000..b3a097887 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/StartingWall.png differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/TerrainDirt.jpg b/examples/starting-expanded-3d-top-down-rpg/assets/TerrainDirt.jpg new file mode 100644 index 000000000..71221d59d Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/TerrainDirt.jpg differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/TerrainDirtNormal.jpg b/examples/starting-expanded-3d-top-down-rpg/assets/TerrainDirtNormal.jpg new file mode 100644 index 000000000..f701da364 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/TerrainDirtNormal.jpg differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/TerrainGrass.jpg b/examples/starting-expanded-3d-top-down-rpg/assets/TerrainGrass.jpg new file mode 100644 index 000000000..220cddc0d Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/TerrainGrass.jpg differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/TerrainGrassNormal.jpg b/examples/starting-expanded-3d-top-down-rpg/assets/TerrainGrassNormal.jpg new file mode 100644 index 000000000..cd5053235 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/TerrainGrassNormal.jpg differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/TerrainRock.jpg b/examples/starting-expanded-3d-top-down-rpg/assets/TerrainRock.jpg new file mode 100644 index 000000000..5de6dfb13 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/TerrainRock.jpg differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/TerrainRockNormal.jpg b/examples/starting-expanded-3d-top-down-rpg/assets/TerrainRockNormal.jpg new file mode 100644 index 000000000..8d17bc073 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/TerrainRockNormal.jpg differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/TerrainSand.jpg b/examples/starting-expanded-3d-top-down-rpg/assets/TerrainSand.jpg new file mode 100644 index 000000000..c6f848c09 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/TerrainSand.jpg differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/TerrainSandNormal.jpg b/examples/starting-expanded-3d-top-down-rpg/assets/TerrainSandNormal.jpg new file mode 100644 index 000000000..ef35cddc8 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/TerrainSandNormal.jpg differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/WaterNormalMap.jpg b/examples/starting-expanded-3d-top-down-rpg/assets/WaterNormalMap.jpg new file mode 100644 index 000000000..558426e0e Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/WaterNormalMap.jpg differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/Well.glb b/examples/starting-expanded-3d-top-down-rpg/assets/Well.glb new file mode 100644 index 000000000..9f28056fe Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/Well.glb differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/grass_multiple_blades.glb b/examples/starting-expanded-3d-top-down-rpg/assets/grass_multiple_blades.glb new file mode 100644 index 000000000..078a86ba4 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/grass_multiple_blades.glb differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/starter_pop.wav b/examples/starting-expanded-3d-top-down-rpg/assets/starter_pop.wav new file mode 100644 index 000000000..5bc547262 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/starter_pop.wav differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/starting_bookopen.wav b/examples/starting-expanded-3d-top-down-rpg/assets/starting_bookopen.wav new file mode 100644 index 000000000..a63e2783f Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/starting_bookopen.wav differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/starting_unit_green.glb b/examples/starting-expanded-3d-top-down-rpg/assets/starting_unit_green.glb new file mode 100644 index 000000000..14a76abea Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/starting_unit_green.glb differ diff --git a/examples/starting-expanded-3d-top-down-rpg/assets/starting_unit_orange.glb b/examples/starting-expanded-3d-top-down-rpg/assets/starting_unit_orange.glb new file mode 100644 index 000000000..b13cf08d4 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/assets/starting_unit_orange.glb differ diff --git a/examples/starting-expanded-3d-top-down-rpg/preview.png b/examples/starting-expanded-3d-top-down-rpg/preview.png new file mode 100644 index 000000000..7d7e64006 Binary files /dev/null and b/examples/starting-expanded-3d-top-down-rpg/preview.png differ diff --git a/examples/starting-expanded-3d-top-down-rpg/starting-expanded-3d-top-down-rpg.json b/examples/starting-expanded-3d-top-down-rpg/starting-expanded-3d-top-down-rpg.json new file mode 100644 index 000000000..27dd78458 --- /dev/null +++ b/examples/starting-expanded-3d-top-down-rpg/starting-expanded-3d-top-down-rpg.json @@ -0,0 +1,56408 @@ +{ + "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": "04973cf8-033c-57a0-a86b-fe7b25d3c3a1", + "scaleMode": "linear", + "sizeOnStartupMode": "", + "templateSlug": "starting-top-down", + "version": "1.0.0", + "name": "3D RPG Expanded", + "description": "A 3D top-down RPG in a village by a river, with paths between hills, made with a 3D terrain.", + "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/8BitOperatorPlus8Regular_Edited.png", + "kind": "image", + "metadata": "", + "name": "8BitOperatorPlus8Regular_Edited.png", + "smoothed": false, + "userAdded": false, + "origin": { + "identifier": "https://asset-resources.gdevelop.io/public-resources/Dialog Boxes/1118dc404ecf90d73c29dbc104c1f4d91b09e8d733e10db96a51a68b6cccfb74_8BitOperatorPlus8Regular_Edited.png", + "name": "8BitOperatorPlus8Regular_Edited.png" + } + }, + { + "file": "assets/Border.png", + "kind": "image", + "metadata": "", + "name": "Border.png", + "smoothed": true, + "userAdded": false, + "origin": { + "identifier": "https://asset-resources.gdevelop.io/public-resources/Dialog Boxes/05ecd64f1c91a6829602b38fba30e6ee390053c063d5c31084423ba5d3cabd14_Border.png", + "name": "Border.png" + } + }, + { + "file": "assets/Border2.png", + "kind": "image", + "metadata": "", + "name": "Border2.png", + "smoothed": true, + "userAdded": false, + "origin": { + "identifier": "https://asset-resources.gdevelop.io/public-resources/Dialog Boxes/f658987acc6831462b2b708842db73eb5d455d414b0c281f1e2237c335972585_Border2.png", + "name": "Border2.png" + } + }, + { + "file": "assets/8BitOperatorPlus8Regular_Edited.fnt", + "kind": "bitmapFont", + "metadata": "", + "name": "8BitOperatorPlus8Regular_Edited.fnt", + "userAdded": false, + "origin": { + "identifier": "https://asset-resources.gdevelop.io/public-resources/Dialog Boxes/6b410f07a3a02e721a49d41795e84b56707ef28ac4d095d55f1fd1093760e134_8BitOperatorPlus8Regular_Edited.fnt", + "name": "8BitOperatorPlus8Regular_Edited.fnt" + } + }, + { + "file": "assets/StartingCameraEye.png", + "kind": "image", + "metadata": "", + "name": "assets\\StartingCameraEye.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/StartingWall.png", + "kind": "image", + "metadata": "", + "name": "assets\\StartingWall.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/StartingCameraTop.png", + "kind": "image", + "metadata": "", + "name": "assets\\StartingCameraTop.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/StartingCameraBody.png", + "kind": "image", + "metadata": "", + "name": "assets\\StartingCameraBody.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/starting_unit_orange.glb", + "kind": "model3D", + "metadata": "", + "name": "assets\\starting_unit_orange.glb", + "userAdded": false + }, + { + "file": "assets/starting_unit_green.glb", + "kind": "model3D", + "metadata": "", + "name": "assets\\starting_unit_green.glb", + "userAdded": false + }, + { + "file": "assets/grass_multiple_blades.glb", + "kind": "model3D", + "metadata": "", + "name": "assets\\grass_multiple_blades.glb", + "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/Flat dark joystick thumb.png", + "kind": "image", + "metadata": "", + "name": "assets\\Flat dark joystick thumb.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/A button.png", + "kind": "image", + "metadata": "", + "name": "assets\\A button.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/DirtParticle.png", + "kind": "image", + "metadata": "", + "name": "assets/DirtParticle.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/starting_bookopen.wav", + "kind": "audio", + "metadata": "", + "name": "assets/starting_bookopen.wav", + "preloadAsMusic": false, + "preloadAsSound": false, + "preloadInCache": false, + "userAdded": false + }, + { + "file": "assets/starter_pop.wav", + "kind": "audio", + "metadata": "", + "name": "assets/starter_pop.wav", + "preloadAsMusic": false, + "preloadAsSound": false, + "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/House 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/House 1.glb", + "userAdded": true + }, + { + "file": "assets/House 2.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/House 2.glb", + "userAdded": true + }, + { + "file": "assets/Inn.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Inn.glb", + "userAdded": true + }, + { + "file": "assets/Blacksmith.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Blacksmith.glb", + "userAdded": true + }, + { + "file": "assets/Well.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Well.glb", + "userAdded": true + }, + { + "file": "assets/Market Stand 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Market Stand 1.glb", + "userAdded": true + }, + { + "file": "assets/Dock Long.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Dock Long.glb", + "userAdded": true + }, + { + "file": "assets/Common Tree 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Common Tree 1.glb", + "userAdded": true + }, + { + "file": "assets/Pine Tree 2.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Pine Tree 2.glb", + "userAdded": true + }, + { + "file": "assets/Rock Moss 2.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Rock Moss 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.5843784410741105, + "windowMask": false, + "selectedLayer": "", + "gameEditorMode": "embedded-game" + }, + "objectsGroups": [], + "variables": [], + "instances": [ + { + "angle": 0, + "customSize": true, + "depth": 32, + "height": 192, + "keepRatio": true, + "layer": "Dialog layer", + "locked": true, + "name": "TwoChoicesDialogBox", + "persistentUuid": "ff7316fe-3b04-4ad5-8194-042724f9f18d", + "width": 640, + "x": 320, + "y": 448, + "zOrder": 8, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "MobileControls", + "locked": true, + "name": "MovementJoystick", + "persistentUuid": "f45d2c95-0d48-4da5-a5a5-75c6f88a1cb4", + "width": 0, + "x": 176, + "y": 576, + "zOrder": 22, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 0, + "height": 104, + "keepRatio": true, + "layer": "MobileControls", + "locked": true, + "name": "ActionButton", + "persistentUuid": "0133b683-d9f1-45f0-947e-5511772fc059", + "width": 104, + "x": 1104, + "y": 576, + "zOrder": 23, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 4096, + "width": 4096, + "layer": "", + "locked": false, + "name": "Terrain", + "persistentUuid": "62097ce0-cfca-53ae-ac5c-17589a064470", + "x": -2048, + "y": -2048, + "z": -80, + "zOrder": 0, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 900 + }, + { + "angle": 0, + "customSize": true, + "height": 4096, + "width": 900, + "layer": "", + "locked": false, + "name": "Water", + "persistentUuid": "78505cbd-9815-5a48-b1c9-16008a95fc87", + "x": 300, + "y": -2048, + "z": -30, + "zOrder": 2, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 1 + }, + { + "angle": 0, + "customSize": true, + "height": 300, + "width": 4000, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "6d436398-064b-5e63-b838-9511e5a93332", + "x": -2000, + "y": -2000, + "z": -500, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 4000 + }, + { + "angle": 0, + "customSize": true, + "height": 300, + "width": 4000, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "71827827-a543-54a5-a9a7-d10e082abe13", + "x": -2000, + "y": 1700, + "z": -500, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 4000 + }, + { + "angle": 0, + "customSize": true, + "height": 3400, + "width": 300, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "dead7b9b-7dda-5407-b9b6-b45342bca9f3", + "x": -2000, + "y": -1700, + "z": -500, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 4000 + }, + { + "angle": 0, + "customSize": true, + "height": 3400, + "width": 300, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "a1d7bf4e-1b4a-597d-86a2-2aadd1ee886a", + "x": 1700, + "y": -1700, + "z": -500, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 4000 + }, + { + "angle": 0, + "customSize": true, + "height": 4096, + "width": 4096, + "layer": "", + "locked": false, + "name": "Grass", + "persistentUuid": "7bcf0b18-0176-57e5-b8c9-caa2c50e972e", + "x": -2048, + "y": -2048, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 40 + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "House", + "persistentUuid": "5f79bcd0-981f-57c3-bba5-392953090c58", + "x": -700, + "y": -150, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "HouseLarge", + "persistentUuid": "7ff02a6c-0a65-558c-914e-317e053a0dd3", + "x": -650, + "y": 450, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Inn", + "persistentUuid": "80624d41-45be-5a58-afc9-28c6cc6d2999", + "x": 100, + "y": -300, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Blacksmith", + "persistentUuid": "2e1404e3-7316-5048-9dca-d468bcf66007", + "x": 50, + "y": 600, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "House", + "persistentUuid": "31b2752f-4131-57ef-8672-1fb4e2d3e81b", + "x": -350, + "y": -450, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Well", + "persistentUuid": "253bcc3a-a466-5beb-9c76-333f089ad61e", + "x": -300, + "y": 200, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "MarketStand", + "persistentUuid": "ba361044-19e2-5545-8167-f7667feb9bb5", + "x": -100, + "y": 330, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Bridge", + "persistentUuid": "4caecd92-2ce7-5f4b-9104-6a5fbc3eebad", + "x": 780, + "y": 0, + "z": -87.8, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Player", + "persistentUuid": "e9c4bc87-5b2d-599a-baa8-3884c1be277f", + "x": -300, + "y": 0, + "z": 0, + "zOrder": 3, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": -90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": true, + "name": "Camera", + "persistentUuid": "91ad53f5-f603-59b1-b69d-80c81d235fd7", + "x": -364, + "y": 649, + "z": 850, + "zOrder": 9, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "rotationY": 55 + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "NPC", + "persistentUuid": "03d73031-c6e2-5388-a94c-40539d6db444", + "x": -480, + "y": 250, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "NPC", + "persistentUuid": "cb9d8769-683c-5a33-8903-00e14b09d57d", + "x": 150, + "y": 150, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "NPC", + "persistentUuid": "4639a18e-9cdc-5e2c-9d69-2a6c3eab8b83", + "x": -150, + "y": -700, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "NPC", + "persistentUuid": "bc924bce-1e82-5aa0-8bbd-bd32bc3e47f8", + "x": 1300, + "y": -350, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 192, + "width": 192, + "layer": "", + "locked": false, + "name": "Building_Obstacle", + "persistentUuid": "e5e5b6a3-b585-52a5-b779-f927e9fd39a5", + "x": 1500, + "y": 600, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 192 + }, + { + "angle": 0, + "customSize": true, + "height": 4096, + "width": 4096, + "layer": "", + "locked": true, + "name": "Trees", + "persistentUuid": "f009daf5-1e9b-53e5-9f42-dd873deaecfe", + "x": -2048, + "y": -2048, + "z": -80, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 4096, + "width": 4096, + "layer": "", + "locked": true, + "name": "PineTrees", + "persistentUuid": "b258e967-0b09-525b-8f34-18009930d782", + "x": -2048, + "y": -2048, + "z": -80, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 4096, + "width": 4096, + "layer": "", + "locked": true, + "name": "Rocks", + "persistentUuid": "8a0b631a-72c4-5c53-863c-2d23c17f81de", + "x": -2048, + "y": -2048, + "z": -80, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 95.5, + "customSize": true, + "height": 40, + "width": 1392.760181617827, + "layer": "", + "locked": false, + "name": "RiverBank", + "persistentUuid": "d7bf1107-44d6-529b-8f80-21225a22592f", + "x": 152.7, + "y": -1525, + "z": -150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 450 + }, + { + "angle": 95.5, + "customSize": true, + "height": 40, + "width": 1392.7601816178271, + "layer": "", + "locked": false, + "name": "RiverBank", + "persistentUuid": "6a93dc8d-9594-5393-8251-e46c8f4f6ea8", + "x": -245.5, + "y": -1563, + "z": -150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 450 + }, + { + "angle": 78.7, + "customSize": true, + "height": 40, + "width": 1178.417629528258, + "layer": "", + "locked": false, + "name": "RiverBank", + "persistentUuid": "949a6471-8e03-5017-9222-05c362755c24", + "x": 289.1, + "y": -648.2, + "z": -150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 450 + }, + { + "angle": 78.7, + "customSize": true, + "height": 40, + "width": 1098.417629528258, + "layer": "", + "locked": false, + "name": "RiverBank", + "persistentUuid": "6048175e-34a1-5e91-a382-9420fec9e85d", + "x": -71, + "y": -608.9, + "z": -150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 450 + }, + { + "angle": 99.5, + "customSize": true, + "height": 40, + "width": 1064.7821500245868, + "layer": "", + "locked": false, + "name": "RiverBank", + "persistentUuid": "11201631-13c9-5ad1-98a4-aff9ea082c33", + "x": 374.5, + "y": 555.4, + "z": -150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 450 + }, + { + "angle": 99.5, + "customSize": true, + "height": 40, + "width": 998.1154833579202, + "layer": "", + "locked": false, + "name": "RiverBank", + "persistentUuid": "7ab79028-c090-57de-a4ff-cdec24e8b8cd", + "x": 7.8, + "y": 522.5, + "z": -150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 450 + }, + { + "angle": 85, + "customSize": true, + "height": 40, + "width": 1492.3471699101794, + "layer": "", + "locked": false, + "name": "RiverBank", + "persistentUuid": "860eff41-4c67-522a-8c0e-bc384740af04", + "x": 153.1, + "y": 1436.6, + "z": -150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 450 + }, + { + "angle": 85, + "customSize": true, + "height": 40, + "width": 1492.3471699101797, + "layer": "", + "locked": false, + "name": "RiverBank", + "persistentUuid": "0d908b92-ec80-5838-a256-6d9e807d2e28", + "x": -245.4, + "y": 1471.4, + "z": -150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 450 + }, + { + "angle": 0, + "customSize": true, + "height": 40, + "width": 449.0528648869854, + "layer": "", + "locked": false, + "name": "RiverBank", + "persistentUuid": "1a794d74-6675-5b83-961d-355d4da73d66", + "x": 562, + "y": -110, + "z": -150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 450 + }, + { + "angle": 0, + "customSize": true, + "height": 40, + "width": 449.0528648869854, + "layer": "", + "locked": false, + "name": "RiverBank", + "persistentUuid": "5e54f5b4-ee94-5358-8068-7642ce544808", + "x": 562, + "y": 70, + "z": -150, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 450 + }, + { + "angle": 0, + "customSize": true, + "height": 4096, + "width": 4096, + "layer": "", + "locked": true, + "name": "GrassTufts", + "persistentUuid": "0089c3e9-7869-52c5-8ca4-3d7b0aa169c4", + "x": -2048, + "y": -2048, + "z": -80, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + } + ], + "objects": [ + { + "assetStoreId": "9d5de415b6e469c681701ea42d3b1c486fee31d6ef591a2d3e226f639ab486ff", + "name": "TwoChoicesDialogBox", + "persistentUuid": "26ac36eb-00ba-493a-9b9e-f5a513e5e4f1", + "type": "TwoChoicesDialogBoxes::TwoChoicesDialogBox", + "variant": "DialogBox", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "LabelButtonID0": "Yes", + "TextMessage": "Want me to go away?", + "EnableEscape": false + } + }, + { + "assetStoreId": "", + "name": "Camera", + "persistentUuid": "ca864da0-f02a-4478-af66-73e95de350bf", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 128, + "height": 128, + "depth": 128, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "frontFaceResourceName": "assets\\StartingCameraTop.png", + "backFaceResourceName": "assets\\StartingCameraBody.png", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "assets\\StartingCameraBody.png", + "rightFaceResourceName": "assets\\StartingCameraEye.png", + "topFaceResourceName": "assets\\StartingCameraBody.png", + "bottomFaceResourceName": "assets\\StartingCameraBody.png", + "frontFaceVisible": true, + "backFaceVisible": true, + "leftFaceVisible": true, + "rightFaceVisible": true, + "topFaceVisible": true, + "bottomFaceVisible": true, + "frontFaceResourceRepeat": false, + "backFaceResourceRepeat": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceRepeat": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceRepeat": false, + "materialType": "Basic", + "tint": "255;255;255", + "isCastingShadow": false, + "isReceivingShadow": false + } + }, + { + "assetStoreId": "", + "name": "Player", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "isFolded": true, + "name": "SmoothCamera", + "type": "SmoothCamera::SmoothCamera", + "LeftwardSpeed": 0.9, + "RightwardSpeed": 0.9, + "UpwardSpeed": 0.9, + "DownwardSpeed": 0.9, + "FollowOnX": true, + "FollowOnY": true, + "FollowFreeAreaLeft": 0, + "FollowFreeAreaRight": 0, + "FollowFreeAreaTop": 0, + "FollowFreeAreaBottom": 0, + "CameraOffsetX": 0, + "CameraOffsetY": 600, + "CameraDelay": 0, + "ForecastTime": 0, + "ForecastHistoryDuration": 0, + "LogLeftwardSpeed": 2.0247e-320, + "LogRightwardSpeed": 2.0247e-320, + "LogDownwardSpeed": 2.0247e-320, + "LogUpwardSpeed": 2.0247e-320, + "DelayedCenterX": 2.0247e-320, + "DelayedCenterY": 2.0247e-320, + "ForecastHistoryMeanX": 2.0247e-320, + "ForecastHistoryMeanY": 2.0247e-320, + "ForecastHistoryVarianceX": 2.0247e-320, + "ForecastHistoryCovariance": 2.0247e-320, + "ForecastHistoryLinearA": 2.0247e-320, + "ForecastHistoryLinearB": 2.0247e-320, + "ForecastedX": 2.0247e-320, + "ForecastedY": 2.0247e-320, + "ProjectedNewestX": 2.0247e-320, + "ProjectedNewestY": 2.0247e-320, + "ProjectedOldestX": 2.0247e-320, + "ProjectedOldestY": 2.0247e-320, + "ForecastHistoryVarianceY": 2.0247e-320, + "Index": 2.0247e-320, + "CameraDelayCatchUpSpeed": 0, + "CameraExtraDelay": 2.0247e-320, + "WaitingSpeedXMax": 2.0247e-320, + "WaitingSpeedYMax": 2.0247e-320, + "WaitingEnd": 2.0247e-320, + "CameraDelayCatchUpDuration": 2.0247e-320, + "LeftwardSpeedMax": 9000, + "RightwardSpeedMax": 9000, + "UpwardSpeedMax": 9000, + "DownwardSpeedMax": 9000, + "OldX": 9000.000000007454, + "OldY": 9000.000000007454, + "IsCalledManually": false + }, + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Dynamic", + "bullet": false, + "fixedRotation": false, + "shape": "Capsule", + "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, + "isFolded": true + }, + { + "isFolded": true, + "name": "PhysicsCharacter3D", + "type": "Physics3D::PhysicsCharacter3D", + "physics3D": "Physics3D", + "jumpHeight": 80, + "jumpSustainTime": 0.2, + "gravity": 1000, + "fallingSpeedMax": 600, + "forwardAcceleration": 2000, + "forwardDeceleration": 4000, + "forwardSpeedMax": 300, + "sidewaysAcceleration": 2000, + "sidewaysDeceleration": 4000, + "sidewaysSpeedMax": 300, + "slopeMaxAngle": 50, + "stairHeightMax": 20, + "shouldBindObjectAndForwardAngle": true, + "canBePushed": false + }, + { + "isFolded": true, + "name": "Platformer3DKeyboardMapper", + "type": "PhysicsCharacter3DKeyMapper::Platformer3DKeyboardMapper", + "PhysicsCharacter3D": "PhysicsCharacter3D", + "Jump": "", + "Up": "Up", + "Left": "Left", + "Down": "Down", + "Right": "Right", + "CameraAngle": 0, + "LockedCameraAngle": false + }, + { + "isFolded": true, + "name": "Platformer3DMultitouchMapper", + "type": "SpriteMultitouchJoystick::Platformer3DMultitouchMapper", + "PhysicsCharacter3D": "PhysicsCharacter3D", + "ControllerIdentifier": 1, + "JoystickIdentifier": "Primary", + "JumpButton": "" + } + ], + "content": { + "centerLocation": "ModelOrigin", + "crossfadeDuration": 0.10000000149011612, + "depth": 139, + "height": 64, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets\\starting_unit_orange.glb", + "originLocation": "ModelOrigin", + "rotationX": 0, + "rotationY": 0, + "rotationZ": 0, + "width": 64, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "NPC", + "persistentUuid": "354d6e82-38d7-4c08-a801-cd9c6c3adb37", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "centerLocation": "ObjectCenter", + "crossfadeDuration": 0.10000000149011612, + "depth": 138.67, + "height": 64, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets\\starting_unit_green.glb", + "originLocation": "ModelOrigin", + "rotationX": 0, + "rotationY": 0, + "rotationZ": 0, + "width": 64, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "Building_Obstacle", + "persistentUuid": "5169d11c-2c06-4b3c-820f-5559751fc212", + "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": 128, + "height": 128, + "depth": 128, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "frontFaceResourceName": "assets\\StartingWall.png", + "backFaceResourceName": "assets\\StartingWall.png", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "assets\\StartingWall.png", + "rightFaceResourceName": "assets\\StartingWall.png", + "topFaceResourceName": "assets\\StartingWall.png", + "bottomFaceResourceName": "assets\\StartingWall.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 + } + }, + { + "adaptCollisionMaskAutomatically": true, + "assetStoreId": "", + "name": "ActionButton", + "persistentUuid": "458751d3-3821-4d05-9235-3c9ffb34e3b5", + "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.3, + "FocusedScale": 1.25, + "FadeInDuration": 0.02, + "FadeOutDuration": 0.05, + "PressedScale": 1.2, + "FadeInEasing": "easeOutQuad", + "FadeOutEasing": "easeInQuad", + "PreviousState": "Idle" + }, + { + "name": "Tween", + "type": "Tween::TweenBehavior" + } + ], + "animations": [ + { + "name": "", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": true, + "image": "assets\\A 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": "e71bd69f896d6c7531b48c65ceb5da25071d4fbdeb518aeceecba8d21f34ed8d", + "name": "MovementJoystick", + "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": "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": "Debris", + "persistentUuid": "7be91ac9-81bc-4626-9f2f-0d269ae7645e", + "type": "ParticleEmitter3D::ParticleEmitter3D", + "variant": "", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "EndColor": "255;255;255", + "StartColor": "109;109;109", + "SpayConeAngle": 360, + "EndScale": 1, + "Gravity": 600, + "StartSpeedMax": 500, + "StartSizeMin": 32, + "Duration": 0.01, + "Flow": 2000, + "LifespanMin": 0.075, + "LifespanMax": 0.25, + "StartSizeMax": 32, + "StartSpeedMin": 300 + }, + "childrenContent": { + "Particle": { + "adaptCollisionMaskAutomatically": true, + "updateIfNotVisible": false, + "animations": [ + { + "name": "Image", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": true, + "image": "assets/DirtParticle.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 0, + "y": 0 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [ + [ + { + "x": 0, + "y": 0 + }, + { + "x": 32, + "y": 0 + }, + { + "x": 32, + "y": 32 + }, + { + "x": 0, + "y": 32 + } + ] + ] + } + ] + } + ] + } + ] + } + } + }, + { + "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": "128", + "TextureSize": 300, + "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.0889},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.0889},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.0889},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.0889},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.0889},{\"tool\":\"raise\",\"x\":0,\"y\":0,\"toX\":1,\"toY\":0,\"radius\":0.127,\"height\":0.4667},{\"tool\":\"raise\",\"x\":0,\"y\":1,\"toX\":1,\"toY\":1,\"radius\":0.127,\"height\":0.4667},{\"tool\":\"raise\",\"x\":0,\"y\":0,\"toX\":0,\"toY\":1,\"radius\":0.127,\"height\":0.4667},{\"tool\":\"raise\",\"x\":1,\"y\":0,\"toX\":1,\"toY\":1,\"radius\":0.127,\"height\":0.4667},{\"tool\":\"raise\",\"x\":0.2314,\"y\":0.2559,\"radius\":0.1465,\"height\":0.1778},{\"tool\":\"raise\",\"x\":0.7686,\"y\":0.2314,\"radius\":0.1343,\"height\":0.1556},{\"tool\":\"raise\",\"x\":0.793,\"y\":0.7686,\"radius\":0.1221,\"height\":0.1333},{\"tool\":\"raise\",\"x\":0.1826,\"y\":0.7441,\"radius\":0.1099,\"height\":0.1222},{\"tool\":\"raise\",\"x\":0.5,\"y\":0.1338,\"radius\":0.0854,\"height\":0.0889},{\"tool\":\"smooth\",\"x\":0.5,\"y\":0.5,\"radius\":0.4639,\"strength\":0.3},{\"tool\":\"flatten\",\"x\":0.4268,\"y\":0.5488,\"radius\":0.1587,\"z\":0.0889},{\"tool\":\"flatten\",\"x\":0.4268,\"y\":0.5488,\"radius\":0.1587,\"z\":0.0889},{\"tool\":\"flatten\",\"x\":0.4268,\"y\":0.5488,\"radius\":0.1587,\"z\":0.0889},{\"tool\":\"flatten\",\"x\":0.4268,\"y\":0.5488,\"radius\":0.1587,\"z\":0.0889},{\"tool\":\"flatten\",\"x\":0.6709,\"y\":0,\"toX\":0.6465,\"toY\":0.2559,\"radius\":0.0464,\"z\":0.0278},{\"tool\":\"flatten\",\"x\":0.6709,\"y\":0,\"toX\":0.6465,\"toY\":0.2559,\"radius\":0.0464,\"z\":0.0278},{\"tool\":\"paint\",\"x\":0.6709,\"y\":0,\"toX\":0.6465,\"toY\":0.2559,\"radius\":0.0562,\"layer\":4,\"strength\":0.9},{\"tool\":\"flatten\",\"x\":0.6465,\"y\":0.2559,\"toX\":0.6953,\"toY\":0.5,\"radius\":0.0464,\"z\":0.0278},{\"tool\":\"flatten\",\"x\":0.6465,\"y\":0.2559,\"toX\":0.6953,\"toY\":0.5,\"radius\":0.0464,\"z\":0.0278},{\"tool\":\"paint\",\"x\":0.6465,\"y\":0.2559,\"toX\":0.6953,\"toY\":0.5,\"radius\":0.0562,\"layer\":4,\"strength\":0.9},{\"tool\":\"flatten\",\"x\":0.6953,\"y\":0.5,\"toX\":0.6587,\"toY\":0.7197,\"radius\":0.0464,\"z\":0.0278},{\"tool\":\"flatten\",\"x\":0.6953,\"y\":0.5,\"toX\":0.6587,\"toY\":0.7197,\"radius\":0.0464,\"z\":0.0278},{\"tool\":\"paint\",\"x\":0.6953,\"y\":0.5,\"toX\":0.6587,\"toY\":0.7197,\"radius\":0.0562,\"layer\":4,\"strength\":0.9},{\"tool\":\"flatten\",\"x\":0.6587,\"y\":0.7197,\"toX\":0.6831,\"toY\":1,\"radius\":0.0464,\"z\":0.0278},{\"tool\":\"flatten\",\"x\":0.6587,\"y\":0.7197,\"toX\":0.6831,\"toY\":1,\"radius\":0.0464,\"z\":0.0278},{\"tool\":\"paint\",\"x\":0.6587,\"y\":0.7197,\"toX\":0.6831,\"toY\":1,\"radius\":0.0562,\"layer\":4,\"strength\":0.9},{\"tool\":\"paint\",\"x\":0.4268,\"y\":0.5488,\"toX\":0.3838,\"toY\":0.4612,\"radius\":0.0176,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3838,\"y\":0.4612,\"toX\":0.3357,\"toY\":0.3762,\"radius\":0.0176,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3357,\"y\":0.3762,\"toX\":0.2813,\"toY\":0.2964,\"radius\":0.0176,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.2813,\"y\":0.2964,\"toX\":0.3174,\"toY\":0.2087,\"radius\":0.0176,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3174,\"y\":0.2087,\"toX\":0.3779,\"toY\":0.1338,\"radius\":0.0176,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.4268,\"y\":0.5488,\"toX\":0.5227,\"toY\":0.5303,\"radius\":0.0176,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5227,\"y\":0.5303,\"toX\":0.6169,\"toY\":0.5046,\"radius\":0.0176,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.6169,\"y\":0.5046,\"toX\":0.6318,\"toY\":0.5,\"radius\":0.0176,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.749,\"y\":0.5,\"toX\":0.8159,\"toY\":0.4294,\"radius\":0.0176,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8159,\"y\":0.4294,\"toX\":0.8494,\"toY\":0.3379,\"radius\":0.0176,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.8494,\"y\":0.3379,\"toX\":0.8662,\"toY\":0.2803,\"radius\":0.0176,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.4268,\"y\":0.5488,\"toX\":0.3638,\"toY\":0.6235,\"radius\":0.0161,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3638,\"y\":0.6235,\"toX\":0.3076,\"toY\":0.7034,\"radius\":0.0161,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3076,\"y\":0.7034,\"toX\":0.26,\"toY\":0.7881,\"radius\":0.0161,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.26,\"y\":0.7881,\"toX\":0.2949,\"toY\":0.8711,\"radius\":0.0161,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.2949,\"y\":0.8711,\"toX\":0.3535,\"toY\":0.915,\"radius\":0.0161,\"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": "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": "40;110;140" + } + }, + { + "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": 20, + "BladeWidth": 2, + "BaseColor": "40;62;22", + "TipColor": "112;130;52", + "FadeDistance": 2500 + } + }, + { + "assetStoreId": "0b4ae6f11461685adc55c64ccbf36fe05a5c2067ef93bc4e2ea22410531d9993", + "name": "House", + "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": 380, + "height": 298, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/House 1.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 240.3, + "animations": [] + } + }, + { + "assetStoreId": "03e458788d22ceedea2397464d1d551351a136ce4e77e844391469aae3f55613", + "name": "HouseLarge", + "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": 380.2, + "height": 400, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/House 2.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 259.7, + "animations": [] + } + }, + { + "assetStoreId": "23b17fb3a9b81fd67983298b39b06273d78fb4dc11275e075fce809e43a71bde", + "name": "Inn", + "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": 398.6, + "height": 459.1, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Inn.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 460, + "animations": [] + } + }, + { + "assetStoreId": "ff7291456c107c4a8f186ea3bcf473b481b7d81e3ca7a3efb0d6dea2cc81a79e", + "name": "Blacksmith", + "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": 324.3, + "height": 354.5, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Blacksmith.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 420, + "animations": [] + } + }, + { + "assetStoreId": "4cf7468b15ae3df2a5f089ba58a880854859564df6f416dd55c42883c86fecd1", + "name": "Well", + "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": 150, + "height": 120.4, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Well.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 79.9, + "animations": [] + } + }, + { + "assetStoreId": "fc133724c50164f0b053ee6d1d478df59cbb6e595c04bf66c3372c5e139825a1", + "name": "MarketStand", + "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": 176.4, + "height": 200, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Market Stand 1.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 159.8, + "animations": [] + } + }, + { + "assetStoreId": "dfbff992befff7266b9aa1c0906a8ad4881c1aa3955bc56c05c7c4d4b0580f9a", + "name": "Bridge", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Mesh", + "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": 172.7, + "height": 203.9, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Dock Long.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 90, + "width": 480, + "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/Common Tree 1.glb", + "Size": 380, + "Density": 0.05, + "Seed": 1, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.439,\"y\":0.5366,\"radius\":0.1831},{\"tool\":\"erase\",\"x\":0.6709,\"y\":0,\"toX\":0.6709,\"toY\":1,\"radius\":0.0806},{\"tool\":\"erase\",\"x\":0.3291,\"y\":0.4634,\"radius\":0.0461},{\"tool\":\"erase\",\"x\":0.3413,\"y\":0.6099,\"radius\":0.0586},{\"tool\":\"erase\",\"x\":0.5244,\"y\":0.4268,\"radius\":0.0659},{\"tool\":\"erase\",\"x\":0.5122,\"y\":0.6465,\"radius\":0.061},{\"tool\":\"erase\",\"x\":0.4146,\"y\":0.3901,\"radius\":0.0461},{\"tool\":\"erase\",\"x\":0.4268,\"y\":0.5488,\"radius\":0.0245},{\"tool\":\"erase\",\"x\":0.4756,\"y\":0.5806,\"radius\":0.0342},{\"tool\":\"erase\",\"x\":0.6904,\"y\":0.5,\"radius\":0.0684},{\"tool\":\"erase\",\"x\":0.3828,\"y\":0.561,\"radius\":0.0176},{\"tool\":\"erase\",\"x\":0.5366,\"y\":0.5366,\"radius\":0.0176},{\"tool\":\"erase\",\"x\":0.4634,\"y\":0.3291,\"radius\":0.0176},{\"tool\":\"erase\",\"x\":0.8174,\"y\":0.4146,\"radius\":0.0176},{\"tool\":\"erase\",\"x\":0.8662,\"y\":0.6465,\"radius\":0.0332}]", + "MaxSlope": 33, + "TrunkCollisions": true, + "TrunkDiameter": 30 + } + }, + { + "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/Pine Tree 2.glb", + "Size": 400, + "Density": 0.05, + "Seed": 2, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.439,\"y\":0.5366,\"radius\":0.1831},{\"tool\":\"erase\",\"x\":0.6709,\"y\":0,\"toX\":0.6709,\"toY\":1,\"radius\":0.0806},{\"tool\":\"erase\",\"x\":0.3291,\"y\":0.4634,\"radius\":0.0461},{\"tool\":\"erase\",\"x\":0.3413,\"y\":0.6099,\"radius\":0.0586},{\"tool\":\"erase\",\"x\":0.5244,\"y\":0.4268,\"radius\":0.0659},{\"tool\":\"erase\",\"x\":0.5122,\"y\":0.6465,\"radius\":0.061},{\"tool\":\"erase\",\"x\":0.4146,\"y\":0.3901,\"radius\":0.0461},{\"tool\":\"erase\",\"x\":0.4268,\"y\":0.5488,\"radius\":0.0245},{\"tool\":\"erase\",\"x\":0.4756,\"y\":0.5806,\"radius\":0.0342},{\"tool\":\"erase\",\"x\":0.6904,\"y\":0.5,\"radius\":0.0684},{\"tool\":\"erase\",\"x\":0.3828,\"y\":0.561,\"radius\":0.0176},{\"tool\":\"erase\",\"x\":0.5366,\"y\":0.5366,\"radius\":0.0176},{\"tool\":\"erase\",\"x\":0.4634,\"y\":0.3291,\"radius\":0.0176},{\"tool\":\"erase\",\"x\":0.8174,\"y\":0.4146,\"radius\":0.0176},{\"tool\":\"erase\",\"x\":0.8662,\"y\":0.6465,\"radius\":0.0332}]", + "MaxSlope": 33, + "TrunkCollisions": true, + "TrunkDiameter": 30 + } + }, + { + "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 Moss 2.glb", + "Size": 150, + "Density": 0.012, + "Seed": 3, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.439,\"y\":0.5366,\"radius\":0.1831},{\"tool\":\"erase\",\"x\":0.6709,\"y\":0,\"toX\":0.6709,\"toY\":1,\"radius\":0.0806},{\"tool\":\"erase\",\"x\":0.3291,\"y\":0.4634,\"radius\":0.0461},{\"tool\":\"erase\",\"x\":0.3413,\"y\":0.6099,\"radius\":0.0586},{\"tool\":\"erase\",\"x\":0.5244,\"y\":0.4268,\"radius\":0.0659},{\"tool\":\"erase\",\"x\":0.5122,\"y\":0.6465,\"radius\":0.061},{\"tool\":\"erase\",\"x\":0.4146,\"y\":0.3901,\"radius\":0.0461},{\"tool\":\"erase\",\"x\":0.4268,\"y\":0.5488,\"radius\":0.0245},{\"tool\":\"erase\",\"x\":0.4756,\"y\":0.5806,\"radius\":0.0342},{\"tool\":\"erase\",\"x\":0.6904,\"y\":0.5,\"radius\":0.0684},{\"tool\":\"erase\",\"x\":0.3828,\"y\":0.561,\"radius\":0.0176},{\"tool\":\"erase\",\"x\":0.5366,\"y\":0.5366,\"radius\":0.0176},{\"tool\":\"erase\",\"x\":0.4634,\"y\":0.3291,\"radius\":0.0176},{\"tool\":\"erase\",\"x\":0.8174,\"y\":0.4146,\"radius\":0.0176},{\"tool\":\"erase\",\"x\":0.8662,\"y\":0.6465,\"radius\":0.0332}]", + "MaxSlope": 38, + "TrunkCollisions": true, + "TrunkDiameter": 90 + } + }, + { + "assetStoreId": "", + "name": "RiverBank", + "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": "GrassTufts", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Model": "assets\\grass_multiple_blades.glb", + "Size": 40, + "Density": 0.03, + "Seed": 4, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.6709,\"y\":0,\"toX\":0.6709,\"toY\":1,\"radius\":0.0806},{\"tool\":\"erase\",\"x\":0.3291,\"y\":0.4634,\"radius\":0.0461},{\"tool\":\"erase\",\"x\":0.3413,\"y\":0.6099,\"radius\":0.0586},{\"tool\":\"erase\",\"x\":0.5244,\"y\":0.4268,\"radius\":0.0659},{\"tool\":\"erase\",\"x\":0.5122,\"y\":0.6465,\"radius\":0.061},{\"tool\":\"erase\",\"x\":0.4146,\"y\":0.3901,\"radius\":0.0461},{\"tool\":\"erase\",\"x\":0.4268,\"y\":0.5488,\"radius\":0.0245},{\"tool\":\"erase\",\"x\":0.4756,\"y\":0.5806,\"radius\":0.0342},{\"tool\":\"erase\",\"x\":0.6904,\"y\":0.5,\"radius\":0.0684},{\"tool\":\"erase\",\"x\":0.3828,\"y\":0.561,\"radius\":0.0176},{\"tool\":\"erase\",\"x\":0.5366,\"y\":0.5366,\"radius\":0.0176},{\"tool\":\"erase\",\"x\":0.4634,\"y\":0.3291,\"radius\":0.0176},{\"tool\":\"erase\",\"x\":0.8174,\"y\":0.4146,\"radius\":0.0176},{\"tool\":\"erase\",\"x\":0.8662,\"y\":0.6465,\"radius\":0.0332}]", + "CastShadow": false, + "DrawDistance": 3000, + "MaxSlope": 40 + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Player" + }, + { + "objectName": "NPC" + }, + { + "objectName": "Debris" + }, + { + "objectName": "Building_Obstacle" + }, + { + "objectName": "Camera" + }, + { + "objectName": "TwoChoicesDialogBox" + }, + { + "folderName": "Mobile Controls", + "children": [ + { + "objectName": "MovementJoystick" + }, + { + "objectName": "ActionButton" + } + ] + }, + { + "folderName": "World", + "children": [ + { + "objectName": "Terrain" + }, + { + "objectName": "Water" + }, + { + "objectName": "MapLimit" + }, + { + "objectName": "Grass" + }, + { + "objectName": "RiverBank" + } + ] + }, + { + "folderName": "Village", + "children": [ + { + "objectName": "House" + }, + { + "objectName": "HouseLarge" + }, + { + "objectName": "Inn" + }, + { + "objectName": "Blacksmith" + }, + { + "objectName": "Well" + }, + { + "objectName": "MarketStand" + }, + { + "objectName": "Bridge" + } + ] + }, + { + "folderName": "Nature", + "children": [ + { + "objectName": "Trees" + }, + { + "objectName": "PineTrees" + }, + { + "objectName": "Rocks" + }, + { + "objectName": "GrassTufts" + } + ] + } + ] + }, + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "FirstPersonCamera::LookFrom3DObjectEyes" + }, + "parameters": [ + "", + "Camera", + "Object3D", + "", + "" + ] + }, + { + "type": { + "value": "Delete" + }, + "parameters": [ + "Camera", + "" + ] + }, + { + "type": { + "value": "HideLayer" + }, + "parameters": [ + "", + "\"Dialog layer\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "LayerVisible" + }, + "parameters": [ + "", + "\"Dialog layer\"" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CollisionNP" + }, + "parameters": [ + "Player", + "NPC", + "", + "", + "" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "KeyFromTextPressed" + }, + "parameters": [ + "", + "\"x\"" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonFSM::IsClicked" + }, + "parameters": [ + "ActionButton", + "ButtonFSM", + "" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "Player", + "Platformer3DKeyboardMapper", + "" + ] + }, + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "Player", + "Platformer3DMultitouchMapper", + "" + ] + }, + { + "type": { + "value": "ShowLayer" + }, + "parameters": [ + "", + "\"Dialog layer\"" + ] + }, + { + "type": { + "value": "PlaySound" + }, + "parameters": [ + "", + "assets/starting_bookopen.wav", + "", + "80", + "1.1" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "LayerVisible" + }, + "parameters": [ + "", + "\"Dialog layer\"" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "TwoChoicesDialogBoxes::TwoChoicesDialogBox::IsYesClicked" + }, + "parameters": [ + "TwoChoicesDialogBox", + "" + ] + }, + { + "type": { + "value": "PickNearest" + }, + "parameters": [ + "NPC", + "Player.X()", + "Player.Y()", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Create" + }, + "parameters": [ + "", + "Debris", + "NPC.CenterX()", + "NPC.CenterY()", + "" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetCenterZ" + }, + "parameters": [ + "Debris", + "Object3D", + "=", + "NPC.Object3D::CenterZ()" + ] + }, + { + "type": { + "value": "PlaySound" + }, + "parameters": [ + "", + "assets/starter_pop.wav", + "", + "80", + "RandomFloatInRange(0.7,0.9)" + ] + }, + { + "type": { + "value": "Delete" + }, + "parameters": [ + "NPC", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "TwoChoicesDialogBoxes::TwoChoicesDialogBox::IsNoClicked" + }, + "parameters": [ + "TwoChoicesDialogBox", + "" + ] + }, + { + "type": { + "value": "TwoChoicesDialogBoxes::TwoChoicesDialogBox::IsYesClicked" + }, + "parameters": [ + "TwoChoicesDialogBox", + "" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "Player", + "Platformer3DKeyboardMapper", + "yes" + ] + }, + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "Player", + "Platformer3DMultitouchMapper", + "yes" + ] + }, + { + "type": { + "value": "HideLayer" + }, + "parameters": [ + "", + "\"Dialog layer\"" + ] + }, + { + "type": { + "value": "PlaySound" + }, + "parameters": [ + "", + "assets/starting_bookopen.wav", + "", + "80", + "0.8" + ] + } + ] + } + ] + } + ], + "layers": [ + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "perspective", + "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": 2000, + "elevation": 40, + "frustumSize": 3500, + "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::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": {} + } + ] + }, + { + "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": [] + }, + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "camera3DFieldOfView": 45, + "camera3DNearPlaneDistance": 3, + "cameraType": "", + "followBaseLayerCamera": false, + "isLightingLayer": false, + "isLocked": false, + "name": "Dialog layer", + "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": "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": "SmoothCamera", + "type": "SmoothCamera::SmoothCamera" + }, + { + "name": "Text", + "type": "TextContainerCapability::TextContainerBehavior" + }, + { + "name": "Tween", + "type": "Tween::TweenBehavior" + } + ] + } + ], + "externalEvents": [], + "eventsFunctionsExtensions": [ + { + "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": "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": "User interface", + "dimension": "", + "extensionNamespace": "", + "fullName": "Two choices dialog boxes", + "gdevelopVersion": ">=5.5.230", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPGxpbmUgY2xhc3M9InN0MCIgeDE9IjMiIHkxPSIxMSIgeDI9IjI5IiB5Mj0iMTEiLz4NCjxnPg0KCTxwYXRoIGQ9Ik03LDlDNi43LDksNi41LDguOSw2LjMsOC43QzYuMSw4LjUsNiw4LjMsNiw4YzAtMC4zLDAuMS0wLjUsMC4zLTAuN2MwLDAsMC4xLTAuMSwwLjEtMC4xYzAuMSwwLDAuMS0wLjEsMC4yLTAuMQ0KCQlDNi43LDcsNi43LDcsNi44LDdjMC4xLDAsMC4zLDAsMC40LDBjMC4xLDAsMC4xLDAsMC4yLDAuMWMwLjEsMCwwLjEsMC4xLDAuMiwwLjFjMCwwLDAuMSwwLjEsMC4xLDAuMWMwLjEsMC4xLDAuMiwwLjIsMC4yLDAuMw0KCQlDOCw3LjcsOCw3LjksOCw4YzAsMC4xLDAsMC4zLTAuMSwwLjRDNy45LDguNSw3LjgsOC42LDcuNyw4LjdDNy41LDguOSw3LjMsOSw3LDl6Ii8+DQo8L2c+DQo8Zz4NCgk8cGF0aCBkPSJNMTAsOUM5LjcsOSw5LjUsOC45LDkuMyw4LjdDOS4xLDguNSw5LDguMyw5LDhjMC0wLjEsMC0wLjMsMC4xLTAuNGMwLjEtMC4xLDAuMS0wLjIsMC4yLTAuM2MwLjEtMC4xLDAuMi0wLjIsMC4zLTAuMg0KCQlDMTAsNi45LDEwLjQsNywxMC43LDcuM2MwLjEsMC4xLDAuMiwwLjIsMC4yLDAuM0MxMSw3LjcsMTEsNy45LDExLDhjMCwwLjMtMC4xLDAuNS0wLjMsMC43QzEwLjUsOC45LDEwLjMsOSwxMCw5eiIvPg0KPC9nPg0KPGc+DQoJPHBhdGggZD0iTTEzLDljLTAuMSwwLTAuMywwLTAuNC0wLjFjLTAuMS0wLjEtMC4yLTAuMS0wLjMtMC4yYy0wLjEtMC4xLTAuMi0wLjItMC4yLTAuM0MxMiw4LjMsMTIsOC4xLDEyLDhjMC0wLjEsMC0wLjMsMC4xLTAuNA0KCQljMC4xLTAuMSwwLjEtMC4yLDAuMi0wLjNjMC40LTAuNCwxLTAuNCwxLjQsMGMwLjEsMC4xLDAuMiwwLjIsMC4yLDAuM0MxNCw3LjcsMTQsNy45LDE0LDhjMCwwLjEsMCwwLjMtMC4xLDAuNA0KCQljLTAuMSwwLjEtMC4xLDAuMi0wLjIsMC4zQzEzLjUsOC45LDEzLjMsOSwxMyw5eiIvPg0KPC9nPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTI3LDVINUMzLjksNSwzLDUuOSwzLDd2MThjMCwxLjEsMC45LDIsMiwyaDIyYzEuMSwwLDItMC45LDItMlY3QzI5LDUuOSwyOC4xLDUsMjcsNXoiLz4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0yMywyM0g5Yy0xLjEsMC0yLTAuOS0yLTJ2LTRjMC0xLjEsMC45LTIsMi0yaDE0YzEuMSwwLDIsMC45LDIsMnY0QzI1LDIyLjEsMjQuMSwyMywyMywyM3oiLz4NCjxwb2x5bGluZSBjbGFzcz0ic3QwIiBwb2ludHM9IjE1LDE5IDE2LDIwIDE4LDE4ICIvPg0KPC9zdmc+DQo=", + "name": "TwoChoicesDialogBoxes", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/Line Hero Pack/Master/SVG/Interface Elements/0842ffc478006e9b6687fca9a5761494c4efd1df873220637af5b4ac7253f850_Interface Elements_interface_ui_window_application_app_button_cta.svg", + "shortDescription": "Two-choice dialog box with keyboard, gamepad, and touch support. Customizable text.", + "version": "0.3.2", + "description": [ + "A dialog box showing multiple options (usually \"yes\" and \"no\") and a customizable text message.", + "It handles keyboard, gamepad and touch controls.", + "", + "There are ready-to-use dialog boxes in the asset-store [dialog boxes pack](https://editor.gdevelop.io/?initial-dialog=asset-store&asset-pack=dialog-boxes-dialog-boxes)." + ], + "origin": { + "identifier": "TwoChoicesDialogBoxes", + "name": "gdevelop-extension-store" + }, + "tags": [ + "dialog", + "dialogue", + "box", + "input", + "confirmation", + "popup" + ], + "authorIds": [ + "2OwwM8ToR9dx9RJ2sAKTcrLmCB92" + ], + "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": [], + "eventsBasedObjects": [ + { + "areaMaxX": 600, + "areaMaxY": 213, + "areaMaxZ": 32, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "assetStoreTag": "dialog boxes", + "defaultName": "", + "description": "A dialog box showing two options.", + "fullName": "Two choices dialog box ", + "helpPath": "", + "iconUrl": "", + "isInnerAreaFollowingParentSize": true, + "isTextContainer": true, + "isUsingLegacyInstancesRenderer": false, + "name": "TwoChoicesDialogBox", + "previewIconUrl": "", + "objects": [ + { + "assetStoreId": "90d94b6e3a23c34948e59eaaef4679740f8ab588696d59b21e0c0c2ef106b94d", + "name": "NoButton", + "type": "PanelSpriteButton::PanelSpriteButton", + "variant": "Semi-transparent black and white", + "variables": [ + { + "name": "ID", + "type": "number", + "value": 0 + } + ], + "effects": [ + { + "effectType": "Outline", + "name": "OutlineEffect", + "doubleParameters": { + "padding": 0, + "thickness": 2 + }, + "stringParameters": { + "color": "255;255;255" + }, + "booleanParameters": {} + } + ], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 0, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 0, + "useLegacyBottomAndRightAnchors": false + } + ], + "content": { + "LeftPadding": 8, + "RightPadding": 8, + "PressedLabelOffsetY": 0, + "IdleLabelOffsetY": -8, + "BottomPadding": 8, + "TopPadding": 8, + "MinimalWidth": 300 + } + }, + { + "assetStoreId": "90d94b6e3a23c34948e59eaaef4679740f8ab588696d59b21e0c0c2ef106b94d", + "name": "YesButton", + "type": "PanelSpriteButton::PanelSpriteButton", + "variant": "Semi-transparent black and white", + "variables": [ + { + "name": "ID", + "type": "number", + "value": 0 + } + ], + "effects": [ + { + "effectType": "Outline", + "name": "OutlineEffect", + "doubleParameters": { + "padding": 0, + "thickness": 2 + }, + "stringParameters": { + "color": "255;255;255" + }, + "booleanParameters": {} + } + ], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 0, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 0, + "useLegacyBottomAndRightAnchors": false + } + ], + "content": { + "LeftPadding": 8, + "RightPadding": 8, + "PressedLabelOffsetY": 0, + "IdleLabelOffsetY": -8, + "BottomPadding": 8, + "TopPadding": 8, + "MinimalWidth": 300 + } + }, + { + "assetStoreId": "", + "bottomMargin": 16, + "height": 48, + "leftMargin": 16, + "name": "TextBorder", + "rightMargin": 16, + "texture": "Border.png", + "tiled": true, + "topMargin": 16, + "type": "PanelSpriteObject::PanelSprite", + "width": 48, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 1, + "useLegacyBottomAndRightAnchors": false + } + ] + }, + { + "assetStoreId": "", + "name": "Dialogue", + "type": "BitmapText::BitmapTextObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 1, + "useLegacyBottomAndRightAnchors": false + } + ], + "content": { + "text": "Default message. Continue?", + "opacity": 255, + "scale": 2, + "fontSize": 20, + "tint": "255;255;255", + "bitmapFontResourceName": "8BitOperatorPlus8Regular_Edited.fnt", + "textureAtlasResourceName": "8BitOperatorPlus8Regular_Edited.png", + "align": "left", + "wordWrap": true, + "verticalTextAlignment": "top" + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Dialogue" + }, + { + "objectName": "TextBorder" + }, + { + "objectName": "YesButton" + }, + { + "objectName": "NoButton" + } + ] + }, + "objectsGroups": [ + { + "name": "Buttons", + "objects": [ + { + "name": "NoButton" + }, + { + "name": "YesButton" + } + ] + } + ], + "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": 1, + "height": 124, + "layer": "", + "name": "Dialogue", + "persistentUuid": "c7183394-9ca6-4c16-9d13-f0b7cfe803d8", + "width": 554, + "x": 19, + "y": 18, + "zOrder": 45, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 0, + "height": 213, + "layer": "", + "name": "TextBorder", + "persistentUuid": "639d5661-817d-4faa-b33b-c0ff129c309a", + "width": 600, + "x": 0, + "y": 0, + "zOrder": 43, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 32, + "height": 36, + "layer": "", + "name": "YesButton", + "persistentUuid": "a0ca78d3-0a23-4bbe-89de-fb2d0a53996c", + "width": 105, + "x": 350, + "y": 160, + "zOrder": 510, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 32, + "height": 36, + "layer": "", + "name": "NoButton", + "persistentUuid": "6781aafa-3ae9-4820-bb97-682a524a0cf6", + "width": 103, + "x": 471, + "y": 160, + "zOrder": 510, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [ + { + "name": "ID", + "persistentUuid": "bb563dbe-93eb-4412-b843-f60016b9a893", + "type": "number", + "value": 1 + } + ] + } + ], + "editionSettings": {}, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Buttons", + "Effect", + "\"OutlineEffect\"", + "" + ] + }, + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "Object", + "Text", + "=", + "TextMessage" + ] + }, + { + "type": { + "value": "PanelSpriteButton::PanelSpriteButton::SetLabelText" + }, + "parameters": [ + "YesButton", + "LabelButtonID0", + "" + ] + }, + { + "type": { + "value": "PanelSpriteButton::PanelSpriteButton::SetLabelText" + }, + "parameters": [ + "NoButton", + "LabelButtonID1", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TwoChoicesDialogBoxes::TwoChoicesDialogBox", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Gamepad, mouse, touch and keyboard are fully supported.\nEach action from a device increment or decrement an index that is used to focus the active button, make outlines and trigger a sound effect." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Visible" + }, + "parameters": [ + "Object" + ] + } + ], + "actions": [ + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "Dialogue", + "Text", + "=", + "Object.Text::Value()" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "TwoChoicesDialogBoxes::IsInGameEdition" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "PanelSpriteButton::PanelSpriteButton::IsHovered" + }, + "parameters": [ + "Buttons", + "" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [ + { + "type": { + "value": "PlaySound" + }, + "parameters": [ + "", + "HoveredSound", + "", + "20", + "RandomFloatInRange(0.8,0.9)" + ] + }, + { + "type": { + "value": "TwoChoicesDialogBoxes::TwoChoicesDialogBox::SetActiveButtonById" + }, + "parameters": [ + "Object", + "=", + "Buttons.ID", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "KeyPressed" + }, + "parameters": [ + "", + "NumpadLeft" + ] + }, + { + "type": { + "value": "KeyPressed" + }, + "parameters": [ + "", + "Left" + ] + }, + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "1", + "\"Left\"", + "" + ] + }, + { + "type": { + "value": "Gamepads::C_Axis_pushed" + }, + "parameters": [ + "", + "1", + "\"Left\"", + "\"Left\"", + "" + ] + } + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [ + { + "type": { + "value": "TwoChoicesDialogBoxes::TwoChoicesDialogBox::SetActiveButtonById" + }, + "parameters": [ + "Object", + "-", + "1", + "" + ] + }, + { + "type": { + "value": "PlaySound" + }, + "parameters": [ + "", + "HoveredSound", + "", + "20", + "RandomFloatInRange(0.8,0.9)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "KeyPressed" + }, + "parameters": [ + "", + "NumpadRight" + ] + }, + { + "type": { + "value": "KeyPressed" + }, + "parameters": [ + "", + "Right" + ] + }, + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "1", + "\"Right\"", + "" + ] + }, + { + "type": { + "value": "Gamepads::C_Axis_pushed" + }, + "parameters": [ + "", + "1", + "\"Left\"", + "\"Right\"", + "" + ] + } + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [ + { + "type": { + "value": "TwoChoicesDialogBoxes::TwoChoicesDialogBox::SetActiveButtonById" + }, + "parameters": [ + "Object", + "+", + "1", + "" + ] + }, + { + "type": { + "value": "PlaySound" + }, + "parameters": [ + "", + "HoveredSound", + "", + "20", + "RandomFloatInRange(0.8,0.9)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "To escape the dialog we can focus the button with the identifier 1 which is usually the button to cancel/deny by pressing one of the key or buttons" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "EnableEscape", + "True", + "" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "KeyPressed" + }, + "parameters": [ + "", + "Escape" + ] + }, + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "1", + "\"B\"", + "" + ] + } + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [ + { + "type": { + "value": "TwoChoicesDialogBoxes::TwoChoicesDialogBox::SetActiveButtonById" + }, + "parameters": [ + "Object", + "=", + "1", + "" + ] + } + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TwoChoicesDialogBoxes::TwoChoicesDialogBox", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "Object", + "Text", + "=", + "TextMessage" + ] + }, + { + "type": { + "value": "PanelSpriteButton::PanelSpriteButton::SetLabelTextOp" + }, + "parameters": [ + "YesButton", + "=", + "LabelButtonID0", + "" + ] + }, + { + "type": { + "value": "PanelSpriteButton::PanelSpriteButton::SetLabelTextOp" + }, + "parameters": [ + "NoButton", + "=", + "LabelButtonID1", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TwoChoicesDialogBoxes::TwoChoicesDialogBox", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the \"Yes\" button of the dialog was selected.", + "fullName": "\"Yes\" button is clicked", + "functionType": "Condition", + "name": "IsYesClicked", + "sentence": "\"Yes\" button of _PARAM0_ is clicked", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "The button is clicked\nOr\nThe button is active and a key is pressed to validate the button" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "PanelSpriteButton::PanelSpriteButton::IsClicked" + }, + "parameters": [ + "YesButton", + "" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::And" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "TwoChoicesDialogBoxes::TwoChoicesDialogBox::ActiveButtonById" + }, + "parameters": [ + "Object", + "=", + "0", + "" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "KeyPressed" + }, + "parameters": [ + "", + "NumpadReturn" + ] + }, + { + "type": { + "value": "KeyPressed" + }, + "parameters": [ + "", + "Return" + ] + }, + { + "type": { + "value": "KeyPressed" + }, + "parameters": [ + "", + "Space" + ] + }, + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "1", + "\"A\"", + "" + ] + }, + { + "type": { + "value": "KeyPressed" + }, + "parameters": [ + "", + "Space" + ] + } + ] + } + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TwoChoicesDialogBoxes::TwoChoicesDialogBox", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the \"No\" button of the dialog was selected.", + "fullName": "\"No\" button is clicked", + "functionType": "Condition", + "name": "IsNoClicked", + "sentence": "\"No\" button of _PARAM0_ is clicked", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "The button is clicked\nOr\nThe button is active and a key is pressed to validate the button" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "PanelSpriteButton::PanelSpriteButton::IsClicked" + }, + "parameters": [ + "NoButton", + "" + ] + }, + { + "type": { + "value": "KeyPressed" + }, + "parameters": [ + "", + "Escape" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::And" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "TwoChoicesDialogBoxes::TwoChoicesDialogBox::ActiveButtonById" + }, + "parameters": [ + "Object", + "=", + "1", + "" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "KeyPressed" + }, + "parameters": [ + "", + "NumpadReturn" + ] + }, + { + "type": { + "value": "KeyPressed" + }, + "parameters": [ + "", + "Return" + ] + }, + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "1", + "\"A\"", + "" + ] + }, + { + "type": { + "value": "KeyPressed" + }, + "parameters": [ + "", + "Space" + ] + } + ] + } + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TwoChoicesDialogBoxes::TwoChoicesDialogBox", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the highlighted button.", + "fullName": "Highlighted button", + "functionType": "ExpressionAndCondition", + "group": "Dialog Box configuration", + "name": "ActiveButtonById", + "private": true, + "sentence": "the highlighted button", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "HighlightedButtonIndex" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TwoChoicesDialogBoxes::TwoChoicesDialogBox", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ActiveButtonById", + "group": "Dialog Box configuration", + "name": "SetActiveButtonById", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "HighlightedButtonIndex", + "=", + "mod(Value, SceneInstancesCount(Buttons))" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "VarObjet" + }, + "parameters": [ + "Buttons", + "ID", + "=", + "HighlightedButtonIndex" + ] + } + ], + "actions": [ + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Buttons", + "Effect", + "\"OutlineEffect\"", + "yes" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "VarObjet" + }, + "parameters": [ + "Buttons", + "ID", + "!=", + "HighlightedButtonIndex" + ] + } + ], + "actions": [ + { + "type": { + "value": "EffectCapability::EffectBehavior::EnableEffect" + }, + "parameters": [ + "Buttons", + "Effect", + "\"OutlineEffect\"", + "no" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TwoChoicesDialogBoxes::TwoChoicesDialogBox", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the text shown by the dialog.", + "fullName": "Text message", + "functionType": "ExpressionAndCondition", + "group": "Dialog Box configuration", + "name": "TextMessage", + "private": true, + "sentence": "the text", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "TextMessage" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TwoChoicesDialogBoxes::TwoChoicesDialogBox", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "TextMessage", + "group": "Dialog Box configuration", + "name": "SetTextMessage", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "TextMessage", + "=", + "Value" + ] + }, + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "Dialogue", + "Text", + "=", + "TextMessage" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TwoChoicesDialogBoxes::TwoChoicesDialogBox", + "type": "object" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPostEvents" + }, + { + "functionName": "onHotReloading" + }, + { + "functionName": "IsYesClicked" + }, + { + "functionName": "IsNoClicked" + }, + { + "folderName": "Dialog Box configuration", + "children": [ + { + "functionName": "ActiveButtonById" + }, + { + "functionName": "SetActiveButtonById" + }, + { + "functionName": "TextMessage" + }, + { + "functionName": "SetTextMessage" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "Default message. Continue?", + "type": "MultilineString", + "label": "Message", + "name": "TextMessage" + }, + { + "value": "0", + "type": "Number", + "unit": "Dimensionless", + "label": "", + "hidden": true, + "name": "HighlightedButtonIndex" + }, + { + "value": "true", + "type": "Boolean", + "label": "Cancel with Escape key", + "description": "Enable or disable the escape key to close the dialog.", + "name": "EnableEscape" + }, + { + "value": "Yes", + "type": "String", + "label": "Label for the \"Yes\" button", + "description": "This is the button with identifier 0.", + "name": "LabelButtonID0" + }, + { + "value": "No", + "type": "String", + "label": "Label for the \"No\" button", + "description": "This is the button with identifier 1.", + "name": "LabelButtonID1" + }, + { + "value": "", + "type": "Choice", + "label": "", + "choices": [ + { + "label": "YesButton.LabelText=LabelButtonID0", + "value": "YesButton.LabelText=LabelButtonID0" + }, + { + "label": "NoButton.LabelText=LabelButtonID1", + "value": "NoButton.LabelText=LabelButtonID1" + }, + { + "label": "Dialogue.Text=TextMessage", + "value": "Dialogue.Text=TextMessage" + } + ], + "hidden": true, + "name": "_PropertyMapping" + }, + { + "value": "", + "type": "Resource", + "label": "Sound at hovering", + "extraInformation": [ + "audio" + ], + "choices": [], + "name": "HoveredSound" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "TextMessage" + }, + { + "propertyName": "HighlightedButtonIndex" + }, + { + "propertyName": "EnableEscape" + }, + { + "propertyName": "LabelButtonID0" + }, + { + "propertyName": "LabelButtonID1" + }, + { + "propertyName": "_PropertyMapping" + }, + { + "propertyName": "HoveredSound" + } + ] + }, + "variants": [ + { + "areaMaxX": 600, + "areaMaxY": 213, + "areaMaxZ": 32, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "name": "DialogBox", + "objects": [ + { + "assetStoreId": "90d94b6e3a23c34948e59eaaef4679740f8ab588696d59b21e0c0c2ef106b94d", + "name": "NoButton", + "type": "PanelSpriteButton::PanelSpriteButton", + "variant": "OptionButton", + "variables": [ + { + "name": "ID", + "type": "number", + "value": 0 + } + ], + "effects": [ + { + "effectType": "Outline", + "name": "OutlineEffect", + "doubleParameters": { + "padding": 0, + "thickness": 2 + }, + "stringParameters": { + "color": "255;255;255" + }, + "booleanParameters": {} + } + ], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 0, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 0, + "useLegacyBottomAndRightAnchors": false + } + ], + "content": { + "LeftPadding": 8, + "RightPadding": 8, + "PressedLabelOffsetY": 0, + "IdleLabelOffsetY": -8, + "BottomPadding": 8, + "TopPadding": 8, + "MinimalWidth": 300 + } + }, + { + "assetStoreId": "90d94b6e3a23c34948e59eaaef4679740f8ab588696d59b21e0c0c2ef106b94d", + "name": "YesButton", + "persistentUuid": "8d80f548-1022-4481-ba31-4fdf850768da", + "type": "PanelSpriteButton::PanelSpriteButton", + "variant": "OptionButton", + "variables": [ + { + "name": "ID", + "persistentUuid": "111a6781-3c8b-47b7-b062-c4558df1435e", + "type": "number", + "value": 0 + } + ], + "effects": [ + { + "effectType": "Outline", + "name": "OutlineEffect", + "doubleParameters": { + "padding": 0, + "thickness": 2 + }, + "stringParameters": { + "color": "255;255;255" + }, + "booleanParameters": {} + } + ], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 0, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 0, + "useLegacyBottomAndRightAnchors": false + } + ], + "content": { + "LeftPadding": 8, + "RightPadding": 8, + "PressedLabelOffsetY": 0, + "IdleLabelOffsetY": -8, + "BottomPadding": 8, + "TopPadding": 8, + "MinimalWidth": 300 + } + }, + { + "assetStoreId": "", + "bottomMargin": 18, + "height": 48, + "leftMargin": 18, + "name": "TextBorder", + "persistentUuid": "0b929a4e-9454-4fb6-8576-a41a124addb9", + "rightMargin": 18, + "texture": "Border.png", + "tiled": true, + "topMargin": 18, + "type": "PanelSpriteObject::PanelSprite", + "width": 48, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 1, + "useLegacyBottomAndRightAnchors": false + } + ] + }, + { + "assetStoreId": "", + "name": "Dialogue", + "type": "BitmapText::BitmapTextObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 1, + "useLegacyBottomAndRightAnchors": false + } + ], + "content": { + "text": "Default message. Continue?", + "opacity": 255, + "scale": 2, + "fontSize": 20, + "tint": "255;255;255", + "bitmapFontResourceName": "8BitOperatorPlus8Regular_Edited.fnt", + "textureAtlasResourceName": "8BitOperatorPlus8Regular_Edited.png", + "align": "left", + "wordWrap": true, + "verticalTextAlignment": "top" + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Dialogue" + }, + { + "objectName": "TextBorder" + }, + { + "objectName": "YesButton" + }, + { + "objectName": "NoButton" + } + ] + }, + "objectsGroups": [ + { + "name": "Buttons", + "objects": [ + { + "name": "NoButton" + }, + { + "name": "YesButton" + } + ] + } + ], + "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": 1, + "height": 124, + "layer": "", + "name": "Dialogue", + "persistentUuid": "c7183394-9ca6-4c16-9d13-f0b7cfe803d8", + "width": 554, + "x": 19, + "y": 18, + "zOrder": 45, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 0, + "height": 213, + "layer": "", + "name": "TextBorder", + "persistentUuid": "639d5661-817d-4faa-b33b-c0ff129c309a", + "width": 600, + "x": 0, + "y": 0, + "zOrder": 43, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 32, + "height": 42, + "layer": "", + "name": "YesButton", + "persistentUuid": "a0ca78d3-0a23-4bbe-89de-fb2d0a53996c", + "width": 105, + "x": 350, + "y": 154, + "zOrder": 510, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 32, + "height": 42, + "layer": "", + "name": "NoButton", + "persistentUuid": "6781aafa-3ae9-4820-bb97-682a524a0cf6", + "width": 103, + "x": 471, + "y": 154, + "zOrder": 510, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [ + { + "name": "ID", + "persistentUuid": "bb563dbe-93eb-4412-b843-f60016b9a893", + "type": "number", + "value": 1 + } + ] + } + ], + "editionSettings": { + "grid": false, + "gridType": "rectangular", + "gridWidth": 32, + "gridHeight": 32, + "gridDepth": 32, + "gridOffsetX": 0, + "gridOffsetY": 0, + "gridOffsetZ": 0, + "gridColor": 10401023, + "gridAlpha": 0.8, + "snap": false, + "zoomFactor": 1.1286095791267934, + "windowMask": false, + "selectedLayer": "", + "gameEditorMode": "instances-editor" + } + }, + { + "areaMaxX": 600, + "areaMaxY": 213, + "areaMaxZ": 32, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "assetStoreAssetId": "9d5de415b6e469c681701ea42d3b1c486fee31d6ef591a2d3e226f639ab486ff", + "assetStoreOriginalName": "Semi-transparent black and white (copy)", + "name": "Semi-transparent black and white (copy)", + "objects": [ + { + "assetStoreId": "", + "name": "NoButton", + "type": "PanelSpriteButton::PanelSpriteButton", + "variant": "Semi-transparent black and white", + "variables": [ + { + "name": "ID", + "type": "number", + "value": 0 + } + ], + "effects": [ + { + "effectType": "Outline", + "name": "OutlineEffect", + "doubleParameters": { + "padding": 0, + "thickness": 2 + }, + "stringParameters": { + "color": "255;255;255" + }, + "booleanParameters": {} + } + ], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 0, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 0, + "useLegacyBottomAndRightAnchors": false + } + ], + "content": { + "PressedLabelOffsetY": 0, + "LabelText": "No" + } + }, + { + "assetStoreId": "", + "name": "YesButton", + "type": "PanelSpriteButton::PanelSpriteButton", + "variant": "Semi-transparent black and white", + "variables": [ + { + "name": "ID", + "type": "number", + "value": 0 + } + ], + "effects": [ + { + "effectType": "Outline", + "name": "OutlineEffect", + "doubleParameters": { + "padding": 0, + "thickness": 2 + }, + "stringParameters": { + "color": "255;255;255" + }, + "booleanParameters": {} + } + ], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 0, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 0, + "useLegacyBottomAndRightAnchors": false + } + ], + "content": { + "PressedLabelOffsetY": 0, + "LabelText": "Yes" + } + }, + { + "assetStoreId": "", + "bottomMargin": 16, + "height": 48, + "leftMargin": 16, + "name": "TextBorder", + "rightMargin": 16, + "texture": "Border.png", + "tiled": true, + "topMargin": 16, + "type": "PanelSpriteObject::PanelSprite", + "width": 48, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 1, + "useLegacyBottomAndRightAnchors": false + } + ] + }, + { + "assetStoreId": "", + "name": "Dialogue", + "type": "BitmapText::BitmapTextObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 1, + "useLegacyBottomAndRightAnchors": false + } + ], + "content": { + "text": "Default message. Continue?", + "opacity": 255, + "scale": 2, + "fontSize": 20, + "tint": "255;255;255", + "bitmapFontResourceName": "8BitOperatorPlus8Regular_Edited.fnt", + "textureAtlasResourceName": "8BitOperatorPlus8Regular_Edited.png", + "align": "left", + "wordWrap": true, + "verticalTextAlignment": "top" + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Dialogue" + }, + { + "objectName": "TextBorder" + }, + { + "objectName": "YesButton" + }, + { + "objectName": "NoButton" + } + ] + }, + "objectsGroups": [ + { + "name": "Buttons", + "objects": [ + { + "name": "NoButton" + }, + { + "name": "YesButton" + } + ] + } + ], + "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": 1, + "height": 124, + "layer": "", + "name": "Dialogue", + "persistentUuid": "c7183394-9ca6-4c16-9d13-f0b7cfe803d8", + "width": 554, + "x": 19, + "y": 18, + "zOrder": 45, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 0, + "height": 213, + "layer": "", + "name": "TextBorder", + "persistentUuid": "639d5661-817d-4faa-b33b-c0ff129c309a", + "width": 600, + "x": 0, + "y": 0, + "zOrder": 43, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 32, + "height": 36, + "layer": "", + "name": "YesButton", + "persistentUuid": "a0ca78d3-0a23-4bbe-89de-fb2d0a53996c", + "width": 105, + "x": 350, + "y": 160, + "zOrder": 510, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 32, + "height": 36, + "layer": "", + "name": "NoButton", + "persistentUuid": "6781aafa-3ae9-4820-bb97-682a524a0cf6", + "width": 103, + "x": 471, + "y": 160, + "zOrder": 510, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [ + { + "name": "ID", + "persistentUuid": "bb563dbe-93eb-4412-b843-f60016b9a893", + "type": "number", + "value": 1 + } + ] + } + ], + "editionSettings": {} + } + ] + } + ] + }, + { + "author": "", + "category": "User interface", + "extensionNamespace": "", + "fullName": "Labeled button", + "gdevelopVersion": ">=5.5.230", + "helpPath": "/objects/button", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTI5LDIzSDNjLTEuMSwwLTItMC45LTItMlYxMWMwLTEuMSwwLjktMiwyLTJoMjZjMS4xLDAsMiwwLjksMiwydjEwQzMxLDIyLjEsMzAuMSwyMywyOSwyM3oiLz4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xMywxOUwxMywxOWMtMS4xLDAtMi0wLjktMi0ydi0yYzAtMS4xLDAuOS0yLDItMmgwYzEuMSwwLDIsMC45LDIsMnYyQzE1LDE4LjEsMTQuMSwxOSwxMywxOXoiLz4NCjxsaW5lIGNsYXNzPSJzdDAiIHgxPSIxOCIgeTE9IjEzIiB4Mj0iMTgiIHkyPSIxOSIvPg0KPGxpbmUgY2xhc3M9InN0MCIgeDE9IjIxIiB5MT0iMTMiIHgyPSIxOCIgeTI9IjE3Ii8+DQo8bGluZSBjbGFzcz0ic3QwIiB4MT0iMjEiIHkxPSIxOSIgeDI9IjE5IiB5Mj0iMTYiLz4NCjwvc3ZnPg0K", + "name": "PanelSpriteButton", + "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Interface Elements/Interface Elements_interface_ui_button_ok_cta_clock_tap.svg", + "shortDescription": "Resizeable button with a label.", + "dimension": "2D", + "version": "2.2.1", + "description": [ + "A button that can be used for menus and most labelled buttons of a game. The button can be customized with a background for each state and a label. It handles user interactions and a simple condition can be used to check if it is clicked.", + "", + "There are ready-to-use buttons in the asset-store [menu buttons pack](https://editor.gdevelop.io/?initial-dialog=asset-store&asset-pack=menu-buttons-menu-buttons)." + ], + "origin": { + "identifier": "PanelSpriteButton", + "name": "gdevelop-extension-store" + }, + "tags": [ + "button", + "ui" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "changelog": [ + { + "version": "2.0.0", + "breaking": "- Buttons now use \"variants\", allowing easy swapping of their visual aspect. You will have to make some adjustments to existing buttons 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": [] + } + ], + "eventsBasedBehaviors": [ + { + "description": "The finite state machine used internally by the button object.", + "fullName": "Button finite state machine", + "name": "ButtonFSM", + "objectType": "", + "private": true, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "PanelSpriteButton::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": "PanelSpriteButton::ButtonFSM::PropertyState" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "\"Hovered\"" + ] + }, + { + "type": { + "value": "PanelSpriteButton::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": "PanelSpriteButton::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onDeActivate", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "PanelSpriteButton::ButtonFSM::ResetState" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "PanelSpriteButton::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": "PanelSpriteButton::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": "PanelSpriteButton::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": "PanelSpriteButton::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": "PanelSpriteButton::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": "PanelSpriteButton::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": "PanelSpriteButton::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": "PanelSpriteButton::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": "PanelSpriteButton::ButtonFSM", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "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": "Change the text style when the button is hovered.", + "fullName": "Hover text style", + "name": "TextStyleState", + "objectType": "TextObject::Text", + "private": true, + "eventsFunctions": [ + { + "description": "Check if isHovered.", + "fullName": "IsHovered", + "functionType": "Condition", + "name": "IsHovered", + "sentence": "_PARAM0_ isHovered", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsHovered", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TextObject::Text", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "PanelSpriteButton::TextStyleState", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Change if isHovered.", + "fullName": "IsHovered", + "functionType": "Action", + "name": "SetIsHovered", + "sentence": "_PARAM0_ isHovered: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsHovered", + "False", + "" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsHovered", + "True", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "HoverFontSize", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "IdleFontSize", + "=", + "Object.FontSize()" + ] + }, + { + "type": { + "value": "TextObject::Text::SetFontSize" + }, + "parameters": [ + "Object", + "=", + "HoverFontSize" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "HoverColor", + "!=", + "\"\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "IdleColor", + "=", + "Object.Behavior::Color()" + ] + }, + { + "type": { + "value": "TextObject::ChangeColor" + }, + "parameters": [ + "Object", + "HoverColor" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldEnableShadowOnHover", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TextObject::ShowShadow" + }, + "parameters": [ + "Object", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldEnableOutlineOnHover", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TextObject::Text::SetOutlineEnabled" + }, + "parameters": [ + "Object", + "yes" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsHovered", + "True", + "" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "False", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsHovered", + "False", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "HoverFontSize", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "TextObject::Text::SetFontSize" + }, + "parameters": [ + "Object", + "=", + "IdleFontSize" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "HoverColor", + "!=", + "\"\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TextObject::ChangeColor" + }, + "parameters": [ + "Object", + "IdleColor" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldEnableShadowOnHover", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TextObject::ShowShadow" + }, + "parameters": [ + "Object", + "no" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldEnableOutlineOnHover", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TextObject::Text::SetOutlineEnabled" + }, + "parameters": [ + "Object", + "no" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TextObject::Text", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "PanelSpriteButton::TextStyleState", + "type": "behavior" + }, + { + "defaultValue": "yes", + "description": "IsHovered", + "name": "Value", + "optional": true, + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Return the text color", + "fullName": "Text color", + "functionType": "StringExpression", + "name": "Color", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.TextRuntimeObject} */", + "const object = eventsFunctionContext.getObjects(\"Object\")[0];", + "", + "eventsFunctionContext.returnValue = object._color.join(\";\");" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "color" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "TextObject::Text", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "PanelSpriteButton::TextStyleState", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "propertyDescriptors": [ + { + "value": "", + "type": "Boolean", + "label": "Outline on hover", + "name": "ShouldEnableOutlineOnHover" + }, + { + "value": "", + "type": "Color", + "label": "Hover color", + "name": "HoverColor" + }, + { + "value": "", + "type": "Boolean", + "label": "Enable shadow on hover", + "name": "ShouldEnableShadowOnHover" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Hover font size", + "name": "HoverFontSize" + }, + { + "value": "", + "type": "Boolean", + "label": "", + "hidden": true, + "name": "IsHovered" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Idle font size", + "hidden": true, + "name": "IdleFontSize" + }, + { + "value": "", + "type": "Color", + "label": "Idle color", + "hidden": true, + "name": "IdleColor" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "IsHovered" + }, + { + "propertyName": "HoverFontSize" + }, + { + "propertyName": "IdleFontSize" + }, + { + "propertyName": "HoverColor" + }, + { + "propertyName": "IdleColor" + }, + { + "propertyName": "ShouldEnableOutlineOnHover" + }, + { + "propertyName": "ShouldEnableShadowOnHover" + } + ] + } + }, + { + "description": "Change the text style when the button is hovered.", + "fullName": "Hover bitmap text style", + "name": "BitmapTextStyleState", + "objectType": "BitmapText::BitmapTextObject", + "private": true, + "eventsFunctions": [ + { + "description": "Check if isHovered.", + "fullName": "IsHovered", + "functionType": "Condition", + "name": "IsHovered", + "sentence": "_PARAM0_ isHovered", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsHovered", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "BitmapText::BitmapTextObject", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "PanelSpriteButton::BitmapTextStyleState", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Change if isHovered.", + "fullName": "IsHovered", + "functionType": "Action", + "name": "SetIsHovered", + "sentence": "_PARAM0_ isHovered: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsHovered", + "False", + "" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsHovered", + "True", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "HoverColor", + "!=", + "\"\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "IdleColor", + "=", + "Object.Behavior::Color()" + ] + }, + { + "type": { + "value": "BitmapText::SetTint" + }, + "parameters": [ + "Object", + "HoverColor" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "HoverPrefix", + "!=", + "\"\"" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "HoverSuffix", + "!=", + "\"\"" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "IdleText", + "=", + "Object.Text::Value()" + ] + }, + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "Object", + "Text", + "=", + "HoverPrefix + Object.Text::Value() + HoverSuffix" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsHovered", + "True", + "" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "False", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsHovered", + "False", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "HoverColor", + "!=", + "\"\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "BitmapText::SetTint" + }, + "parameters": [ + "Object", + "IdleColor" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "HoverPrefix", + "!=", + "\"\"" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "HoverSuffix", + "!=", + "\"\"" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "Object", + "Text", + "=", + "IdleText" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "BitmapText::BitmapTextObject", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "PanelSpriteButton::BitmapTextStyleState", + "type": "behavior" + }, + { + "defaultValue": "yes", + "description": "IsHovered", + "name": "Value", + "optional": true, + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Return the text color", + "fullName": "Text color", + "functionType": "StringExpression", + "name": "Color", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.BitmapTextRuntimeObject} */", + "const object = eventsFunctionContext.getObjects(\"Object\")[0];", + "", + "eventsFunctionContext.returnValue = object._tint.join(\";\");" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "color" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "BitmapText::BitmapTextObject", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "PanelSpriteButton::BitmapTextStyleState", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "propertyDescriptors": [ + { + "value": "", + "type": "Color", + "label": "Hover color", + "name": "HoverColor" + }, + { + "value": "", + "type": "String", + "label": "Hover prefix", + "name": "HoverPrefix" + }, + { + "value": "", + "type": "String", + "label": "Hover suffix", + "name": "HoverSuffix" + }, + { + "value": "", + "type": "Boolean", + "label": "", + "hidden": true, + "name": "IsHovered" + }, + { + "value": "", + "type": "String", + "label": "Idle text", + "hidden": true, + "name": "IdleText" + }, + { + "value": "", + "type": "Color", + "label": "Idle color", + "hidden": true, + "name": "IdleColor" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "IsHovered" + }, + { + "propertyName": "HoverPrefix" + }, + { + "propertyName": "HoverSuffix" + }, + { + "propertyName": "IdleText" + }, + { + "propertyName": "HoverColor" + }, + { + "propertyName": "IdleColor" + } + ] + } + } + ], + "eventsBasedObjects": [ + { + "areaMaxX": 256, + "areaMaxY": 64, + "areaMaxZ": 64, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "assetStoreTag": "menu buttons", + "defaultName": "Button", + "description": "Button with a label.", + "fullName": "Labeled button", + "isInnerAreaFollowingParentSize": true, + "isUsingLegacyInstancesRenderer": false, + "name": "PanelSpriteButton", + "objects": [ + { + "assetStoreId": "", + "bold": false, + "italic": false, + "name": "Label", + "smoothed": true, + "type": "TextObject::Text", + "underlined": false, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 4, + "topEdgeAnchor": 4, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "useLegacyBottomAndRightAnchors": false + }, + { + "name": "TextStyleState", + "type": "PanelSpriteButton::TextStyleState", + "ShouldEnableOutlineOnHover": false, + "HoverColor": "", + "ShouldEnableShadowOnHover": false, + "HoverFontSize": 0, + "IsHovered": false, + "IdleFontSize": 0, + "IdleColor": "" + } + ], + "string": "Text", + "font": "", + "textAlignment": "center", + "characterSize": 20, + "color": { + "b": 0, + "g": 0, + "r": 0 + }, + "content": { + "bold": false, + "isOutlineEnabled": false, + "isShadowEnabled": false, + "italic": false, + "outlineColor": "255;255;255", + "outlineThickness": 2, + "shadowAngle": 90, + "shadowBlurRadius": 2, + "shadowColor": "0;0;0", + "shadowDistance": 4, + "shadowOpacity": 127, + "smoothed": true, + "underlined": false, + "text": "Text", + "font": "", + "textAlignment": "center", + "verticalTextAlignment": "center", + "characterSize": 20, + "lineHeight": 0, + "color": "0;0;0" + } + }, + { + "assetStoreId": "", + "bottomMargin": 0, + "height": 32, + "leftMargin": 0, + "name": "Idle", + "rightMargin": 0, + "texture": "", + "tiled": false, + "topMargin": 0, + "type": "PanelSpriteObject::PanelSprite", + "width": 32, + "variables": [ + { + "folded": true, + "name": "State", + "type": "string", + "value": "Idle" + } + ], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 1, + "useLegacyBottomAndRightAnchors": false + }, + { + "name": "ButtonFSM", + "type": "PanelSpriteButton::ButtonFSM", + "ShouldCheckHovering": true + } + ] + }, + { + "assetStoreId": "", + "bottomMargin": 0, + "height": 32, + "leftMargin": 0, + "name": "Hovered", + "rightMargin": 0, + "texture": "", + "tiled": false, + "topMargin": 0, + "type": "PanelSpriteObject::PanelSprite", + "width": 32, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 1, + "useLegacyBottomAndRightAnchors": false + }, + { + "name": "Tween", + "type": "Tween::TweenBehavior" + } + ] + }, + { + "assetStoreId": "", + "bottomMargin": 0, + "height": 32, + "leftMargin": 0, + "name": "Pressed", + "rightMargin": 0, + "texture": "", + "tiled": false, + "topMargin": 0, + "type": "PanelSpriteObject::PanelSprite", + "width": 32, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 1, + "useLegacyBottomAndRightAnchors": false + } + ] + }, + { + "assetStoreId": "", + "name": "BitmapLabel", + "type": "BitmapText::BitmapTextObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 4, + "topEdgeAnchor": 4, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "useLegacyBottomAndRightAnchors": false + }, + { + "name": "BitmapTextStyleState", + "type": "PanelSpriteButton::BitmapTextStyleState", + "HoverColor": "", + "HoverPrefix": "", + "HoverSuffix": "", + "IsHovered": false, + "IdleText": "", + "IdleColor": "" + } + ], + "content": { + "text": "Text", + "opacity": 255, + "scale": 1, + "fontSize": 20, + "tint": "255;255;255", + "bitmapFontResourceName": "", + "textureAtlasResourceName": "", + "align": "center", + "verticalTextAlignment": "center" + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Label" + }, + { + "objectName": "BitmapLabel" + }, + { + "objectName": "Idle" + }, + { + "objectName": "Hovered" + }, + { + "objectName": "Pressed" + } + ] + }, + "objectsGroups": [ + { + "name": "Background", + "objects": [ + { + "name": "Idle" + }, + { + "name": "Hovered" + }, + { + "name": "Pressed" + } + ] + }, + { + "name": "Labels", + "objects": [ + { + "name": "Label" + }, + { + "name": "BitmapLabel" + } + ] + } + ], + "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": 1, + "height": 64, + "layer": "", + "name": "Idle", + "persistentUuid": "24882334-eec8-403e-8bf1-70fc928a46e6", + "width": 256, + "x": 0, + "y": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 1, + "height": 64, + "layer": "", + "name": "Label", + "persistentUuid": "3b29c95c-5363-4e25-bf47-eecf13e8a226", + "width": 256, + "x": 0, + "y": 32, + "zOrder": 2, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": { + "grid": false, + "gridType": "rectangular", + "gridWidth": 32, + "gridHeight": 32, + "gridDepth": 32, + "gridOffsetX": 0, + "gridOffsetY": 0, + "gridOffsetZ": 0, + "gridColor": 10401023, + "gridAlpha": 0.8, + "snap": false, + "zoomFactor": 2.734375, + "windowMask": false, + "selectedLayer": "", + "gameEditorMode": "instances-editor" + }, + "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": "Create one background instance for of each state.\nOnly the instance for the current state is shown." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Create" + }, + "parameters": [ + "", + "Hovered", + "0", + "0", + "" + ] + }, + { + "type": { + "value": "Create" + }, + "parameters": [ + "", + "Pressed", + "0", + "0", + "" + ] + }, + { + "type": { + "value": "ResizableCapability::ResizableBehavior::SetSize" + }, + "parameters": [ + "Hovered", + "Resizable", + "Idle.Width()", + "Idle.Height()" + ] + }, + { + "type": { + "value": "ResizableCapability::ResizableBehavior::SetSize" + }, + "parameters": [ + "Pressed", + "Resizable", + "Idle.Width()", + "Idle.Height()" + ] + }, + { + "type": { + "value": "Hide" + }, + "parameters": [ + "Hovered" + ] + }, + { + "type": { + "value": "Hide" + }, + "parameters": [ + "Pressed" + ] + }, + { + "type": { + "value": "SetZOrder" + }, + "parameters": [ + "Hovered", + "=", + "1" + ] + }, + { + "type": { + "value": "SetZOrder" + }, + "parameters": [ + "Pressed", + "=", + "1" + ] + }, + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "Labels", + "Text", + "=", + "LabelText" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "PanelSpriteButton::PanelSpriteButton", + "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": "PanelSpriteButton::PanelSpriteButton::IsPressed" + }, + "parameters": [ + "Object", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PanelSpriteButton::PanelSpriteButton::SetLabelOffset" + }, + "parameters": [ + "Object", + "=", + "PressedLabelOffsetY", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "Labels", + "Text", + "=", + "LabelText" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "PanelSpriteButton::PanelSpriteButton", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "sentence": "", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Apply states", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Show the right background accordingly to the new state." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "PanelSpriteButton::PanelSpriteButton::IsIdle" + }, + "parameters": [ + "Object", + "ButtonFSM" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [ + { + "type": { + "value": "PanelSpriteButton::TextStyleState::SetIsHovered" + }, + "parameters": [ + "Label", + "TextStyleState", + "no", + "" + ] + }, + { + "type": { + "value": "PanelSpriteButton::BitmapTextStyleState::SetIsHovered" + }, + "parameters": [ + "BitmapLabel", + "BitmapTextStyleState", + "no", + "" + ] + }, + { + "type": { + "value": "PanelSpriteButton::PanelSpriteButton::SetLabelOffset" + }, + "parameters": [ + "Object", + "=", + "0", + "" + ] + }, + { + "type": { + "value": "Show" + }, + "parameters": [ + "Idle", + "" + ] + }, + { + "type": { + "value": "Hide" + }, + "parameters": [ + "Pressed" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Visible" + }, + "parameters": [ + "Hovered" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "HoveredFadeOutDuration", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween::TweenBehavior::AddObjectOpacityTween2" + }, + "parameters": [ + "Hovered", + "Tween", + "\"Fadeout\"", + "0", + "\"linear\"", + "HoveredFadeOutDuration", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "HoveredFadeOutDuration", + "=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "Hide" + }, + "parameters": [ + "Hovered" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "OpacityCapability::OpacityBehavior::Value" + }, + "parameters": [ + "Hovered", + "Opacity", + "=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "Hide" + }, + "parameters": [ + "Hovered" + ] + }, + { + "type": { + "value": "OpacityCapability::OpacityBehavior::SetValue" + }, + "parameters": [ + "Hovered", + "Opacity", + "=", + "255" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "PanelSpriteButton::PanelSpriteButton::IsHovered" + }, + "parameters": [ + "Object", + "ButtonFSM" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [ + { + "type": { + "value": "PanelSpriteButton::TextStyleState::SetIsHovered" + }, + "parameters": [ + "Label", + "TextStyleState", + "", + "" + ] + }, + { + "type": { + "value": "PanelSpriteButton::BitmapTextStyleState::SetIsHovered" + }, + "parameters": [ + "BitmapLabel", + "BitmapTextStyleState", + "yes", + "" + ] + }, + { + "type": { + "value": "PanelSpriteButton::PanelSpriteButton::SetLabelOffset" + }, + "parameters": [ + "Object", + "=", + "0", + "" + ] + }, + { + "type": { + "value": "Hide" + }, + "parameters": [ + "Idle" + ] + }, + { + "type": { + "value": "Show" + }, + "parameters": [ + "Hovered", + "" + ] + }, + { + "type": { + "value": "Hide" + }, + "parameters": [ + "Pressed" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "HoveredFadeOutDuration", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween::RemoveTween" + }, + "parameters": [ + "Hovered", + "Tween", + "\"Fadeout\"" + ] + }, + { + "type": { + "value": "OpacityCapability::OpacityBehavior::SetValue" + }, + "parameters": [ + "Hovered", + "Opacity", + "=", + "255" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "PanelSpriteButton::PanelSpriteButton::IsPressed" + }, + "parameters": [ + "Object", + "ButtonFSM" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [ + { + "type": { + "value": "PanelSpriteButton::TextStyleState::SetIsHovered" + }, + "parameters": [ + "Label", + "TextStyleState", + "no", + "" + ] + }, + { + "type": { + "value": "PanelSpriteButton::BitmapTextStyleState::SetIsHovered" + }, + "parameters": [ + "BitmapLabel", + "BitmapTextStyleState", + "no", + "" + ] + }, + { + "type": { + "value": "PanelSpriteButton::PanelSpriteButton::SetLabelOffset" + }, + "parameters": [ + "Object", + "=", + "PressedLabelOffsetY", + "" + ] + }, + { + "type": { + "value": "Hide" + }, + "parameters": [ + "Idle" + ] + }, + { + "type": { + "value": "Hide" + }, + "parameters": [ + "Hovered" + ] + }, + { + "type": { + "value": "Show" + }, + "parameters": [ + "Pressed", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "PanelSpriteButton::PanelSpriteButton::IsFocused" + }, + "parameters": [ + "Object", + "ButtonFSM" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [ + { + "type": { + "value": "PanelSpriteButton::TextStyleState::SetIsHovered" + }, + "parameters": [ + "Label", + "TextStyleState", + "yes", + "" + ] + }, + { + "type": { + "value": "PanelSpriteButton::BitmapTextStyleState::SetIsHovered" + }, + "parameters": [ + "BitmapLabel", + "BitmapTextStyleState", + "yes", + "" + ] + }, + { + "type": { + "value": "PanelSpriteButton::PanelSpriteButton::SetLabelOffset" + }, + "parameters": [ + "Object", + "=", + "0", + "" + ] + }, + { + "type": { + "value": "Hide" + }, + "parameters": [ + "Idle" + ] + }, + { + "type": { + "value": "Show" + }, + "parameters": [ + "Hovered", + "" + ] + }, + { + "type": { + "value": "Hide" + }, + "parameters": [ + "Pressed" + ] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "PanelSpriteButton::PanelSpriteButton", + "type": "object" + } + ], + "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": "PanelSpriteButton::ButtonFSM::IsIdle" + }, + "parameters": [ + "Idle", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "PanelSpriteButton::PanelSpriteButton", + "type": "object" + } + ], + "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": "PanelSpriteButton::ButtonFSM::IsClicked" + }, + "parameters": [ + "Idle", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "PanelSpriteButton::PanelSpriteButton", + "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": "PanelSpriteButton::ButtonFSM::IsHovered" + }, + "parameters": [ + "Idle", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "PanelSpriteButton::PanelSpriteButton", + "type": "object" + } + ], + "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": "PanelSpriteButton::ButtonFSM::IsFocused" + }, + "parameters": [ + "Idle", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "PanelSpriteButton::PanelSpriteButton", + "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": "PanelSpriteButton::ButtonFSM::IsPressed" + }, + "parameters": [ + "Idle", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "PanelSpriteButton::PanelSpriteButton", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Change the text of the button label.", + "fullName": "Label text", + "functionType": "Action", + "name": "SetLabelText", + "private": true, + "sentence": "Change the text of _PARAM0_ to _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "Labels", + "Text", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "PanelSpriteButton::PanelSpriteButton", + "type": "object" + }, + { + "description": "Text", + "name": "Value", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "LabelText", + "name": "SetLabelTextOp", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "Labels", + "Text", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "PanelSpriteButton::PanelSpriteButton", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the label text.", + "fullName": "Label text", + "functionType": "ExpressionAndCondition", + "name": "LabelText", + "sentence": "the label text", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "Labels.Text::Value()" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "PanelSpriteButton::PanelSpriteButton", + "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": [ + "Idle", + "ButtonFSM", + "yes" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "ShouldActivate", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "Idle", + "ButtonFSM", + "no" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "PanelSpriteButton::PanelSpriteButton", + "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": [ + "Idle", + "ButtonFSM" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "PanelSpriteButton::PanelSpriteButton", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the labelOffset of the object.", + "fullName": "LabelOffset", + "functionType": "ExpressionAndCondition", + "name": "LabelOffset", + "private": true, + "sentence": "the labelOffset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "LabelOffset" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "PanelSpriteButton::PanelSpriteButton", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "LabelOffset", + "name": "SetLabelOffset", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetCenterY" + }, + "parameters": [ + "Labels", + "+", + "Value - LabelOffset" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "LabelOffset", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "PanelSpriteButton::PanelSpriteButton", + "type": "object" + } + ], + "objectGroups": [] + } + ], + "propertyDescriptors": [ + { + "value": "", + "type": "MultilineString", + "label": "Label", + "name": "LabelText" + }, + { + "value": "0.25", + "type": "Number", + "unit": "Second", + "label": "Hovered fade out duration", + "group": "States", + "name": "HoveredFadeOutDuration" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Label offset on Y axis when pressed", + "group": "States", + "name": "PressedLabelOffsetY" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "", + "group": "States", + "hidden": true, + "name": "LabelOffset" + }, + { + "value": "", + "type": "Choice", + "label": "", + "choices": [ + { + "label": "Label.Text=LabelText", + "value": "Label.Text=LabelText" + }, + { + "label": "BitmapLabel.Text=LabelText", + "value": "BitmapLabel.Text=LabelText" + } + ], + "hidden": true, + "name": "_PropertyMapping" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "LabelText" + }, + { + "propertyName": "_PropertyMapping" + }, + { + "folderName": "States", + "children": [ + { + "propertyName": "HoveredFadeOutDuration" + }, + { + "propertyName": "PressedLabelOffsetY" + }, + { + "propertyName": "LabelOffset" + } + ] + } + ] + }, + "variants": [ + { + "areaMaxX": 256, + "areaMaxY": 64, + "areaMaxZ": 64, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "name": "OptionButton", + "objects": [ + { + "assetStoreId": "", + "bold": false, + "italic": false, + "name": "Label", + "smoothed": true, + "type": "TextObject::Text", + "underlined": false, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 4, + "topEdgeAnchor": 4, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "useLegacyBottomAndRightAnchors": false + }, + { + "name": "TextStyleState", + "type": "PanelSpriteButton::TextStyleState", + "ShouldEnableOutlineOnHover": false, + "HoverColor": "", + "ShouldEnableShadowOnHover": false, + "HoverFontSize": 0, + "IsHovered": false, + "IdleFontSize": 0, + "IdleColor": "" + } + ], + "string": "Button", + "font": "", + "textAlignment": "center", + "characterSize": 26, + "color": { + "b": 255, + "g": 255, + "r": 255 + }, + "content": { + "bold": false, + "isOutlineEnabled": false, + "isShadowEnabled": false, + "italic": false, + "outlineColor": "255;255;255", + "outlineThickness": 2, + "shadowAngle": 90, + "shadowBlurRadius": 2, + "shadowColor": "0;0;0", + "shadowDistance": 4, + "shadowOpacity": 127, + "smoothed": true, + "underlined": false, + "text": "Button", + "font": "", + "textAlignment": "center", + "verticalTextAlignment": "center", + "characterSize": 26, + "lineHeight": 0, + "color": "255;255;255" + } + }, + { + "assetStoreId": "", + "bottomMargin": 18, + "height": 106, + "leftMargin": 18, + "name": "Idle", + "persistentUuid": "254dcfa6-6139-4367-9749-91132f41bef0", + "rightMargin": 18, + "texture": "Border.png", + "tiled": false, + "topMargin": 18, + "type": "PanelSpriteObject::PanelSprite", + "width": 256, + "variables": [ + { + "folded": true, + "name": "State", + "persistentUuid": "b562fb1e-48a7-46f2-8f48-a01125097603", + "type": "string", + "value": "Idle" + } + ], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 1, + "useLegacyBottomAndRightAnchors": false + }, + { + "name": "ButtonFSM", + "type": "PanelSpriteButton::ButtonFSM", + "ShouldCheckHovering": true + } + ] + }, + { + "assetStoreId": "", + "bottomMargin": 18, + "height": 106, + "leftMargin": 18, + "name": "Hovered", + "persistentUuid": "ddd20d6d-ec1f-458c-be84-6f19c9377c7a", + "rightMargin": 18, + "texture": "Border2.png", + "tiled": false, + "topMargin": 18, + "type": "PanelSpriteObject::PanelSprite", + "width": 256, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 1, + "useLegacyBottomAndRightAnchors": false + }, + { + "name": "Tween", + "type": "Tween::TweenBehavior" + } + ] + }, + { + "assetStoreId": "", + "bottomMargin": 18, + "height": 106, + "leftMargin": 18, + "name": "Pressed", + "persistentUuid": "1bfe3758-0c26-49b9-ac16-258afb377443", + "rightMargin": 18, + "texture": "Border2.png", + "tiled": false, + "topMargin": 18, + "type": "PanelSpriteObject::PanelSprite", + "width": 256, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 1, + "useLegacyBottomAndRightAnchors": false + } + ] + }, + { + "assetStoreId": "", + "name": "BitmapLabel", + "type": "BitmapText::BitmapTextObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 4, + "topEdgeAnchor": 4, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "useLegacyBottomAndRightAnchors": false + }, + { + "name": "BitmapTextStyleState", + "type": "PanelSpriteButton::BitmapTextStyleState", + "HoverColor": "", + "HoverPrefix": "", + "HoverSuffix": "", + "IsHovered": false, + "IdleText": "", + "IdleColor": "" + } + ], + "content": { + "text": "Text", + "opacity": 255, + "scale": 1, + "fontSize": 20, + "tint": "255;255;255", + "bitmapFontResourceName": "", + "textureAtlasResourceName": "", + "align": "center", + "verticalTextAlignment": "center" + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Label" + }, + { + "objectName": "Idle" + }, + { + "objectName": "Hovered" + }, + { + "objectName": "Pressed" + }, + { + "objectName": "BitmapLabel" + } + ] + }, + "objectsGroups": [ + { + "name": "Background", + "objects": [ + { + "name": "Idle" + }, + { + "name": "Hovered" + }, + { + "name": "Pressed" + } + ] + }, + { + "name": "Labels", + "objects": [ + { + "name": "Label" + }, + { + "name": "BitmapLabel" + } + ] + } + ], + "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": 1, + "height": 64, + "layer": "", + "name": "Idle", + "persistentUuid": "24882334-eec8-403e-8bf1-70fc928a46e6", + "width": 256, + "x": 0, + "y": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 1, + "height": 64, + "layer": "", + "name": "Label", + "persistentUuid": "3b29c95c-5363-4e25-bf47-eecf13e8a226", + "width": 256, + "x": 0, + "y": 32, + "zOrder": 2, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": { + "grid": false, + "gridType": "rectangular", + "gridWidth": 32, + "gridHeight": 32, + "gridDepth": 32, + "gridOffsetX": 0, + "gridOffsetY": 0, + "gridOffsetZ": 0, + "gridColor": 10401023, + "gridAlpha": 0.8, + "snap": false, + "zoomFactor": 2.734375, + "windowMask": false, + "selectedLayer": "", + "gameEditorMode": "instances-editor" + } + }, + { + "areaMaxX": 256, + "areaMaxY": 64, + "areaMaxZ": 64, + "areaMinX": 0, + "areaMinY": 0, + "areaMinZ": 0, + "assetStoreAssetId": "9d5de415b6e469c681701ea42d3b1c486fee31d6ef591a2d3e226f639ab486ff", + "assetStoreOriginalName": "Semi-transparent black and white", + "name": "Semi-transparent black and white", + "objects": [ + { + "assetStoreId": "", + "bold": false, + "italic": false, + "name": "Label", + "smoothed": true, + "type": "TextObject::Text", + "underlined": false, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 4, + "topEdgeAnchor": 4, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "useLegacyBottomAndRightAnchors": false + }, + { + "name": "TextStyleState", + "type": "PanelSpriteButton::TextStyleState", + "ShouldEnableOutlineOnHover": false, + "HoverColor": "", + "ShouldEnableShadowOnHover": false, + "HoverFontSize": 0, + "IsHovered": false, + "IdleFontSize": 0, + "IdleColor": "" + } + ], + "string": "Button", + "font": "", + "textAlignment": "center", + "characterSize": 26, + "color": { + "b": 255, + "g": 255, + "r": 255 + }, + "content": { + "bold": false, + "isOutlineEnabled": false, + "isShadowEnabled": false, + "italic": false, + "outlineColor": "255;255;255", + "outlineThickness": 2, + "shadowAngle": 90, + "shadowBlurRadius": 2, + "shadowColor": "0;0;0", + "shadowDistance": 4, + "shadowOpacity": 127, + "smoothed": true, + "underlined": false, + "text": "Button", + "font": "", + "textAlignment": "center", + "verticalTextAlignment": "center", + "characterSize": 26, + "lineHeight": 0, + "color": "255;255;255" + } + }, + { + "assetStoreId": "", + "bottomMargin": 8, + "height": 106, + "leftMargin": 8, + "name": "Idle", + "rightMargin": 8, + "texture": "Border.png", + "tiled": false, + "topMargin": 8, + "type": "PanelSpriteObject::PanelSprite", + "width": 256, + "variables": [ + { + "folded": true, + "name": "State", + "type": "string", + "value": "Idle" + } + ], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 1, + "useLegacyBottomAndRightAnchors": false + }, + { + "name": "ButtonFSM", + "type": "PanelSpriteButton::ButtonFSM", + "ShouldCheckHovering": true + } + ] + }, + { + "assetStoreId": "", + "bottomMargin": 8, + "height": 106, + "leftMargin": 8, + "name": "Hovered", + "rightMargin": 8, + "texture": "Border2.png", + "tiled": false, + "topMargin": 8, + "type": "PanelSpriteObject::PanelSprite", + "width": 256, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 1, + "useLegacyBottomAndRightAnchors": false + }, + { + "name": "Tween", + "type": "Tween::TweenBehavior" + } + ] + }, + { + "assetStoreId": "", + "bottomMargin": 8, + "height": 106, + "leftMargin": 8, + "name": "Pressed", + "rightMargin": 8, + "texture": "Border2.png", + "tiled": false, + "topMargin": 8, + "type": "PanelSpriteObject::PanelSprite", + "width": 256, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 2, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "topEdgeAnchor": 1, + "useLegacyBottomAndRightAnchors": false + } + ] + }, + { + "assetStoreId": "", + "name": "BitmapLabel", + "type": "BitmapText::BitmapTextObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Anchor", + "type": "AnchorBehavior::AnchorBehavior", + "bottomEdgeAnchor": 4, + "topEdgeAnchor": 4, + "leftEdgeAnchor": 1, + "relativeToOriginalWindowSize": true, + "rightEdgeAnchor": 2, + "useLegacyBottomAndRightAnchors": false + }, + { + "name": "BitmapTextStyleState", + "type": "PanelSpriteButton::BitmapTextStyleState", + "HoverColor": "", + "HoverPrefix": "", + "HoverSuffix": "", + "IsHovered": false, + "IdleText": "", + "IdleColor": "" + } + ], + "content": { + "text": "Text", + "opacity": 255, + "scale": 1, + "fontSize": 20, + "tint": "255;255;255", + "bitmapFontResourceName": "", + "textureAtlasResourceName": "", + "align": "center", + "verticalTextAlignment": "center" + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Label" + }, + { + "objectName": "Idle" + }, + { + "objectName": "Hovered" + }, + { + "objectName": "Pressed" + }, + { + "objectName": "BitmapLabel" + } + ] + }, + "objectsGroups": [ + { + "name": "Background", + "objects": [ + { + "name": "Idle" + }, + { + "name": "Hovered" + }, + { + "name": "Pressed" + } + ] + }, + { + "name": "Labels", + "objects": [ + { + "name": "Label" + }, + { + "name": "BitmapLabel" + } + ] + } + ], + "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": 1, + "height": 64, + "layer": "", + "name": "Idle", + "persistentUuid": "24882334-eec8-403e-8bf1-70fc928a46e6", + "width": 256, + "x": 0, + "y": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 1, + "height": 64, + "layer": "", + "name": "Label", + "persistentUuid": "3b29c95c-5363-4e25-bf47-eecf13e8a226", + "width": 256, + "x": 0, + "y": 32, + "zOrder": 2, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "editionSettings": {} + } + ] + } + ] + }, + { + "author": "Bouh", + "category": "Input", + "extensionNamespace": "", + "fullName": "Gamepads (controllers)", + "gdevelopVersion": ">=5.5.222", + "helpPath": "/all-features/gamepad", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLWdhbWVwYWQtdmFyaWFudC1vdXRsaW5lIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTYsOUg4VjExSDEwVjEzSDhWMTVINlYxM0g0VjExSDZWOU0xOC41LDlBMS41LDEuNSAwIDAsMSAyMCwxMC41QTEuNSwxLjUgMCAwLDEgMTguNSwxMkExLjUsMS41IDAgMCwxIDE3LDEwLjVBMS41LDEuNSAwIDAsMSAxOC41LDlNMTUuNSwxMkExLjUsMS41IDAgMCwxIDE3LDEzLjVBMS41LDEuNSAwIDAsMSAxNS41LDE1QTEuNSwxLjUgMCAwLDEgMTQsMTMuNUExLjUsMS41IDAgMCwxIDE1LjUsMTJNMTcsNUE3LDcgMCAwLDEgMjQsMTJBNyw3IDAgMCwxIDE3LDE5QzE1LjA0LDE5IDEzLjI3LDE4LjIgMTIsMTYuOUMxMC43MywxOC4yIDguOTYsMTkgNywxOUE3LDcgMCAwLDEgMCwxMkE3LDcgMCAwLDEgNyw1SDE3TTcsN0E1LDUgMCAwLDAgMiwxMkE1LDUgMCAwLDAgNywxN0M4LjY0LDE3IDEwLjA5LDE2LjIxIDExLDE1SDEzQzEzLjkxLDE2LjIxIDE1LjM2LDE3IDE3LDE3QTUsNSAwIDAsMCAyMiwxMkE1LDUgMCAwLDAgMTcsN0g3WiIgLz48L3N2Zz4=", + "name": "Gamepads", + "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/gamepad-variant-outline.svg", + "shortDescription": "Gamepad/controller support: buttons, sticks, triggers, vibration. Mapper behaviors for 2D/3D.", + "version": "0.9.2", + "description": [ + "Add support for gamepads (or other physical controllers).", + "", + "It gives access to:", + "- button presses,", + "- axis positions and force,", + "- trigger pressure,", + "- configurable deadzone,", + "- vibration,", + "- automatic \"gamepad 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 gamepad (it works by reading the gamepad state and simulating controls). No need for additional events to make it work once the behaviors are set up.", + "", + "The Bomberman-like example handles 4 players with gamepads ([open the project online](https://editor.gdevelop.io/?project=example://goose-bomberman))." + ], + "origin": { + "identifier": "Gamepads", + "name": "gdevelop-extension-store" + }, + "tags": [ + "controllers", + "gamepads", + "joysticks", + "axis", + "xbox", + "ps4", + "platformer", + "platform", + "top-down" + ], + "authorIds": [ + "2OwwM8ToR9dx9RJ2sAKTcrLmCB92", + "taRwmWxwAFYFL9yyBwB3cwBw0BO2", + "mnImQKdn8nQxwzkS5D6a1JB27V23", + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [ + { + "name": "Buttons", + "type": "structure", + "children": [ + { + "name": "A or Cross", + "type": "string", + "value": "A" + }, + { + "name": "B or Circle", + "type": "string", + "value": "B" + }, + { + "name": "LB or L1", + "type": "string", + "value": "LB" + }, + { + "name": "LT or L2", + "type": "string", + "value": "LT" + }, + { + "name": "RB or R1", + "type": "string", + "value": "RB" + }, + { + "name": "RT or R2", + "type": "string", + "value": "RT" + }, + { + "name": "X or Square", + "type": "string", + "value": "X" + }, + { + "name": "Y or Triangle", + "type": "string", + "value": "Y" + } + ] + } + ], + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onFirstSceneLoaded", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (gdjs._extensionController) {\r", + " return;\r", + "}\r", + "\r", + "/**\r", + " * Associate controller button ids to button names\r", + " */\r", + "const controllerButtonNames = {\r", + " \"XBOX\": {\r", + " 0: \"A\",\r", + " 1: \"B\",\r", + " 2: \"X\",\r", + " 3: \"Y\",\r", + " 4: \"LB\",\r", + " 5: \"RB\",\r", + " 6: \"LT\",\r", + " 7: \"RT\",\r", + " 8: \"BACK\",\r", + " 9: \"START\",\r", + " 10: \"CLICK_STICK_LEFT\",\r", + " 11: \"CLICK_STICK_RIGHT\",\r", + " 12: \"UP\",\r", + " 13: \"DOWN\",\r", + " 14: \"LEFT\",\r", + " 15: \"RIGHT\",\r", + " 16: \"NONE\",\r", + " 17: \"NONE\"\r", + " },\r", + " \"PS4\": {\r", + " 0: \"CROSS\",\r", + " 1: \"CIRCLE\",\r", + " 2: \"SQUARE\",\r", + " 3: \"TRIANGLE\",\r", + " 4: \"L1\",\r", + " 5: \"R1\",\r", + " 6: \"L2\",\r", + " 7: \"R2\",\r", + " 8: \"SHARE\",\r", + " 9: \"OPTIONS\",\r", + " 10: \"CLICK_STICK_LEFT\",\r", + " 11: \"CLICK_STICK_RIGHT\",\r", + " 12: \"UP\",\r", + " 13: \"DOWN\",\r", + " 14: \"LEFT\",\r", + " 15: \"RIGHT\",\r", + " 16: \"PS_BUTTON\",\r", + " 17: \"CLICK_TOUCHPAD\"\r", + " }\r", + "};\r", + "\r", + "/**\r", + " * @param {number} playerId\r", + " */\r", + "function getGamepad(playerId) {\r", + " /** @type {Gamepad[]} */\r", + " const gamepads = navigator.getGamepads ? navigator.getGamepads() : [];\r", + " return gamepads[playerId];\r", + "}\r", + "\r", + "/** @type {{[playerId: number]: Player}} */\r", + "const players = {};\r", + "\r", + "/**\r", + " * @param {number} playerId\r", + " */\r", + "function getPlayer(playerId) {\r", + " let player = players[playerId];\r", + " if (!player) {\r", + " player = new Player(playerId);\r", + " players[playerId] = player;\r", + " }\r", + " return player;\r", + "}\r", + "\r", + "class Player {\r", + " /** @type {number} */\r", + " playerId;\r", + " mapping = 'DEFAULT';\r", + " lastButtonUsed = -1;\r", + " deadzone = 0.2;\r", + " /** @type {{[buttonId: number]: ButtonState}} */\r", + " buttonStates = {};\r", + " rumble = { elapsedTime: 0, duration: 0, weakMagnitude: 0, strongMagnitude: 0 };\r", + "\r", + " /**\r", + " * @param {number} playerId\r", + " */\r", + " constructor(playerId) {\r", + " this.playerId = playerId;\r", + " }\r", + "\r", + " /**\r", + " * @param {number} buttonId\r", + " */\r", + " getButtonState(buttonId) {\r", + " let buttonState = this.buttonStates[buttonId];\r", + " if (!buttonState) {\r", + " buttonState = new ButtonState();\r", + " this.buttonStates[buttonId] = buttonState;\r", + " }\r", + " return buttonState;\r", + " }\r", + "\r", + " /**\r", + " * @param {number} buttonId\r", + " */\r", + " isButtonPressed(buttonId) {\r", + " return this.getButtonState(buttonId).isPressed;\r", + " }\r", + "\r", + " /**\r", + " * @param {number} buttonId\r", + " */\r", + " isButtonJustPressed(buttonId) {\r", + " return this.getButtonState(buttonId).isJustPressed();\r", + " }\r", + "\r", + " /**\r", + " * @param {number} buttonId\r", + " */\r", + " isButtonReleased(buttonId) {\r", + " return this.getButtonState(buttonId).isReleased();\r", + " }\r", + "\r", + " isAnyButtonReleased() {\r", + " for (const buttonId in this.buttonStates) {\r", + " const buttonState = this.buttonStates[buttonId];\r", + " if (buttonState.isReleased()) {\r", + " return true;\r", + " }\r", + " }\r", + " return false;\r", + " }\r", + "\r", + " isAnyButtonPressed() {\r", + " for (const buttonId in this.buttonStates) {\r", + " const buttonState = this.buttonStates[buttonId];\r", + " if (buttonState.isPressed) {\r", + " return true;\r", + " }\r", + " }\r", + " return false;\r", + " }\r", + "}\r", + "\r", + "class ButtonState {\r", + " wasPressed = false;\r", + " isPressed = false;\r", + "\r", + " isReleased() {\r", + " return this.wasPressed && !this.isPressed;\r", + " }\r", + "\r", + " isJustPressed() {\r", + " return !this.wasPressed && this.isPressed;\r", + " }\r", + "}\r", + "\r", + "// Async tasks are run before everything.\r", + "// This is a hack to make sure that button states are updated\r", + "// before mapping behavior events.\r", + "const frameBeginningTask = new class extends gdjs.AsyncTask {\r", + " update() {\r", + " /** @type {Gamepad[]} */\r", + " const gamepads = navigator.getGamepads ? navigator.getGamepads() : [];\r", + " for (let playerId = 0; playerId < gamepads.length; playerId++) {\r", + " const gamepad = gamepads[playerId];\r", + " if (gamepad == null) {\r", + " // The gamepad is not connected.\r", + " continue;\r", + " }\r", + " const player = getPlayer(playerId);\r", + "\r", + " for (let buttonId = 0; buttonId < Object.keys(gamepad.buttons).length; buttonId++) {\r", + " const buttonState = player.getButtonState(buttonId);\r", + " buttonState.wasPressed = buttonState.isPressed;\r", + " buttonState.isPressed = gamepad.buttons[buttonId].pressed;\r", + " if (buttonState.isJustPressed()) {\r", + " player.lastButtonUsed = buttonId;\r", + " }\r", + " }\r", + " }\r", + " return false;\r", + " }\r", + "}();\r", + "\r", + "/**\r", + " * @param {gdjs.RuntimeScene} runtimeScene\r", + " */\r", + "function addFrameBeginningTaskIfNeeded(runtimeScene) {\r", + " const taskManager = runtimeScene.getAsyncTasksManager();\r", + " for (const taskWithCallback of taskManager.tasksWithCallback) {\r", + " if (taskWithCallback.asyncTask === frameBeginningTask) {\r", + " return;\r", + " }\r", + " }\r", + " // Async tasks are run before everything.\r", + " // This is a hack to make sure that button states are updated\r", + " // before mapping behavior events.\r", + " taskManager.addTask(frameBeginningTask);\r", + "}\r", + "\r", + "/**\r", + " * @param {gdjs.RuntimeScene} runtimeScene\r", + " */\r", + "function onScenePostEvents(runtimeScene) {\r", + " addFrameBeginningTaskIfNeeded(runtimeScene);\r", + " /** @type {Gamepad[]} */\r", + " const gamepads = navigator.getGamepads ? navigator.getGamepads() : [];\r", + " for (let playerId = 0; playerId < gamepads.length; playerId++) {\r", + " let gamepad = gamepads[playerId];\r", + " if (gamepad == null) {\r", + " // The gamepad is not connected.\r", + " continue;\r", + " }\r", + " const player = getPlayer(playerId);\r", + " const rumble = player.rumble;\r", + " rumble.elapsedTime += runtimeScene.getElapsedTime(runtimeScene) / 1000;\r", + " if (rumble.duration - rumble.elapsedTime <= 0 &&\r", + " (rumble.weakMagnitude || rumble.strongMagnitude)\r", + " ) {\r", + " rumble.weakMagnitude = 0;\r", + " rumble.strongMagnitude = 0;\r", + " }\r", + " }\r", + "}\r", + "\r", + "/**\r", + " * @param {string} type\r", + " * @param {number} buttonId\r", + " */\r", + "function getInputString(type, buttonId) {\r", + " if (!controllerButtonNames[type]) {\r", + " return \"UNKNOWN_BUTTON\";\r", + " }\r", + " return controllerButtonNames[type][buttonId];\r", + "}\r", + "\r", + "function getButtonId(buttonName) {\r", + " switch (buttonName) {\r", + " case 'A':\r", + " case 'CROSS':\r", + " return 0;\r", + " case 'B':\r", + " case 'CIRCLE':\r", + " return 1;\r", + " case 'X':\r", + " case 'SQUARE':\r", + " return 2;\r", + " case 'Y':\r", + " case 'TRIANGLE':\r", + " return 3;\r", + " case 'LB':\r", + " case 'L1':\r", + " return 4;\r", + " case 'RB':\r", + " case 'R1':\r", + " return 5;\r", + " case 'LT':\r", + " case 'L2':\r", + " return 6;\r", + " case 'RT':\r", + " case 'R2':\r", + " return 7;\r", + " case 'UP':\r", + " return 12;\r", + " case 'DOWN':\r", + " return 13;\r", + " case 'LEFT':\r", + " return 14;\r", + " case 'RIGHT':\r", + " return 15;\r", + " case 'BACK':\r", + " case 'SHARE':\r", + " return 8;\r", + " case 'START':\r", + " case 'OPTIONS':\r", + " return 9;\r", + " case 'CLICK_STICK_LEFT':\r", + " return 10;\r", + " case 'CLICK_STICK_RIGHT':\r", + " return 11;\r", + " //PS4\r", + " case 'PS_BUTTON':\r", + " return 16;\r", + " case 'CLICK_TOUCHPAD':\r", + " return 17;\r", + " default:\r", + " console.error('The gamepad button: ' + buttonName + ' is not valid.');\r", + " return null;\r", + " }\r", + "}\r", + "\r", + "/**\r", + " * @param {number} playerId\r", + " * @param {string} directionName\r", + " * @param {number} axisValueX\r", + " * @param {number} axisValueY\r", + " */\r", + "function isAxisPushed(playerId, directionName, axisValueX, axisValueY) {\r", + " switch (directionName) {\r", + " case 'LEFT':\r", + " return getNormalizedAxisValue(axisValueX, playerId) < 0;\r", + " case 'RIGHT':\r", + " return getNormalizedAxisValue(axisValueX, playerId) > 0;\r", + " case 'UP':\r", + " return getNormalizedAxisValue(axisValueY, playerId) < 0;\r", + " case 'DOWN':\r", + " return getNormalizedAxisValue(axisValueY, playerId) > 0;\r", + " case 'ANY':\r", + " return getNormalizedAxisValue(axisValueX, playerId) < 0\r", + " || getNormalizedAxisValue(axisValueX, playerId) > 0\r", + " || getNormalizedAxisValue(axisValueY, playerId) < 0\r", + " || getNormalizedAxisValue(axisValueY, playerId) > 0\r", + " default:\r", + " console.error('The value stick direction is not valid.');\r", + " return false;\r", + " }\r", + "}\r", + "\r", + "/**\r", + " * @param {number} deltaX\r", + " * @param {number} deltaY\r", + " */\r", + "function axisToAngle(deltaX, deltaY) {\r", + " const rad = Math.atan2(deltaY, deltaX);\r", + " const deg = rad * (180 / Math.PI);\r", + " return deg;\r", + "}\r", + "\r", + "/**\r", + " * @param {{id: string}} gamepad\r", + " */\r", + "function isXbox(gamepad) {\r", + " return (gamepad ? (\r", + " gamepad.id.toUpperCase().indexOf(\"XBOX\") !== -1\r", + " // \"XINPUT\" cannot be used to check if it is a xbox controller is just a generic\r", + " // name reported in Firefox corresponding to the driver being used by the controller\r", + " // https://gamefaqs.gamespot.com/boards/916373-pc/73341312?page=1\r", + " ) : false);\r", + "}\r", + "\r", + "/**\r", + " * Returns the new value taking into account the dead zone for the player_ID given\r", + " * @param {number} value\r", + " * @param {number} playerID\r", + " */\r", + "function getNormalizedAxisValue(value, playerID) {\r", + " // gdjs._extensionController = gdjs._extensionController || { deadzone: 0.2 };\r", + "\r", + " // Anything smaller than this is assumed to be 0,0\r", + " const deadzone = getPlayer(playerID).deadzone;\r", + "\r", + " if (Math.abs(value) < deadzone) {\r", + " return 0;\r", + " } else {\r", + " // We're outside the dead zone, but we'd like to smooth\r", + " // this value out so it still runs nicely between 0..1.\r", + " // That is, we don't want it to jump suddenly from 0 to\r", + " // deadzone.\r", + "\r", + " // Remap value from\r", + " // deadzone..1 to 0..(1-deadzone)\r", + " // or from\r", + " // -1..-deadzone to -(1-deadzone)..0\r", + " value = value - Math.sign(value) * deadzone;\r", + "\r", + " // Remap value from\r", + " // 0..(1-deadzone) to 0..1\r", + " // or from\r", + " // -(1-deadzone)..0 to -1..0\r", + " return value / (1 - deadzone);\r", + " }\r", + "}\r", + "\r", + "gdjs._extensionController = {\r", + " getPlayer,\r", + " controllerButtonNames,\r", + " getInputString,\r", + " getButtonId,\r", + " axisToAngle,\r", + " isXbox,\r", + " getNormalizedAxisValue,\r", + " isAxisPushed,\r", + " getGamepad,\r", + " onScenePostEvents,\r", + " frameBeginningTask,\r", + " addFrameBeginningTaskIfNeeded,\r", + "}" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onSceneLoaded", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "gdjs._extensionController.addFrameBeginningTaskIfNeeded(runtimeScene);", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onScenePostEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "gdjs._extensionController.onScenePostEvents(runtimeScene);", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "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": "Get the value of the pressure on a gamepad trigger.", + "fullName": "Pressure on a gamepad trigger", + "functionType": "Expression", + "name": "TriggerPressure", + "sentence": "Player _PARAM1_ push axis _PARAM2_ to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;\r", + "const trigger = eventsFunctionContext.getArgument(\"Trigger\").toUpperCase();\r", + "\r", + "if (trigger != \"LT\" && trigger != \"RT\" && trigger != \"L2\" && trigger != \"R2\") {\r", + " console.error('Parameter trigger is not valid in expression: \"Pressure on a gamepad trigger\"');\r", + " return;\r", + "}\r", + "/** @type {Gamepad} */\r", + "const gamepad = gdjs._extensionController.getGamepad(playerId);\r", + "if (!gamepad) {\r", + " // The gamepad is not connected.\r", + " return;\r", + "}\r", + "switch (trigger) {\r", + " case 'LT':\r", + " case 'L2':\r", + " eventsFunctionContext.returnValue = gamepad.buttons[6].value;\r", + " break;\r", + "\r", + " case 'RT':\r", + " case 'R2':\r", + " eventsFunctionContext.returnValue = gamepad.buttons[7].value;\r", + " break;\r", + "\r", + " default:\r", + " eventsFunctionContext.returnValue = -1;\r", + " break;\r", + "}" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + }, + { + "description": "Trigger button", + "name": "Trigger", + "supplementaryInformation": "[\"LT\",\"RT\",\"L2\",\"R2\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "the force of gamepad stick (from 0 to 1).", + "fullName": "Stick force", + "functionType": "ExpressionAndCondition", + "name": "StickForce", + "sentence": "the gamepad _PARAM1_ _PARAM2_ stick force", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { getNormalizedAxisValue } = gdjs._extensionController;\r", + "\r", + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;\r", + "const stick = eventsFunctionContext.getArgument(\"Stick\").toUpperCase();\r", + "\r", + "if (stick !== \"LEFT\" && stick !== \"RIGHT\") {\r", + " console.error('Parameter stick is not valid in expression: \"Value of a stick force\"');\r", + " return;\r", + "}\r", + "/** @type {Gamepad} */\r", + "const gamepad = gdjs._extensionController.getGamepad(playerId);\r", + "if (!gamepad) {\r", + " // The gamepad is not connected.\r", + " return;\r", + "}\r", + "const axisValueX = stick === 'RIGHT' ? gamepad.axes[2] : gamepad.axes[0];\r", + "const axisValueY = stick === 'RIGHT' ? gamepad.axes[3] : gamepad.axes[1];\r", + "\r", + "eventsFunctionContext.returnValue = gdjs.evtTools.common.clamp(\r", + " Math.abs(getNormalizedAxisValue(axisValueX, playerId)) +\r", + " Math.abs(getNormalizedAxisValue(axisValueY, playerId)), 0, 1);\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + }, + { + "description": "Stick: \"Left\" or \"Right\"", + "name": "Stick", + "supplementaryInformation": "[\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Get the rotation value of a gamepad stick.\nIf the deadzone value is high, the angle value is rounded to main axes, left, left, up, down.\nAn zero deadzone value give a total freedom on the angle value.", + "fullName": "Value of a stick rotation (deprecated)", + "functionType": "Expression", + "name": "StickRotationValue", + "private": true, + "sentence": "Player _PARAM1_ push axis _PARAM2_ to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Gamepads::StickAngle(PlayerId, Stick)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + }, + { + "description": "Stick: \"Left\" or \"Right\"", + "name": "Stick", + "supplementaryInformation": "[\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Return the angle of a gamepad stick.\nIf the deadzone value is high, the angle value is rounded to main axes, left, left, up, down.\nAn zero deadzone value give a total freedom on the angle value.", + "fullName": "Stick angle", + "functionType": "Expression", + "name": "StickAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { getNormalizedAxisValue } = gdjs._extensionController;\r", + "\r", + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;\r", + "const stick = eventsFunctionContext.getArgument(\"Stick\").toUpperCase();\r", + "\r", + "if (stick !== \"LEFT\" && stick !== \"RIGHT\") {\r", + " console.error('Parameter stick is not valid in expression: \"Value of a stick rotation\"');\r", + " return;\r", + "}\r", + "/** @type {Gamepad} */\r", + "const gamepad = gdjs._extensionController.getGamepad(playerId);\r", + "if (!gamepad) {\r", + " // The gamepad is not connected.\r", + " return;\r", + "}\r", + "const axisValueX = stick === 'RIGHT' ? gamepad.axes[2] : gamepad.axes[0];\r", + "const axisValueY = stick === 'RIGHT' ? gamepad.axes[3] : gamepad.axes[1];\r", + "\r", + "eventsFunctionContext.returnValue = gdjs._extensionController.axisToAngle(\r", + " getNormalizedAxisValue(axisValueX, playerId),\r", + " getNormalizedAxisValue(axisValueY, playerId));" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + }, + { + "description": "Stick: \"Left\" or \"Right\"", + "name": "Stick", + "supplementaryInformation": "[\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Get the value of axis of a gamepad stick.", + "fullName": "Value of a gamepad axis (deprecated)", + "functionType": "Expression", + "name": "AxisValue", + "private": true, + "sentence": "Player _PARAM1_ push axis _PARAM2_ to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;\r", + "const stick = eventsFunctionContext.getArgument(\"stick\").toUpperCase();\r", + "const direction = eventsFunctionContext.getArgument(\"direction\").toUpperCase();\r", + "\r", + "if (stick != \"LEFT\" && stick != \"RIGHT\") {\r", + " console.error('Parameter stick is not valid in expression: \"Value of a gamepad axis\"');\r", + " return;\r", + "}\r", + "if (direction != \"UP\" && direction != \"DOWN\" && direction != \"LEFT\" && direction != \"RIGHT\" && direction != \"HORIZONTAL\" && direction != \"VERTICAL\") {\r", + " console.error('Parameter direction is not valid in expression: \"Value of a gamepad axis\"');\r", + " return;\r", + "}\r", + "/** @type {Gamepad} */\r", + "const gamepad = gdjs._extensionController.getGamepad(playerId);\r", + "if (!gamepad) {\r", + " // The gamepad is not connected.\r", + " return;\r", + "}\r", + "let parameterError = false;\r", + "switch (stick) {\r", + " case 'LEFT':\r", + " switch (direction) {\r", + " case 'LEFT':\r", + " if (gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[0], playerId) < 0) {\r", + " eventsFunctionContext.returnValue = -gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[0], playerId);\r", + " }\r", + " break;\r", + "\r", + " case 'RIGHT':\r", + " if (gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[0], playerId) > 0) {\r", + " eventsFunctionContext.returnValue = gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[0], playerId);\r", + " }\r", + " break;\r", + "\r", + " case 'UP':\r", + " if (gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[1], playerId) < 0) {\r", + " eventsFunctionContext.returnValue = -gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[1], playerId);\r", + " }\r", + " break;\r", + "\r", + " case 'DOWN':\r", + " if (gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[1], playerId) > 0) {\r", + " eventsFunctionContext.returnValue = gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[1], playerId);\r", + " }\r", + " break;\r", + "\r", + " case \"HORIZONTAL\":\r", + " eventsFunctionContext.returnValue = gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[0], playerId);\r", + " break;\r", + "\r", + " case \"VERTICAL\":\r", + " eventsFunctionContext.returnValue = gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[1], playerId);\r", + " break;\r", + "\r", + " default:\r", + " break;\r", + " }\r", + " break;\r", + "\r", + " case 'RIGHT':\r", + " switch (direction) {\r", + " case 'LEFT':\r", + " if (gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[2], playerId) < 0) {\r", + " eventsFunctionContext.returnValue = -gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[2], playerId);\r", + " }\r", + " break;\r", + "\r", + " case 'RIGHT':\r", + " if (gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[2], playerId) > 0) {\r", + " eventsFunctionContext.returnValue = gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[2], playerId);\r", + " }\r", + " break;\r", + "\r", + " case 'UP':\r", + " if (gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[3], playerId) < 0) {\r", + " eventsFunctionContext.returnValue = -gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[3], playerId);\r", + " }\r", + " break;\r", + "\r", + " case 'DOWN':\r", + " if (gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[3], playerId) > 0) {\r", + " eventsFunctionContext.returnValue = gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[3], playerId);\r", + " }\r", + " break;\r", + "\r", + " case \"HORIZONTAL\":\r", + " eventsFunctionContext.returnValue = gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[2], playerId);\r", + " break;\r", + "\r", + " case \"VERTICAL\":\r", + " eventsFunctionContext.returnValue = gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[3], playerId);\r", + " break;\r", + "\r", + " default:\r", + " break;\r", + " }\r", + " break;\r", + "\r", + " default:\r", + " break;\r", + "}\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + }, + { + "description": "Stick: \"Left\" or \"Right\"", + "name": "stick", + "supplementaryInformation": "[\"Left\",\"Right\"]", + "type": "stringWithSelector" + }, + { + "description": "Direction", + "name": "direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\",\"Horizontal\",\"Vertical\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Return the gamepad 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::JsCode", + "inlineCode": [ + "\r", + "const playerId = eventsFunctionContext.getArgument(\"Gamepad\") - 1;\r", + "const stick = eventsFunctionContext.getArgument(\"Stick\").toLowerCase();\r", + "\r", + "if (stick != \"left\" && stick != \"right\") {\r", + " console.error('Parameter stick is not valid in expression: \"Value of a gamepad axis\"');\r", + " return;\r", + "}\r", + "/** @type {Gamepad} */\r", + "const gamepad = gdjs._extensionController.getGamepad(playerId);\r", + "if (!gamepad) {\r", + " // The gamepad is not connected.\r", + " return;\r", + "}\r", + "const axisIndex = stick === 'right' ? 2 : 0;\r", + "eventsFunctionContext.returnValue = gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[axisIndex], playerId);\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "Gamepad", + "type": "expression" + }, + { + "description": "Stick: \"Left\" or \"Right\"", + "name": "Stick", + "supplementaryInformation": "[\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Return the gamepad 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::JsCode", + "inlineCode": [ + "\r", + "const playerId = eventsFunctionContext.getArgument(\"Gamepad\") - 1;\r", + "const stick = eventsFunctionContext.getArgument(\"Stick\").toLowerCase();\r", + "\r", + "if (stick != \"left\" && stick != \"right\") {\r", + " console.error('Parameter stick is not valid in expression: \"Value of a gamepad axis\"');\r", + " return;\r", + "}\r", + "/** @type {Gamepad} */\r", + "const gamepad = gdjs._extensionController.getGamepad(playerId);\r", + "if (!gamepad) {\r", + " // The gamepad is not connected.\r", + " return;\r", + "}\r", + "const axisIndex = stick === 'right' ? 3 : 1;\r", + "eventsFunctionContext.returnValue = gdjs._extensionController.getNormalizedAxisValue(gamepad.axes[axisIndex], playerId);\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "Gamepad", + "type": "expression" + }, + { + "description": "Stick: \"Left\" or \"Right\"", + "name": "Stick", + "supplementaryInformation": "[\"Left\",\"Right\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Test if a button is released on a gamepad. Buttons can be:\n* Xbox: \"A\", \"B\", \"X\", \"Y\", \"LB\", \"RB\", \"LT\", \"RT\", \"BACK\", \"START\",\n* PS4: \"CROSS\", \"SQUARE\", \"CIRCLE\", \"TRIANGLE\", \"L1\", \"L2\", \"R1\", \"R2\", \"SHARE\", \"OPTIONS\", \"PS_BUTTON\", \"CLICK_TOUCHPAD\",\n* Other: \"UP\", \"DOWN\", \"LEFT\", \"RIGHT\", \"CLICK_STICK_LEFT\", \"CLICK_STICK_RIGHT\".", + "fullName": "Gamepad button released", + "functionType": "Condition", + "name": "C_Button_released", + "sentence": "Button _PARAM2_ of gamepad _PARAM1_ is released", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;\r", + "const button = eventsFunctionContext.getArgument(\"Button\").toUpperCase();\r", + "\r", + "let buttonId = gdjs._extensionController.getButtonId(button);\r", + "if (buttonId === null) {\r", + " return;\r", + "}\r", + "const player = gdjs._extensionController.getPlayer(playerId)\r", + "eventsFunctionContext.returnValue = player.isButtonReleased(buttonId);" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + }, + { + "description": "Name of the button", + "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": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a button was just pressed on a gamepad. Buttons can be:\n* Xbox: \"A\", \"B\", \"X\", \"Y\", \"LB\", \"RB\", \"LT\", \"RT\", \"BACK\", \"START\",\n* PS4: \"CROSS\", \"SQUARE\", \"CIRCLE\", \"TRIANGLE\", \"L1\", \"L2\", \"R1\", \"R2\", \"SHARE\", \"OPTIONS\", \"PS_BUTTON\", \"CLICK_TOUCHPAD\",\n* Other: \"UP\", \"DOWN\", \"LEFT\", \"RIGHT\", \"CLICK_STICK_LEFT\", \"CLICK_STICK_RIGHT\".", + "fullName": "Gamepad button just pressed", + "functionType": "Condition", + "name": "IsButtonJustPressed", + "sentence": "Button _PARAM2_ of gamepad _PARAM1_ was just pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;\r", + "const button = eventsFunctionContext.getArgument(\"Button\").toUpperCase();\r", + "\r", + "let buttonId = gdjs._extensionController.getButtonId(button);\r", + "if (buttonId === null) {\r", + " return;\r", + "}\r", + "const player = gdjs._extensionController.getPlayer(playerId)\r", + "eventsFunctionContext.returnValue = player.isButtonJustPressed(buttonId);" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + }, + { + "description": "Name of the button", + "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": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Return the index of the last pressed button of a gamepad.", + "fullName": "Last pressed button (id)", + "functionType": "Expression", + "name": "LastButtonID", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;\r", + "eventsFunctionContext.returnValue = gdjs._extensionController.getPlayer(playerId).lastButtonUsed;" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Check if any button is pressed on a gamepad.", + "fullName": "Any gamepad button pressed", + "functionType": "Condition", + "name": "C_Any_Button_pressed", + "sentence": "Any button of gamepad _PARAM1_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;\r", + "const player = gdjs._extensionController.getPlayer(playerId)\r", + "eventsFunctionContext.returnValue = player.isAnyButtonPressed();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the last button pressed. \nButtons for Xbox and PS4 can be:\n* Xbox: \"A\", \"B\", \"X\", \"Y\", \"LB\", \"RB\", \"LT\", \"RT\", \"BACK\", \"START\",\n* PS4: \"CROSS\", \"SQUARE\", \"CIRCLE\", \"TRIANGLE\", \"L1\", \"L2\", \"R1\", \"R2\", \"SHARE\", \"OPTIONS\", \"PS_BUTTON\", \"CLICK_TOUCHPAD\",\n* Both: \"UP\", \"DOWN\", \"LEFT\", \"RIGHT\", \"CLICK_STICK_LEFT\", \"CLICK_STICK_RIGHT\".", + "fullName": "Last pressed button (string)", + "functionType": "StringExpression", + "name": "LastButtonString", + "sentence": "Button _PARAM2_ of gamepad _PARAM1_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;\r", + "const controllerType = eventsFunctionContext.getArgument(\"controller_type\").toUpperCase();\r", + "/** @type {Gamepad} */\r", + "const gamepad = gdjs._extensionController.getGamepad(playerId);\r", + "if (!gamepad) {\r", + " // The gamepad is not connected.\r", + " return;\r", + "}\r", + "const lastButtonUsedID = gdjs._extensionController.getPlayer(playerId).lastButtonUsed;\r", + "eventsFunctionContext.returnValue = gdjs._extensionController.getInputString(controllerType, lastButtonUsedID);\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + }, + { + "description": "Controller type", + "name": "controller_type", + "supplementaryInformation": "[\"Xbox\",\"PS4\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Return the number of gamepads.", + "fullName": "Gamepad count", + "functionType": "Expression", + "name": "GamepadCount", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const gamepads = navigator.getGamepads ? navigator.getGamepads() : [];\r", + "let lastGamepadIndex = -1;\r", + "for (let playerId = 0; playerId < gamepads.length; playerId++) {\r", + " // Gamepads can be disconnected and become null\r", + " if (gamepads[playerId]) {\r", + " lastGamepadIndex = playerId\r", + " }\r", + "}\r", + "eventsFunctionContext.returnValue = lastGamepadIndex + 1;" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [], + "objectGroups": [] + }, + { + "description": "Check if a button is pressed on a gamepad. \nButtons can be:\n* Xbox: \"A\", \"B\", \"X\", \"Y\", \"LB\", \"RB\", \"LT\", \"RT\", \"BACK\", \"START\",\n* PS4: \"CROSS\", \"SQUARE\", \"CIRCLE\", \"TRIANGLE\", \"L1\", \"L2\", \"R1\", \"R2\", \"SHARE\", \"OPTIONS\", \"PS_BUTTON\", \"CLICK_TOUCHPAD\",\n* Other: \"UP\", \"DOWN\", \"LEFT\", \"RIGHT\", \"CLICK_STICK_LEFT\", \"CLICK_STICK_RIGHT\".", + "fullName": "Gamepad button pressed", + "functionType": "Condition", + "name": "C_Button_pressed", + "sentence": "Button _PARAM2_ of gamepad _PARAM1_ is pressed", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;\r", + "const button = eventsFunctionContext.getArgument(\"button\").toUpperCase();\r", + "\r", + "const buttonId = gdjs._extensionController.getButtonId(button);\r", + "if (buttonId === null) {\r", + " return;\r", + "}\r", + "const player = gdjs._extensionController.getPlayer(playerId)\r", + "eventsFunctionContext.returnValue = player.isButtonPressed(buttonId);\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + }, + { + "description": "Name of the button", + "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": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Return the value of the deadzone applied to a gamepad sticks, between 0 and 1.", + "fullName": "Gamepad deadzone for sticks", + "functionType": "Expression", + "name": "Deadzone", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;\r", + "eventsFunctionContext.returnValue = gdjs._extensionController.getPlayer(playerId).deadzone;" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Set the deadzone for sticks of the gamepad. 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). Deadzone is between 0 and 1, and is by default 0.2.", + "fullName": "Set gamepad deadzone for sticks", + "functionType": "Action", + "name": "A_Set_deadzone", + "sentence": "Set deadzone for sticks on gamepad: _PARAM1_ to _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;\r", + "const newDeadzone = eventsFunctionContext.getArgument(\"deadzone\");\r", + "\r", + "// https://github.com/4ian/GDevelop-extensions/pull/33#issuecomment-618224857\r", + "gdjs._extensionController.getPlayer(playerId).deadzone = gdjs.evtTools.common.clamp(newDeadzone, 0, 1);\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + }, + { + "description": "Deadzone for sticks, 0.2 by default (0 to 1)", + "name": "deadzone", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a stick of a gamepad is pushed in a given direction.", + "fullName": "Gamepad stick pushed (axis)", + "functionType": "Condition", + "name": "C_Axis_pushed", + "sentence": "_PARAM2_ stick of gamepad _PARAM1_ is pushed in direction _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;\r", + "const stick = eventsFunctionContext.getArgument(\"stick\").toUpperCase();\r", + "const direction = eventsFunctionContext.getArgument(\"direction\").toUpperCase();\r", + "\r", + "if (stick != \"LEFT\" && stick != \"RIGHT\") {\r", + " console.error('Parameter stick in condition: \"Gamepad stick pushed (axis)\", is not valid, must be LEFT or RIGHT');\r", + " return;\r", + "}\r", + "if (direction != \"UP\" && direction != \"DOWN\" && direction != \"LEFT\" && direction != \"RIGHT\" && direction != \"ANY\") {\r", + " console.error('Parameter direction in condition: \"Gamepad stick pushed (axis)\", is not valid, must be UP, DOWN, LEFT or RIGHT');\r", + " return;\r", + "}\r", + "/** @type {Gamepad} */\r", + "const gamepad = gdjs._extensionController.getGamepad(playerId);\r", + "if (!gamepad) {\r", + " // The gamepad is not connected.\r", + " return;\r", + "}\r", + "const axisValueX = stick === 'RIGHT' ? gamepad.axes[2] : gamepad.axes[0];\r", + "const axisValueY = stick === 'RIGHT' ? gamepad.axes[3] : gamepad.axes[1];\r", + "eventsFunctionContext.returnValue = gdjs._extensionController.isAxisPushed(playerId, direction, axisValueX, axisValueY);\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + }, + { + "description": "Stick: \"Left\" or \"Right\"", + "name": "stick", + "supplementaryInformation": "[\"Left\",\"Right\"]", + "type": "stringWithSelector" + }, + { + "description": "Direction", + "name": "direction", + "supplementaryInformation": "[\"Up\",\"Down\",\"Left\",\"Right\",\"Any\"]", + "type": "stringWithSelector" + } + ], + "objectGroups": [] + }, + { + "description": "Return the number of connected gamepads.", + "fullName": "Connected gamepads count", + "functionType": "Expression", + "name": "ConnectedGamepadsCount", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const gamepads = navigator.getGamepads ? navigator.getGamepads() : [];\r", + "let connectedGamepadCount = 0;\r", + "for (let playerId = 0; playerId < gamepads.length; playerId++) {\r", + " // Gamepads can be disconnected and become null\r", + " if (gamepads[playerId]) {\r", + " connectedGamepadCount++;\r", + " }\r", + "}\r", + "eventsFunctionContext.returnValue = connectedGamepadCount;\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [], + "objectGroups": [] + }, + { + "description": "Return a string containing informations about the specified gamepad.", + "fullName": "Gamepad type", + "functionType": "StringExpression", + "name": "GamepadType", + "sentence": "Player _PARAM1_ use _PARAM2_ controller", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;", + "/** @type {Gamepad} */", + "const gamepad = gdjs._extensionController.getGamepad(playerId);", + "if (!gamepad) {", + " // The gamepad is not connected.", + " return;", + "}", + "eventsFunctionContext.returnValue = (gamepad && gamepad.id) ? gamepad.id : \"No information for player \" + (playerId + 1)", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the specified gamepad has the specified information in its description. Useful to know if the gamepad is a Xbox or PS4 controller.", + "fullName": "Gamepad type", + "functionType": "Condition", + "name": "C_Controller_type", + "sentence": "Gamepad _PARAM1_ is a _PARAM2_ controller", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;", + "const controllerType = eventsFunctionContext.getArgument(\"controller_type\").toUpperCase();", + "/** @type {Gamepad} */", + "const gamepad = gdjs._extensionController.getGamepad(playerId);", + "if (!gamepad) {", + " // The gamepad is not connected.", + " return;", + "}", + "if (controllerType == \"XBOX\") {", + " eventsFunctionContext.returnValue = gdjs._extensionController.isXbox(gamepad);", + "} else {", + " eventsFunctionContext.returnValue = gamepad ? gamepad.id.toUpperCase().indexOf(controllerType) !== -1 : false;", + "}" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + }, + { + "description": "Type: \"Xbox\", \"PS4\", \"Steam\" or \"PS3\" (among other)", + "name": "controller_type", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "Check if a gamepad is connected.", + "fullName": "Gamepad connected", + "functionType": "Condition", + "name": "C_Controller_X_is_connected", + "sentence": "Gamepad _PARAM1_ is plugged and connected", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;", + "/** @type {Gamepad[]} */", + "const gamepads = navigator.getGamepads ? navigator.getGamepads() : [];", + "// If gamepad was disconnected it will be null (so this will return false)", + "// If gamepad was never connected it will be undefined (so this will return false)", + "eventsFunctionContext.returnValue = !!gamepads[playerId];" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Generate a vibration on the specified controller. Might only work if the game is running in a recent web browser.", + "fullName": "Gamepad vibration", + "functionType": "Action", + "name": "A_Vibrate_controller", + "sentence": "Make gamepad _PARAM1_ vibrate for _PARAM2_ seconds", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;", + "const duration = eventsFunctionContext.getArgument(\"Duration\") || 1;", + "/** @type {Gamepad} */", + "const gamepad = gdjs._extensionController.getGamepad(playerId);", + "if (!gamepad) {", + " // The gamepad is not connected.", + " return;", + "}", + "//Vibration work only on game in browser.", + "if (gamepad.vibrationActuator) {", + " gamepad.vibrationActuator.playEffect(\"dual-rumble\", {", + " startDelay: 0,", + " duration: duration * 1000,", + " weakMagnitude: 1.0,", + " strongMagnitude: 1.0", + " });", + "}" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + }, + { + "description": "Time of the vibration, in seconds (optional, default value is 1)", + "name": "Duration", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Generate an advanced vibration on the specified controller. Incompatible with Firefox.", + "fullName": "Advanced gamepad vibration", + "functionType": "Action", + "name": "A_Advanced_Vibration_Controller", + "sentence": "Make gamepad _PARAM1_ vibrate for _PARAM2_ seconds with the vibration magnitude of _PARAM3_ and _PARAM4_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { clamp } = gdjs.evtTools.common;", + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;", + "const duration = eventsFunctionContext.getArgument(\"Duration\") || 1;", + "const strongRumbleMagnitude = clamp(eventsFunctionContext.getArgument(\"StrongMagnitude\"), 0, 1);", + "const weakRumbleMagnitude = clamp(eventsFunctionContext.getArgument(\"WeakMagnitude\"), 0, 1);", + "/** @type {Gamepad} */", + "const gamepad = gdjs._extensionController.getGamepad(playerId);", + "if (!gamepad) {", + " // The gamepad is not connected.", + " return;", + "}", + "//Vibration work only on game in browser.", + "if (gamepad.vibrationActuator) {", + " gamepad.vibrationActuator.playEffect(\"dual-rumble\", {", + " startDelay: 0,", + " duration: duration * 1000,", + " weakMagnitude: weakRumbleMagnitude,", + " strongMagnitude: strongRumbleMagnitude", + " });", + "}", + "const player = gdjs._extensionController.getPlayer(playerId)", + "player.rumble.duration = duration;", + "player.rumble.elapsedTime = 0;", + "player.rumble.weakMagnitude = weakRumbleMagnitude;", + "player.rumble.strongMagnitude = strongRumbleMagnitude;" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + }, + { + "description": "Time of the vibration, in seconds (optional, default value is 1)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Strong rumble magnitude (from 0 to 1)", + "name": "StrongMagnitude", + "type": "expression" + }, + { + "description": "Weak rumble magnitude (from 0 to 1)", + "name": "WeakMagnitude", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Change a vibration on the specified controller. Incompatible with Firefox.", + "fullName": "Change gamepad active vibration", + "functionType": "Action", + "name": "A_Change_Vibration_Magnitude", + "sentence": "Change the vibration magnitude of _PARAM2_ & _PARAM3_ on gamepad _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { clamp } = gdjs.evtTools.common;", + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;", + "const player = gdjs._extensionController.getPlayer(playerId);", + "const elapsedTime = player.rumble.elapsedTime || 0;", + "const originalDuration = player.rumble.duration || 1;", + "const strongRumbleMagnitude = clamp(eventsFunctionContext.getArgument(\"StrongMagnitude\"), 0, 1);", + "const weakRumbleMagnitude = clamp(eventsFunctionContext.getArgument(\"WeakMagnitude\"), 0, 1);", + "/** @type {Gamepad} */", + "const gamepad = gdjs._extensionController.getGamepad(playerId);", + "if (!gamepad) {", + " // The gamepad is not connected.", + " return;", + "}", + "if (originalDuration - elapsedTime <= 0) return;", + "//Vibration work only on game in browser.", + "if (gamepad.vibrationActuator) {", + " gamepad.vibrationActuator.playEffect(\"dual-rumble\", {", + " startDelay: 0,", + " duration: 1000 * (originalDuration - elapsedTime),", + " weakMagnitude: weakRumbleMagnitude,", + " strongMagnitude: strongRumbleMagnitude", + " });", + "}", + "", + "player.rumble.weakMagnitude = weakRumbleMagnitude;", + "player.rumble.strongMagnitude = strongRumbleMagnitude;" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + }, + { + "description": "Strong rumble magnitude (from 0 to 1)", + "name": "StrongMagnitude", + "type": "expression" + }, + { + "description": "Weak rumble magnitude (from 0 to 1)", + "name": "WeakMagnitude", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Check if any button is released on a gamepad.", + "fullName": "Any gamepad button released", + "functionType": "Condition", + "name": "C_any_button_released", + "sentence": "Any button of gamepad _PARAM1_ is released", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "\r", + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;\r", + "const player = gdjs._extensionController.getPlayer(playerId)\r", + "eventsFunctionContext.returnValue = player.isAnyButtonReleased();" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the strength of the weak vibration motor on the gamepad of a player.", + "fullName": "Weak rumble magnitude", + "functionType": "Expression", + "name": "WeakVibrationMagnitude", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;\r", + "eventsFunctionContext.returnValue = gdjs._extensionController.getPlayer(playerId).rumble.weakMagnitude;" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the strength of the strong vibration motor on the gamepad of a player.", + "fullName": "Strong rumble magnitude", + "functionType": "Expression", + "name": "StrongVibrationMagnitude", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const playerId = eventsFunctionContext.getArgument(\"PlayerId\") - 1;\r", + "eventsFunctionContext.returnValue = gdjs._extensionController.getPlayer(playerId).rumble.strongMagnitude;" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "The gamepad identifier: 1, 2, 3 or 4", + "name": "PlayerId", + "type": "expression" + } + ], + "objectGroups": [] + } + ], + "eventsBasedBehaviors": [ + { + "description": "Control a platformer character with a gamepad.", + "fullName": "Platformer gamepad mapper", + "name": "PlatformerGamepadMapper", + "objectType": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Controller_X_is_connected" + }, + "parameters": [ + "", + "GamepadIdentifier", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "UseArrows", + "True", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Left\"", + "\"Left\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "PlatformerCharacter" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Right\"", + "\"Left\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "PlatformerCharacter" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Up\"", + "\"Left\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "PlatformerCharacter" + ] + }, + { + "type": { + "value": "PlatformBehavior::SimulateLadderKey" + }, + "parameters": [ + "Object", + "PlatformerCharacter" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Down\"", + "\"Left\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "PlatformerCharacter" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "UseLeftStick", + "True", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Axis_pushed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Left\"", + "\"Left\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "PlatformerCharacter" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Axis_pushed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Left\"", + "\"Right\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "PlatformerCharacter" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Axis_pushed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Left\"", + "\"Up\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "PlatformerCharacter" + ] + }, + { + "type": { + "value": "PlatformBehavior::SimulateLadderKey" + }, + "parameters": [ + "Object", + "PlatformerCharacter" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Axis_pushed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Left\"", + "\"Down\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "PlatformerCharacter" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "UseRightStick", + "True", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Axis_pushed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Right\"", + "\"Left\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "PlatformerCharacter" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Axis_pushed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Right\"", + "\"Right\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "PlatformerCharacter" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Axis_pushed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Right\"", + "\"Up\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "PlatformerCharacter" + ] + }, + { + "type": { + "value": "PlatformBehavior::SimulateLadderKey" + }, + "parameters": [ + "Object", + "PlatformerCharacter" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Axis_pushed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Right\"", + "\"Down\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "PlatformerCharacter" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "Buttons[JumpButton]", + "\"Left\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlatformBehavior::SimulateJumpKey" + }, + "parameters": [ + "Object", + "PlatformerCharacter" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Gamepads::PlatformerGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Platformer character behavior", + "extraInformation": [ + "PlatformBehavior::PlatformerObjectBehavior" + ], + "choices": [], + "name": "PlatformerCharacter" + }, + { + "value": "1", + "type": "Number", + "label": "Gamepad identifier (1, 2, 3 or 4)", + "name": "GamepadIdentifier" + }, + { + "value": "true", + "type": "Boolean", + "label": "Use directional pad", + "group": "Controls", + "name": "UseArrows" + }, + { + "value": "true", + "type": "Boolean", + "label": "Use left stick", + "group": "Controls", + "name": "UseLeftStick" + }, + { + "value": "", + "type": "Boolean", + "label": "Use right stick", + "group": "Controls", + "name": "UseRightStick" + }, + { + "value": "A or Cross", + "type": "Choice", + "label": "Jump button", + "group": "Controls", + "choices": [ + { + "label": "A or Cross", + "value": "A or Cross" + }, + { + "label": "B or Circle", + "value": "B or Circle" + }, + { + "label": "X or Square", + "value": "X or Square" + }, + { + "label": "Y or Triangle", + "value": "Y or Triangle" + }, + { + "label": "LB or L1", + "value": "LB or L1" + }, + { + "label": "RB or R1", + "value": "RB or R1" + }, + { + "label": "LT or L2", + "value": "LT or L2" + }, + { + "label": "RT or R2", + "value": "RT or R2" + } + ], + "name": "JumpButton" + } + ], + "sharedPropertyDescriptors": [] + }, + { + "description": "Control a 3D physics character with a gamepad.", + "fullName": "3D platformer gamepad mapper", + "name": "Platformer3DGamepadMapper", + "objectType": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Controller_X_is_connected" + }, + "parameters": [ + "", + "GamepadIdentifier", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::StickForce" + }, + "parameters": [ + "", + ">", + "0", + "GamepadIdentifier", + "JoystickIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SetForwardAngle" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D", + "=", + "Gamepads::StickAngle(GamepadIdentifier, JoystickIdentifier) + CameraAngle(Object.Layer())" + ] + }, + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateStick" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D", + "-90", + "Gamepads::StickForce(GamepadIdentifier, JoystickIdentifier)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "Buttons[JumpButton]", + "\"Left\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateJumpKey" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Gamepads::Platformer3DGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics character", + "extraInformation": [ + "Physics3D::PhysicsCharacter3D" + ], + "choices": [], + "name": "PhysicsCharacter3D" + }, + { + "value": "1", + "type": "Number", + "label": "Gamepad identifier (1, 2, 3 or 4)", + "name": "GamepadIdentifier" + }, + { + "value": "Left", + "type": "Choice", + "label": "Walk joystick", + "group": "Controls", + "choices": [ + { + "label": "Left", + "value": "Left" + }, + { + "label": "Right", + "value": "Right" + } + ], + "name": "JoystickIdentifier" + }, + { + "value": "A or Cross", + "type": "Choice", + "label": "Jump button", + "group": "Controls", + "choices": [ + { + "label": "A or Cross", + "value": "A or Cross" + }, + { + "label": "B or Circle", + "value": "B or Circle" + }, + { + "label": "X or Square", + "value": "X or Square" + }, + { + "label": "Y or Triangle", + "value": "Y or Triangle" + }, + { + "label": "LB or L1", + "value": "LB or L1" + }, + { + "label": "RB or R1", + "value": "RB or R1" + }, + { + "label": "LT or L2", + "value": "LT or L2" + }, + { + "label": "RT or R2", + "value": "RT or R2" + } + ], + "name": "JumpButton" + } + ], + "sharedPropertyDescriptors": [] + }, + { + "description": "Control a 3D physics character with a gamepad.", + "fullName": "3D shooter gamepad mapper", + "name": "Shooter3DGamepadMapper", + "objectType": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Controller_X_is_connected" + }, + "parameters": [ + "", + "GamepadIdentifier", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::StickForce" + }, + "parameters": [ + "", + ">", + "0", + "GamepadIdentifier", + "WalkStick", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateStick" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D", + "Gamepads::StickAngle(GamepadIdentifier, WalkStick)", + "Gamepads::StickForce(GamepadIdentifier, WalkStick)" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "Buttons[JumpButton]", + "\"Left\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCharacter3D::SimulateJumpKey" + }, + "parameters": [ + "Object", + "PhysicsCharacter3D" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Gamepads::Shooter3DGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics character", + "extraInformation": [ + "Physics3D::PhysicsCharacter3D" + ], + "choices": [], + "name": "PhysicsCharacter3D" + }, + { + "value": "1", + "type": "Number", + "label": "Gamepad identifier (1, 2, 3 or 4)", + "name": "GamepadIdentifier" + }, + { + "value": "Left", + "type": "Choice", + "label": "Walk joystick", + "group": "Controls", + "choices": [ + { + "label": "Left", + "value": "Left" + }, + { + "label": "Right", + "value": "Right" + } + ], + "name": "WalkStick" + }, + { + "value": "Right", + "type": "Choice", + "label": "Camera joystick", + "group": "Controls", + "choices": [ + { + "label": "Left", + "value": "Left" + }, + { + "label": "Right", + "value": "Right" + } + ], + "name": "CameraStick" + }, + { + "value": "A or Cross", + "type": "Choice", + "label": "Jump button", + "group": "Controls", + "choices": [ + { + "label": "A or Cross", + "value": "A or Cross" + }, + { + "label": "B or Circle", + "value": "B or Circle" + }, + { + "label": "X or Square", + "value": "X or Square" + }, + { + "label": "Y or Triangle", + "value": "Y or Triangle" + }, + { + "label": "LB or L1", + "value": "LB or L1" + }, + { + "label": "RB or R1", + "value": "RB or R1" + }, + { + "label": "LT or L2", + "value": "LT or L2" + }, + { + "label": "RT or R2", + "value": "RT or R2" + } + ], + "name": "JumpButton" + } + ], + "sharedPropertyDescriptors": [] + }, + { + "description": "Control camera rotations with a gamepad.", + "fullName": "First person camera gamepad mapper", + "name": "FirstPersonGamepadMapper", + "objectType": "", + "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", + "=", + "Gamepads::AcceleratedSpeed(CurrentRotationSpeedZ, Gamepads::StickForceX(GamepadIdentifier, CameraStick) * HorizontalRotationSpeedMax, HorizontalRotationSpeedMax, HorizontalRotationAcceleration, HorizontalRotationDeceleration)" + ] + }, + { + "type": { + "value": "SetAngle" + }, + "parameters": [ + "Object", + "+", + "CurrentRotationSpeedZ * TimeDelta()" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CurrentRotationSpeedY", + "=", + "Gamepads::AcceleratedSpeed(CurrentRotationSpeedY, Gamepads::StickForceY(GamepadIdentifier, 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": "Gamepads::FirstPersonGamepadMapper::LookFromObjectEyes" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Gamepads::FirstPersonGamepadMapper", + "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": "Gamepads::FirstPersonGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the maximum horizontal rotation speed of the object.", + "fullName": "Maximum horizontal rotation speed", + "functionType": "ExpressionAndCondition", + "group": "First person camera gamepad 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": "Gamepads::FirstPersonGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "HorizontalRotationSpeedMax", + "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": "Gamepads::FirstPersonGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the horizontal rotation acceleration of the object.", + "fullName": "Horizontal rotation acceleration", + "functionType": "ExpressionAndCondition", + "group": "First person camera gamepad 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": "Gamepads::FirstPersonGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "HorizontalRotationAcceleration", + "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": "Gamepads::FirstPersonGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the horizontal rotation deceleration of the object.", + "fullName": "Horizontal rotation deceleration", + "functionType": "ExpressionAndCondition", + "group": "First person camera gamepad 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": "Gamepads::FirstPersonGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "HorizontalRotationDeceleration", + "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": "Gamepads::FirstPersonGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the maximum vertical rotation speed of the object.", + "fullName": "Maximum vertical rotation speed", + "functionType": "ExpressionAndCondition", + "group": "First person camera gamepad 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": "Gamepads::FirstPersonGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalRotationSpeedMax", + "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": "Gamepads::FirstPersonGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the vertical rotation acceleration of the object.", + "fullName": "Vertical rotation acceleration", + "functionType": "ExpressionAndCondition", + "group": "First person camera gamepad 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": "Gamepads::FirstPersonGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalRotationAcceleration", + "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": "Gamepads::FirstPersonGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the vertical rotation deceleration of the object.", + "fullName": "Vertical rotation deceleration", + "functionType": "ExpressionAndCondition", + "group": "First person camera gamepad 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": "Gamepads::FirstPersonGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "VerticalRotationDeceleration", + "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": "Gamepads::FirstPersonGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the minimum vertical camera angle of the object.", + "fullName": "Minimum vertical camera angle", + "functionType": "ExpressionAndCondition", + "group": "First person camera gamepad 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": "Gamepads::FirstPersonGamepadMapper", + "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": "Gamepads::FirstPersonGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the maximum vertical camera angle of the object.", + "fullName": "Maximum vertical camera angle", + "functionType": "ExpressionAndCondition", + "group": "First person camera gamepad 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": "Gamepads::FirstPersonGamepadMapper", + "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": "Gamepads::FirstPersonGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the z position offset of the object.", + "fullName": "Z position offset", + "functionType": "ExpressionAndCondition", + "group": "First person camera gamepad 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": "Gamepads::FirstPersonGamepadMapper", + "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": "Gamepads::FirstPersonGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D capability", + "extraInformation": [ + "Scene3D::Base3DBehavior" + ], + "choices": [], + "name": "Object3D" + }, + { + "value": "1", + "type": "Number", + "label": "Gamepad identifier (1, 2, 3 or 4)", + "name": "GamepadIdentifier" + }, + { + "value": "Right", + "type": "Choice", + "label": "Camera joystick", + "choices": [ + { + "label": "Left", + "value": "Left" + }, + { + "label": "Right", + "value": "Right" + } + ], + "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" + } + ], + "sharedPropertyDescriptors": [] + }, + { + "description": "Control a 3D physics car with a gamepad.", + "fullName": "3D car gamepad mapper", + "name": "PhysicsCar3DGamepadMapper", + "objectType": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Controller_X_is_connected" + }, + "parameters": [ + "", + "GamepadIdentifier", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "UseArrows", + "True", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Left\"", + "\"Left\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateLeftKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Right\"", + "\"Left\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateRightKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "UseLeftStick", + "True", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Axis_pushed" + }, + "parameters": [ + "", + "1", + "\"Left\"", + "\"Any\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateSteeringStick" + }, + "parameters": [ + "Object", + "PhysicsCar3D", + "Gamepads::StickForceX(GamepadIdentifier, \"Left\")" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "UseRightStick", + "True", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Axis_pushed" + }, + "parameters": [ + "", + "1", + "\"Right\"", + "\"Any\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateSteeringStick" + }, + "parameters": [ + "Object", + "PhysicsCar3D", + "Gamepads::StickForceX(GamepadIdentifier, \"Right\")" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "1", + "\"LT\"", + "\"Up\"" + ] + }, + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "1", + "\"RT\"", + "\"Up\"" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateAcceleratorStick" + }, + "parameters": [ + "Object", + "PhysicsCar3D", + "Gamepads::TriggerPressure(GamepadIdentifier, \"RT\") - Gamepads::TriggerPressure(GamepadIdentifier, \"LT\")" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "Buttons[HandBrakeButton]", + "\"Left\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Physics3D::PhysicsCar3D::SimulateHandBrakeKey" + }, + "parameters": [ + "Object", + "PhysicsCar3D" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Gamepads::PhysicsCar3DGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics car", + "extraInformation": [ + "Physics3D::PhysicsCar3D" + ], + "choices": [], + "name": "PhysicsCar3D" + }, + { + "value": "1", + "type": "Number", + "label": "Gamepad identifier (1, 2, 3 or 4)", + "name": "GamepadIdentifier" + }, + { + "value": "true", + "type": "Boolean", + "label": "Use directional pad", + "group": "Controls", + "name": "UseArrows" + }, + { + "value": "true", + "type": "Boolean", + "label": "Use left stick", + "group": "Controls", + "name": "UseLeftStick" + }, + { + "value": "", + "type": "Boolean", + "label": "Use right stick", + "group": "Controls", + "name": "UseRightStick" + }, + { + "value": "B or Circle", + "type": "Choice", + "label": "Hand brake button", + "group": "Controls", + "choices": [ + { + "label": "A or Cross", + "value": "A or Cross" + }, + { + "label": "B or Circle", + "value": "B or Circle" + }, + { + "label": "X or Square", + "value": "X or Square" + }, + { + "label": "Y or Triangle", + "value": "Y or Triangle" + }, + { + "label": "LB or L1", + "value": "LB or L1" + }, + { + "label": "RB or R1", + "value": "RB or R1" + }, + { + "label": "LT or L2", + "value": "LT or L2" + }, + { + "label": "RT or R2", + "value": "RT or R2" + } + ], + "name": "HandBrakeButton" + } + ], + "sharedPropertyDescriptors": [] + }, + { + "description": "Control a top-down character with a gamepad.", + "fullName": "Top-down gamepad mapper", + "name": "TopDownGamepadMapper", + "objectType": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Controller_X_is_connected" + }, + "parameters": [ + "", + "GamepadIdentifier", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "UseArrows", + "True", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Left\"", + "\"Left\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Right\"", + "\"Left\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Up\"", + "\"Left\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Button_pressed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Down\"", + "\"Left\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "UseLeftStick", + "True", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "StickMode", + "=", + "\"Analog\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateStick" + }, + "parameters": [ + "Object", + "TopDownMovement", + "Gamepads::StickRotationValue(GamepadIdentifier, \"Left\")", + "Gamepads::StickForce(GamepadIdentifier, \"Left\")" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "StickMode", + "=", + "\"360°\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateStick" + }, + "parameters": [ + "Object", + "TopDownMovement", + "Gamepads::StickRotationValue(GamepadIdentifier, \"Left\")", + "sign(Gamepads::StickForce(GamepadIdentifier, \"Left\"))" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "StickMode", + "=", + "\"8 Directions\"" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Axis_pushed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Left\"", + "\"Left\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Axis_pushed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Left\"", + "\"Right\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Axis_pushed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Left\"", + "\"Up\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Axis_pushed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Left\"", + "\"Down\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "UseRightStick", + "True", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "StickMode", + "=", + "\"Analog\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateStick" + }, + "parameters": [ + "Object", + "TopDownMovement", + "Gamepads::StickRotationValue(GamepadIdentifier, \"Right\")", + "Gamepads::StickForce(GamepadIdentifier, \"Right\")" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "StickMode", + "=", + "\"360°\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateStick" + }, + "parameters": [ + "Object", + "TopDownMovement", + "sign(Gamepads::StickForce(GamepadIdentifier, \"Right\"))", + "1" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "StickMode", + "=", + "\"8 Directions\"" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Axis_pushed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Right\"", + "\"Left\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateLeftKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Axis_pushed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Right\"", + "\"Right\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateRightKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Axis_pushed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Right\"", + "\"Up\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateUpKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Gamepads::C_Axis_pushed" + }, + "parameters": [ + "", + "GamepadIdentifier", + "\"Right\"", + "\"Down\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "TopDownMovementBehavior::SimulateDownKey" + }, + "parameters": [ + "Object", + "TopDownMovement" + ] + } + ] + } + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Gamepads::TopDownGamepadMapper", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Top-down movement behavior", + "extraInformation": [ + "TopDownMovementBehavior::TopDownMovementBehavior" + ], + "choices": [], + "name": "TopDownMovement" + }, + { + "value": "1", + "type": "Number", + "label": "Gamepad identifier (1, 2, 3 or 4)", + "name": "GamepadIdentifier" + }, + { + "value": "true", + "type": "Boolean", + "label": "Use directional pad", + "group": "Controls", + "name": "UseArrows" + }, + { + "value": "true", + "type": "Boolean", + "label": "Use left stick", + "group": "Controls", + "name": "UseLeftStick" + }, + { + "value": "", + "type": "Boolean", + "label": "Use right stick", + "group": "Controls", + "name": "UseRightStick" + }, + { + "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" + } + ], + "sharedPropertyDescriptors": [] + } + ], + "eventsBasedObjects": [] + }, + { + "author": "", + "category": "Camera", + "extensionNamespace": "", + "fullName": "First person 3D camera", + "gdevelopVersion": ">=5.5.222", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQoJLnN0MXtmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEwO30NCjwvc3R5bGU+DQo8cGF0aCBjbGFzcz0ic3QwIiBkPSJNMjcsMTZjMCwwLTQuOSw3LTExLDdTNSwxNiw1LDE2czQuOS03LDExLTdTMjcsMTYsMjcsMTZ6Ii8+DQo8Y2lyY2xlIGNsYXNzPSJzdDAiIGN4PSIxNiIgY3k9IjE2IiByPSIzIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSIzLDkgMywzIDksMyAiLz4NCjxwb2x5bGluZSBjbGFzcz0ic3QwIiBwb2ludHM9IjksMjkgMywyOSAzLDIzICIvPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMjksMjMgMjksMjkgMjMsMjkgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSIyMywzIDI5LDMgMjksOSAiLz4NCjwvc3ZnPg0K", + "name": "FirstPersonCamera", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/Line Hero Pack/Master/SVG/Security and Protection/e8248ffd504c314e6a9d9d560c3a0fdad3398a5f125ae6a4c0654d4e68c86376_Security and Protection_security_protection_eye_scan_lock.svg", + "shortDescription": "Move the camera to look though objects eyes.", + "dimension": "3D", + "version": "1.0.5", + "description": [ + "Move the camera to look though objects eyes.", + "", + "This extension can be used with the \"Mouse pointer lock\" or \"Gamepad\" extension to build a first person camera.", + "", + "The first person example uses this extension ([open the project online](https://editor.gdevelop.io/?project=example://3d-first-person))." + ], + "origin": { + "identifier": "FirstPersonCamera", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d", + "camera", + "fps" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2", + "m8kleQHonagHWsvILDhyJhgVhuF2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [ + { + "description": "Move the camera to look though the object 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 of _PARAM2_ to look though _PARAM1_ eyes", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "CenterCameraOnObject" + }, + "parameters": [ + "", + "Object", + "", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "Object.Z()", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationX" + }, + "parameters": [ + "", + "=", + "- Object.RotationY() + 90", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationY" + }, + "parameters": [ + "", + "=", + "Object.RotationX()", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraAngle" + }, + "parameters": [ + "", + "=", + "Object.Angle() + 90", + "Layer", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Scene3D::Cube3DObject", + "type": "objectList" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + }, + { + "description": "Move the camera to look though the object 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": "LookFrom3DObjectEyes", + "sentence": "Move the camera of _PARAM3_ to look though _PARAM1_ eyes", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "CenterCameraOnObject" + }, + "parameters": [ + "", + "Object", + "", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraZ" + }, + "parameters": [ + "", + "=", + "Object.Object3D::Z()", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationX" + }, + "parameters": [ + "", + "=", + "- Object.Object3D::RotationY() + 90", + "Layer", + "" + ] + }, + { + "type": { + "value": "Scene3D::SetCameraRotationY" + }, + "parameters": [ + "", + "=", + "Object.Object3D::RotationX()", + "Layer", + "" + ] + }, + { + "type": { + "value": "SetCameraAngle" + }, + "parameters": [ + "", + "=", + "Object.Angle() + 90", + "Layer", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "3D Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Layer", + "name": "Layer", + "type": "layer" + } + ], + "objectGroups": [] + } + ], + "eventsBasedBehaviors": [], + "eventsBasedObjects": [] + }, + { + "author": "", + "category": "Camera", + "extensionNamespace": "", + "fullName": "Smooth Camera", + "gdevelopVersion": ">=5.5.222", + "helpPath": "/tutorials/follow-player-with-camera/", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQoJLnN0MXtmaWxsOm5vbmU7c3Ryb2tlOiMwMDAwMDA7c3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEwO30NCjwvc3R5bGU+DQo8cGF0aCBjbGFzcz0ic3QwIiBkPSJNMjQsMTNoLTZjLTEuMSwwLTItMC45LTItMlY1YzAtMS4xLDAuOS0yLDItMmg2YzEuMSwwLDIsMC45LDIsMnY2QzI2LDEyLjEsMjUuMSwxMywyNCwxM3oiLz4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0yNiw4djEwYzAsMS4xLTAuOSwyLTIsMkg4Yy0xLjEsMC0yLTAuOS0yLTJWOGMwLTEuMSwwLjktMiwyLTJoOCIvPg0KPGNpcmNsZSBjbGFzcz0ic3QwIiBjeD0iMjEiIGN5PSI4IiByPSIyIi8+DQo8Y2lyY2xlIGNsYXNzPSJzdDAiIGN4PSIxMSIgY3k9IjE2IiByPSIxIi8+DQo8cmVjdCB4PSI5IiB5PSI5IiBjbGFzcz0ic3QwIiB3aWR0aD0iNCIgaGVpZ2h0PSIzIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSIyMSwyOSAyMSwyOSAxMSwyOSAxMSwyOSAiLz4NCjxwb2x5bGluZSBjbGFzcz0ic3QwIiBwb2ludHM9IjE4LDIwIDE4LDI5IDE0LDI5IDE0LDIwICIvPg0KPHJlY3QgeD0iNyIgeT0iMyIgY2xhc3M9InN0MCIgd2lkdGg9IjQiIGhlaWdodD0iMyIvPg0KPC9zdmc+DQo=", + "name": "SmoothCamera", + "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Computers and Hardware/Computers and Hardware_camcoder_gopro_go_pro_camera.svg", + "shortDescription": "Smoothly scroll to follow an object.", + "dimension": "2D", + "version": "0.4.5", + "description": [ + "The camera follows an object according to:", + "- a frame rate independent catch-up speed to make the scrolling from smooth to strong", + "- a maximum speed to do linear following ([open the project online](https://editor.gdevelop.io/?project=example://platformer-with-tilemap)) or slow down the camera when teleporting the object", + "- a follow-free zone to avoid scrolling on small movements", + "- an offset to see further in one direction", + "- an extra delay and catch-up speed to give an impression of speed (useful for dash)", + "- position forecasting and delay to simulate a cameraman response time", + "", + "A platformer dedicated behavior allows to switch of settings when the character is in air or on the floor. This can be used to stabilize the camera when jumping." + ], + "origin": { + "identifier": "SmoothCamera", + "name": "gdevelop-extension-store" + }, + "tags": [ + "camera", + "scrolling", + "follow", + "smooth", + "platformer", + "platform" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [], + "eventsBasedBehaviors": [ + { + "description": "Smoothly scroll to follow an object.", + "fullName": "Smooth Camera", + "name": "SmoothCamera", + "objectType": "", + "quickCustomizationVisibility": "hidden", + "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": "Update private properties through setters to check their values and initialize state." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetLeftwardSpeed" + }, + "parameters": [ + "Object", + "Behavior", + "LeftwardSpeed", + "log(1 - )" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetRightwardSpeed" + }, + "parameters": [ + "Object", + "Behavior", + "RightwardSpeed", + "log(1 - )" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetUpwardSpeed" + }, + "parameters": [ + "Object", + "Behavior", + "UpwardSpeed", + "log(1 - )" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetDownwardSpeed" + }, + "parameters": [ + "Object", + "Behavior", + "DownwardSpeed", + "log(1 - )" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetLeftwardSpeedMax" + }, + "parameters": [ + "Object", + "Behavior", + "LeftwardSpeedMax", + "log(1 - )" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetRightwardSpeedMax" + }, + "parameters": [ + "Object", + "Behavior", + "RightwardSpeedMax", + "log(1 - )" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetUpwardSpeedMax" + }, + "parameters": [ + "Object", + "Behavior", + "UpwardSpeedMax", + "log(1 - )" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetDownwardSpeedMax" + }, + "parameters": [ + "Object", + "Behavior", + "DownwardSpeedMax", + "log(1 - )" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetFollowFreeAreaLeft" + }, + "parameters": [ + "Object", + "Behavior", + "FollowFreeAreaLeft", + "log(1 - )" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetFollowFreeAreaRight" + }, + "parameters": [ + "Object", + "Behavior", + "FollowFreeAreaRight", + "log(1 - )" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetFollowFreeAreaTop" + }, + "parameters": [ + "Object", + "Behavior", + "FollowFreeAreaTop", + "log(1 - )" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetFollowFreeAreaBottom" + }, + "parameters": [ + "Object", + "Behavior", + "FollowFreeAreaBottom", + "log(1 - )" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraDelay", + "=", + "CameraDelay" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "IsCalledManually", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SmoothCamera::SmoothCamera::DoMoveCameraCloser" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "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": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsCalledManually", + "True", + "" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::DoMoveCameraCloser" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "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": "Delaying and forecasting can be used at the same time.\nForecasting only use the positions that are older than the one used for delaying.\nThe behavior uses a position history that is split in 2 arrays:\n- one for delaying the position (from TimeFromStart to TimeFromStart - CamearDelay)\n- one for forecasting the position (from TimeFromStart - CamearDelay to TimeFromStart - CamearDelay - ForecastHistoryDuration" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SmoothCamera::SmoothCamera::UpdateDelayedPosition" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::UpdateForecastedPosition" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + }, + { + "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) * 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) * 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) * speed^timeDelta\ncameraX(t) = targetX + (cameraX(t - timeDelta) - targetX) * exp(timeDelta * ln(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": "BooleanVariable" + }, + "parameters": [ + "FollowOnX", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OldX", + "=", + "CameraX(Object.Layer(), 0)" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CameraCenterX" + }, + "parameters": [ + "", + ">", + "Object.Behavior::FreeAreaRight()", + "Object.Layer()", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "=", + "Object.Behavior::FreeAreaRight()\n+ (CameraX(Object.Layer(), 0) - Object.Behavior::FreeAreaRight())\n* exp(TimeDelta() * LogLeftwardSpeed)", + "Object.Layer()", + "0" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CameraCenterX" + }, + "parameters": [ + "", + "<", + "OldX - LeftwardSpeedMax * TimeDelta()", + "Object.Layer()", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "=", + "OldX - LeftwardSpeedMax * TimeDelta()", + "Object.Layer()", + "0" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CameraCenterX" + }, + "parameters": [ + "", + "<", + "Object.Behavior::FreeAreaLeft()", + "Object.Layer()", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "=", + "Object.Behavior::FreeAreaLeft()\n+ (CameraX(Object.Layer(), 0) - Object.Behavior::FreeAreaLeft())\n* exp(TimeDelta() * LogRightwardSpeed)", + "Object.Layer()", + "0" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CameraCenterX" + }, + "parameters": [ + "", + ">", + "OldX + RightwardSpeedMax * TimeDelta()", + "Object.Layer()", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetCameraCenterX" + }, + "parameters": [ + "", + "=", + "OldX + RightwardSpeedMax * TimeDelta()", + "Object.Layer()", + "0" + ] + } + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "FollowOnY", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "OldY", + "=", + "CameraY(Object.Layer(), 0)" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CameraCenterY" + }, + "parameters": [ + "", + ">", + "Object.Behavior::FreeAreaBottom()", + "Object.Layer()", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "=", + "Object.Behavior::FreeAreaBottom()\n+ (CameraY(Object.Layer(), 0) - Object.Behavior::FreeAreaBottom())\n* exp(TimeDelta() * LogUpwardSpeed)", + "Object.Layer()", + "0" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CameraCenterY" + }, + "parameters": [ + "", + "<", + "OldY - UpwardSpeedMax * TimeDelta()", + "Object.Layer()", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "=", + "OldY - UpwardSpeedMax * TimeDelta()", + "Object.Layer()", + "0" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CameraCenterY" + }, + "parameters": [ + "", + "<", + "Object.Behavior::FreeAreaTop()", + "Object.Layer()", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "=", + "Object.Behavior::FreeAreaTop()\n+ (CameraY(Object.Layer(), 0) - Object.Behavior::FreeAreaTop())\n* exp(TimeDelta() * LogDownwardSpeed)", + "Object.Layer()", + "0" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CameraCenterY" + }, + "parameters": [ + "", + ">", + "OldY + DownwardSpeedMax * TimeDelta()", + "Object.Layer()", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetCameraCenterY" + }, + "parameters": [ + "", + "=", + "OldY + DownwardSpeedMax * TimeDelta()", + "Object.Layer()", + "0" + ] + } + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Delay the camera according to a maximum speed and catch up the delay.", + "fullName": "Wait and catch up", + "functionType": "Action", + "name": "WaitAndCatchUp", + "sentence": "Delay the camera of _PARAM0_ during: _PARAM2_ seconds according to the maximum speed _PARAM3_;_PARAM4_ and catch up in _PARAM5_ seconds", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Maybe the catch-up show be done in constant pixel speed instead of constant time speed." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "WaitingEnd", + "=", + "TimeFromStart() + NewWaitingDuration" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "WaitingSpeedXMax", + "=", + "NewWaitingSpeedXMax" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "WaitingSpeedYMax", + "=", + "NewWaitingSpeedYMax" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraDelayCatchUpDuration", + "=", + "NewCatchUpDuration" + ] + } + ] + }, + { + "disabled": true, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "DebuggerTools::ConsoleLog" + }, + "parameters": [ + "\"Wait and catch up\"", + "\"info\"", + "\"SmoothCamera\"" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Waiting duration (in seconds)", + "name": "NewWaitingDuration", + "type": "expression" + }, + { + "description": "Waiting maximum camera target speed X", + "name": "NewWaitingSpeedXMax", + "type": "expression" + }, + { + "description": "Waiting maximum camera target speed Y", + "name": "NewWaitingSpeedYMax", + "type": "expression" + }, + { + "description": "Catch up duration (in seconds)", + "name": "NewCatchUpDuration", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Draw the targeted and actual camera position.", + "fullName": "Draw debug", + "functionType": "Action", + "name": "DrawDebug", + "sentence": "Draw targeted and actual camera position for _PARAM0_ on _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "PrimitiveDrawing::FillOpacity" + }, + "parameters": [ + "ShapePainter", + "=", + "0" + ] + } + ] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Path used by the forecasting", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "Object.VariableChildCount(__SmoothCamera.ForecastHistoryTime)", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Index", + "=", + "0" + ] + }, + { + "type": { + "value": "PrimitiveDrawing::OutlineColor" + }, + "parameters": [ + "ShapePainter", + "\"245;166;35\"" + ] + }, + { + "type": { + "value": "PrimitiveDrawing::BeginFillPath" + }, + "parameters": [ + "ShapePainter", + "Object.Variable(__SmoothCamera.ForecastHistoryX[0])", + "Object.Variable(__SmoothCamera.ForecastHistoryY[0])" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "Object.VariableChildCount(__SmoothCamera.ForecastHistoryX)", + "conditions": [], + "actions": [ + { + "type": { + "value": "PrimitiveDrawing::PathLineTo" + }, + "parameters": [ + "ShapePainter", + "Object.Variable(__SmoothCamera.ForecastHistoryX[Index])", + "Object.Variable(__SmoothCamera.ForecastHistoryY[Index])" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Index", + "+", + "1" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "PrimitiveDrawing::EndFillPath" + }, + "parameters": [ + "ShapePainter" + ] + } + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Follow-free area.", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "FollowFreeAreaLeft", + "!=", + "0" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "FollowFreeAreaRight", + "!=", + "0" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "FollowFreeAreaTop", + "!=", + "0" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "FollowFreeAreaBottom", + "!=", + "0" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "PrimitiveDrawing::OutlineColor" + }, + "parameters": [ + "ShapePainter", + "\"126;211;33\"" + ] + }, + { + "type": { + "value": "PrimitiveDrawing::Rectangle" + }, + "parameters": [ + "ShapePainter", + "Object.Behavior::FreeAreaLeft() - 1", + "Object.Behavior::FreeAreaTop() - 1", + "Object.Behavior::FreeAreaRight() + 1", + "Object.Behavior::FreeAreaBottom() + 1" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Linear regression vector used by the forecasting.", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "PrimitiveDrawing::OutlineColor" + }, + "parameters": [ + "ShapePainter", + "\"208;2;27\"" + ] + }, + { + "type": { + "value": "PrimitiveDrawing::LineV2" + }, + "parameters": [ + "ShapePainter", + "ProjectedOldestX", + "ProjectedOldestY", + "ProjectedNewestX", + "ProjectedNewestY", + "1" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Targeted and actual camera position", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "PrimitiveDrawing::Circle" + }, + "parameters": [ + "ShapePainter", + "ForecastedX", + "ForecastedY", + "3" + ] + }, + { + "type": { + "value": "PrimitiveDrawing::LineV2" + }, + "parameters": [ + "ShapePainter", + "CameraX(Object.Layer(), 0)", + "CameraY(Object.Layer(), 0) - 4", + "CameraX(Object.Layer(), 0)", + "CameraY(Object.Layer(), 0) + 4", + "1" + ] + }, + { + "type": { + "value": "PrimitiveDrawing::LineV2" + }, + "parameters": [ + "ShapePainter", + "CameraX(Object.Layer(), 0) - 4", + "CameraY(Object.Layer(), 0)", + "CameraX(Object.Layer(), 0) + 4", + "CameraY(Object.Layer(), 0)", + "1" + ] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Shape painter", + "name": "ShapePainter", + "supplementaryInformation": "PrimitiveDrawing::Drawer", + "type": "objectList" + } + ], + "objectGroups": [] + }, + { + "description": "Enable or disable the following on X axis.", + "fullName": "Follow on X", + "functionType": "Action", + "group": "Camera configuration", + "name": "SetFollowOnX", + "sentence": "The camera follows _PARAM0_ on X axis: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "FollowOnX", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "FollowOnX", + "True", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Follow on X axis", + "name": "Value", + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Enable or disable the following on Y axis.", + "fullName": "Follow on Y", + "functionType": "Action", + "group": "Camera configuration", + "name": "SetFollowOnY", + "sentence": "The camera follows _PARAM0_ on Y axis: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "FollowOnY", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "FollowOnY", + "True", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Follow on Y axis", + "name": "Value", + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Change the camera follow free area right border.", + "fullName": "Follow free area right border", + "functionType": "Action", + "group": "Camera configuration", + "name": "SetFollowFreeAreaRight", + "sentence": "Change the camera follow free area right border of _PARAM0_: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FollowFreeAreaRight", + "=", + "max(0, Value)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Follow free area right border", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Change the camera follow free area left border.", + "fullName": "Follow free area left border", + "functionType": "Action", + "group": "Camera configuration", + "name": "SetFollowFreeAreaLeft", + "sentence": "Change the camera follow free area left border of _PARAM0_: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FollowFreeAreaLeft", + "=", + "max(0, Value)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Follow free area left border", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Change the camera follow free area top border.", + "fullName": "Follow free area top border", + "functionType": "Action", + "group": "Camera configuration", + "name": "SetFollowFreeAreaTop", + "sentence": "Change the camera follow free area top border of _PARAM0_: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FollowFreeAreaTop", + "=", + "max(0, Value)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Follow free area top border", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Change the camera follow free area bottom border.", + "fullName": "Follow free area bottom border", + "functionType": "Action", + "group": "Camera configuration", + "name": "SetFollowFreeAreaBottom", + "sentence": "Change the camera follow free area bottom border of _PARAM0_: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "FollowFreeAreaBottom", + "=", + "max(0, Value)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Follow free area bottom border", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Change the camera leftward maximum speed (in pixels per second).", + "fullName": "Leftward maximum speed", + "functionType": "Action", + "group": "Camera configuration", + "name": "SetLeftwardSpeedMax", + "sentence": "Change the camera leftward maximum speed of _PARAM0_: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "LeftwardSpeedMax", + "=", + "max(0, Value)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Leftward maximum speed (in pixels per second)", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Change the camera rightward maximum speed (in pixels per second).", + "fullName": "Rightward maximum speed", + "functionType": "Action", + "group": "Camera configuration", + "name": "SetRightwardSpeedMax", + "sentence": "Change the camera rightward maximum speed of _PARAM0_: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "RightwardSpeedMax", + "=", + "max(0, Value)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Rightward maximum speed (in pixels per second)", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Change the camera upward maximum speed (in pixels per second).", + "fullName": "Upward maximum speed", + "functionType": "Action", + "group": "Camera configuration", + "name": "SetUpwardSpeedMax", + "sentence": "Change the camera upward maximum speed of _PARAM0_: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "UpwardSpeedMax", + "=", + "max(0, Value)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Upward maximum speed (in pixels per second)", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Change the camera downward maximum speed (in pixels per second).", + "fullName": "Downward maximum speed", + "functionType": "Action", + "group": "Camera configuration", + "name": "SetDownwardSpeedMax", + "sentence": "Change the camera downward maximum speed of _PARAM0_: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DownwardSpeedMax", + "=", + "max(0, Value)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Downward maximum speed (in pixels per second)", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Change the camera leftward catch-up speed (in ratio per second).", + "fullName": "Leftward catch-up speed", + "functionType": "Action", + "group": "Camera configuration", + "name": "SetLeftwardSpeed", + "sentence": "Change the camera leftward catch-up speed of _PARAM0_: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "LeftwardSpeed", + "=", + "clamp(0, 1, Value)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "LogLeftwardSpeed", + "=", + "log(1 - Value)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Leftward catch-up speed (in ratio per second)", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Change the camera rightward catch-up speed (in ratio per second).", + "fullName": "Rightward catch-up speed", + "functionType": "Action", + "group": "Camera configuration", + "name": "SetRightwardSpeed", + "sentence": "Change the camera rightward catch-up speed of _PARAM0_: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "RightwardSpeed", + "=", + "clamp(0, 1, Value)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "LogRightwardSpeed", + "=", + "log(1 - Value)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Rightward catch-up speed (in ratio per second)", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Change the camera downward catch-up speed (in ratio per second).", + "fullName": "Downward catch-up speed", + "functionType": "Action", + "group": "Camera configuration", + "name": "SetDownwardSpeed", + "sentence": "Change the camera downward catch-up speed of _PARAM0_: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DownwardSpeed", + "=", + "clamp(0, 1, Value)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "LogDownwardSpeed", + "=", + "log(1 - Value)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Downward catch-up speed (in ratio per second)", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Change the camera upward catch-up speed (in ratio per second).", + "fullName": "Upward catch-up speed", + "functionType": "Action", + "group": "Camera configuration", + "name": "SetUpwardSpeed", + "sentence": "Change the camera upward catch-up speed of _PARAM0_: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "UpwardSpeed", + "=", + "clamp(0, 1, Value)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "LogUpwardSpeed", + "=", + "log(1 - Value)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Upward catch-up speed (in ratio per second)", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the camera offset on X axis of the object. This is not the current difference between the object and the camera position.", + "fullName": "Camera offset X", + "functionType": "ExpressionAndCondition", + "group": "Camera configuration", + "name": "OffsetX", + "sentence": "the camera offset on X axis", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "CameraOffsetX" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "OffsetX", + "name": "SetOffsetXOp", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraOffsetX", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Change the camera offset on X axis of an object.", + "fullName": "Camera Offset X", + "functionType": "Action", + "group": "Camera configuration", + "name": "SetOffsetX", + "private": true, + "sentence": "Change the camera offset on X axis of _PARAM0_: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Deprecated use SetOffsetXOp instead." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetOffsetXOp" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Value", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Camera offset X", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the camera offset on Y axis of the object. This is not the current difference between the object and the camera position.", + "fullName": "Camera offset Y", + "functionType": "ExpressionAndCondition", + "group": "Camera configuration", + "name": "OffsetY", + "sentence": "the camera offset on Y axis", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "CameraOffsetY" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "OffsetY", + "name": "SetOffsetYOp", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraOffsetY", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Change the camera offset on Y axis of an object.", + "fullName": "Camera Offset Y", + "functionType": "Action", + "group": "Camera configuration", + "name": "SetOffsetY", + "private": true, + "sentence": "Change the camera offset on Y axis of _PARAM0_: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Deprecated use SetOffsetYOp instead." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetOffsetYOp" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "CameraOffsetY", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Camera offset Y", + "name": "CameraOffsetY", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Change the camera forecast time (in seconds).", + "fullName": "Forecast time", + "functionType": "Action", + "group": "Camera configuration", + "name": "SetForecastTime", + "sentence": "Change the camera forecast time of _PARAM0_: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastTime", + "=", + "min(0, Value)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Forecast time", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Change the camera delay (in seconds).", + "fullName": "Camera delay", + "functionType": "Action", + "group": "Camera configuration", + "name": "SetCameraDelay", + "sentence": "Change the camera delay of _PARAM0_: _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraDelay", + "=", + "max(0, Value)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Camera delay", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return follow free area left border X.", + "fullName": "Free area left", + "functionType": "Expression", + "group": "Private", + "name": "FreeAreaLeft", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ForecastedX + CameraOffsetX - FollowFreeAreaLeft" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Return follow free area right border X.", + "fullName": "Free area right", + "functionType": "Expression", + "group": "Private", + "name": "FreeAreaRight", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ForecastedX + CameraOffsetX + FollowFreeAreaRight" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Return follow free area bottom border Y.", + "fullName": "Free area bottom", + "functionType": "Expression", + "group": "Private", + "name": "FreeAreaBottom", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ForecastedY + CameraOffsetY + FollowFreeAreaBottom" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Return follow free area top border Y.", + "fullName": "Free area top", + "functionType": "Expression", + "group": "Private", + "name": "FreeAreaTop", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ForecastedY + CameraOffsetY - FollowFreeAreaTop" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Update delayed position and delayed history. This is called in doStepPreEvents.", + "fullName": "Update delayed position", + "functionType": "Action", + "group": "Private", + "name": "UpdateDelayedPosition", + "private": true, + "sentence": "Update delayed position and delayed history of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Add the previous position to have enough (2) positions to evaluate the extra delay for waiting mode." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SmoothCamera::SmoothCamera::IsWaiting" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "Object.VariableChildCount(__SmoothCamera.ObjectTime)", + "=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "ObjectVariablePushNumber" + }, + "parameters": [ + "Object", + "__SmoothCamera.ObjectTime", + "TimeFromStart()" + ] + }, + { + "type": { + "value": "ObjectVariablePushNumber" + }, + "parameters": [ + "Object", + "__SmoothCamera.ObjectX", + "DelayedCenterX" + ] + }, + { + "type": { + "value": "ObjectVariablePushNumber" + }, + "parameters": [ + "Object", + "__SmoothCamera.ObjectY", + "DelayedCenterY" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Use the object center when no delay is asked." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DelayedCenterX", + "=", + "Object.CenterX()" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DelayedCenterY", + "=", + "Object.CenterY()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SmoothCamera::SmoothCamera::IsDelayed" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SmoothCamera::SmoothCamera::AddForecastHistoryPosition" + }, + "parameters": [ + "Object", + "Behavior", + "TimeFromStart()", + "Object.CenterX()", + "Object.CenterY()", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "SmoothCamera::SmoothCamera::IsDelayed" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::IsWaiting" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "ObjectVariablePushNumber" + }, + "parameters": [ + "Object", + "__SmoothCamera.ObjectTime", + "TimeFromStart()" + ] + }, + { + "type": { + "value": "ObjectVariablePushNumber" + }, + "parameters": [ + "Object", + "__SmoothCamera.ObjectX", + "Object.CenterX()" + ] + }, + { + "type": { + "value": "ObjectVariablePushNumber" + }, + "parameters": [ + "Object", + "__SmoothCamera.ObjectY", + "Object.CenterY()" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Remove history entries that are too old to be useful for delaying and pass it to the history for forecasting." + }, + { + "infiniteLoopWarning": true, + "type": "BuiltinCommonInstructions::While", + "whileConditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "Object.VariableChildCount(__SmoothCamera.ObjectTime)", + ">=", + "2" + ] + }, + { + "type": { + "value": "VarObjet" + }, + "parameters": [ + "Object", + "__SmoothCamera.ObjectTime[1]", + "<", + "TimeFromStart() - Object.Behavior::CurrentDelay()" + ] + } + ], + "conditions": [], + "actions": [ + { + "type": { + "value": "SmoothCamera::SmoothCamera::AddForecastHistoryPosition" + }, + "parameters": [ + "Object", + "Behavior", + "Object.Variable(__SmoothCamera.ObjectTime[0])", + "Object.Variable(__SmoothCamera.ObjectX[0])", + "Object.Variable(__SmoothCamera.ObjectY[0])", + "" + ] + }, + { + "type": { + "value": "ObjectVariableRemoveAt" + }, + "parameters": [ + "Object", + "__SmoothCamera.ObjectTime", + "0" + ] + }, + { + "type": { + "value": "ObjectVariableRemoveAt" + }, + "parameters": [ + "Object", + "__SmoothCamera.ObjectX", + "0" + ] + }, + { + "type": { + "value": "ObjectVariableRemoveAt" + }, + "parameters": [ + "Object", + "__SmoothCamera.ObjectY", + "0" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Don't move the camera if there is not enough history." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DelayedCenterX", + "=", + "Object.Variable(__SmoothCamera.ObjectX[0])" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DelayedCenterY", + "=", + "Object.Variable(__SmoothCamera.ObjectY[0])" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "Object.VariableChildCount(__SmoothCamera.ObjectTime)", + ">=", + "2" + ] + }, + { + "type": { + "value": "VarObjet" + }, + "parameters": [ + "Object", + "__SmoothCamera.ObjectTime[0]", + "<", + "TimeFromStart() - Object.Behavior::CurrentDelay()" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Add the extra delay that could be needed to respect the speed limit in waiting mode.\n\nspeedRatio = min(speedMaxX / historySpeedX, speedMaxY / historySpeedY)\ndelay += min(0, timeDelta * (1 - speedRatio))" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SmoothCamera::SmoothCamera::IsWaiting" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraExtraDelay", + "+", + "max(0, TimeDelta() * (1 - min(WaitingSpeedXMax * abs(Object.Variable(__SmoothCamera.ObjectX[1]) - Object.Variable(__SmoothCamera.ObjectX[0])), WaitingSpeedYMax * abs(Object.Variable(__SmoothCamera.ObjectY[1]) - Object.Variable(__SmoothCamera.ObjectY[0]))) / (Object.Variable(__SmoothCamera.ObjectTime[1]) - Object.Variable(__SmoothCamera.ObjectTime[0]))))" + ] + } + ], + "events": [ + { + "disabled": true, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "DebuggerTools::ConsoleLog" + }, + "parameters": [ + "\"Extra delay: \" + ToString(CameraExtraDelay)", + "\"info\"", + "\"SmoothCamera\"" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "The time with delay is now between the first 2 indexes" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DelayedCenterX", + "=", + "lerp(Object.Variable(__SmoothCamera.ObjectX[1]), Object.Variable(__SmoothCamera.ObjectX[0]), ((TimeFromStart() - Object.Behavior::CurrentDelay()) - Object.Variable(__SmoothCamera.ObjectTime[1])) / (Object.Variable(__SmoothCamera.ObjectTime[0]) - Object.Variable(__SmoothCamera.ObjectTime[1])))" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DelayedCenterY", + "=", + "lerp(Object.Variable(__SmoothCamera.ObjectY[1]), Object.Variable(__SmoothCamera.ObjectY[0]), ((TimeFromStart() - Object.Behavior::CurrentDelay()) - Object.Variable(__SmoothCamera.ObjectTime[1])) / (Object.Variable(__SmoothCamera.ObjectTime[0]) - Object.Variable(__SmoothCamera.ObjectTime[1])))" + ] + } + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SmoothCamera::SmoothCamera::IsDelayed" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "SmoothCamera::SmoothCamera::IsWaiting" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ObjectVariableClearChildren" + }, + "parameters": [ + "Object", + "__SmoothCamera.ObjectTime" + ] + }, + { + "type": { + "value": "ObjectVariableClearChildren" + }, + "parameters": [ + "Object", + "__SmoothCamera.ObjectX" + ] + }, + { + "type": { + "value": "ObjectVariableClearChildren" + }, + "parameters": [ + "Object", + "__SmoothCamera.ObjectY" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SmoothCamera::SmoothCamera::IsWaiting" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraDelayCatchUpSpeed", + "=", + "CameraExtraDelay / CameraDelayCatchUpDuration" + ] + } + ], + "events": [ + { + "disabled": true, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "DebuggerTools::ConsoleLog" + }, + "parameters": [ + "\"Start to catch up\"", + "\"info\"", + "\"SmoothCamera\"" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SmoothCamera::SmoothCamera::IsWaiting" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "CameraExtraDelay", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "CameraExtraDelay", + "=", + "max(0, CameraExtraDelay -CameraDelayCatchUpSpeed * TimeDelta())" + ] + } + ], + "events": [ + { + "disabled": true, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "DebuggerTools::ConsoleLog" + }, + "parameters": [ + "\"Catching up delay: \" + ToString(CameraExtraDelay)", + "\"info\"", + "\"SmoothCamera\"" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the camera following target is delayed from the object.", + "fullName": "Camera is delayed", + "functionType": "Condition", + "name": "IsDelayed", + "private": true, + "sentence": "The camera of _PARAM0_ is delayed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "Object.Behavior::CurrentDelay()", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Return the current camera delay.", + "fullName": "Current delay", + "functionType": "Expression", + "name": "CurrentDelay", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "CameraDelay + CameraExtraDelay" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the camera following is waiting at a reduced speed.", + "fullName": "Camera is waiting", + "functionType": "Condition", + "name": "IsWaiting", + "private": true, + "sentence": "The camera of _PARAM0_ is waiting", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "WaitingEnd", + ">", + "TimeFromStart()" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Add a position to the history for forecasting. This is called 2 times in UpdateDelayedPosition.", + "fullName": "Add forecast history position", + "functionType": "Action", + "group": "Private", + "name": "AddForecastHistoryPosition", + "private": true, + "sentence": "Add the time:_PARAM2_ and position: _PARAM3_; _PARAM4_ to the forecast history of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "ForecastHistoryDuration", + ">", + "0" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "ForecastTime", + ">", + "0" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "ObjectVariablePushNumber" + }, + "parameters": [ + "Object", + "__SmoothCamera.ForecastHistoryTime", + "Time" + ] + }, + { + "type": { + "value": "ObjectVariablePushNumber" + }, + "parameters": [ + "Object", + "__SmoothCamera.ForecastHistoryX", + "ObjectX" + ] + }, + { + "type": { + "value": "ObjectVariablePushNumber" + }, + "parameters": [ + "Object", + "__SmoothCamera.ForecastHistoryY", + "ObjectY" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Remove history entries that are too old to be useful.\nKeep at least 2 positions because no forecast can be done with less positions." + }, + { + "infiniteLoopWarning": true, + "type": "BuiltinCommonInstructions::While", + "whileConditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "Object.VariableChildCount(__SmoothCamera.ForecastHistoryTime)", + ">=", + "3" + ] + }, + { + "type": { + "value": "VarObjet" + }, + "parameters": [ + "Object", + "__SmoothCamera.ForecastHistoryTime[0]", + "<", + "TimeFromStart() - CameraDelay - CameraExtraDelay - ForecastHistoryDuration" + ] + } + ], + "conditions": [], + "actions": [ + { + "type": { + "value": "ObjectVariableRemoveAt" + }, + "parameters": [ + "Object", + "__SmoothCamera.ForecastHistoryTime", + "0" + ] + }, + { + "type": { + "value": "ObjectVariableRemoveAt" + }, + "parameters": [ + "Object", + "__SmoothCamera.ForecastHistoryX", + "0" + ] + }, + { + "type": { + "value": "ObjectVariableRemoveAt" + }, + "parameters": [ + "Object", + "__SmoothCamera.ForecastHistoryY", + "0" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "Time", + "name": "Time", + "type": "expression" + }, + { + "description": "Object X", + "name": "ObjectX", + "type": "expression" + }, + { + "description": "Object Y", + "name": "ObjectY", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Update forecasted position. This is called in doStepPreEvents.", + "fullName": "Update forecasted position", + "functionType": "Action", + "group": "Private", + "name": "UpdateForecastedPosition", + "private": true, + "sentence": "Update forecasted position of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastedX", + "=", + "DelayedCenterX" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastedY", + "=", + "DelayedCenterY" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Simple linear regression\ny = A * x + B\n\nA = Covariance / VarianceX\nB = MeanY - A * MeanX\n\nNote than we could use only one position every N positions to reduce the process time,\nbut if we really need efficient process JavaScript and circular queues are a must." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "Object.VariableChildCount(__SmoothCamera.ForecastHistoryTime)", + ">=", + "2" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "ForecastHistoryDuration", + ">", + "0" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "ForecastTime", + ">", + "0" + ] + } + ] + } + ], + "actions": [], + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Mean X", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastHistoryMeanX", + "=", + "0" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Index", + "=", + "0" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "Object.VariableChildCount(__SmoothCamera.ForecastHistoryX)", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastHistoryMeanX", + "+", + "Object.Variable(__SmoothCamera.ForecastHistoryX[Index])" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Index", + "+", + "1" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastHistoryMeanX", + "/", + "Object.VariableChildCount(__SmoothCamera.ForecastHistoryX)" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Mean Y", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastHistoryMeanY", + "=", + "0" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Index", + "=", + "0" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "Object.VariableChildCount(__SmoothCamera.ForecastHistoryY)", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastHistoryMeanY", + "+", + "Object.Variable(__SmoothCamera.ForecastHistoryY[Index])" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Index", + "+", + "1" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastHistoryMeanY", + "/", + "Object.VariableChildCount(__SmoothCamera.ForecastHistoryY)" + ] + } + ] + }, + { + "disabled": true, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "DebuggerTools::ConsoleLog" + }, + "parameters": [ + "\"Mean: \" + ToString(ForecastHistoryMeanX) + \" \" + ToString(ForecastHistoryMeanY)", + "", + "" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Variance and Covariance", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "VarianceX = sum((X[i] - MeanX)²)\nVarianceY = sum((Y[i] - MeanY)²)\nCovariance = sum((X[i] - MeanX) * (Y[i] - MeanY))" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastHistoryVarianceX", + "=", + "0" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastHistoryVarianceY", + "=", + "0" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastHistoryCovariance", + "=", + "0" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Index", + "=", + "0" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "Object.VariableChildCount(__SmoothCamera.ForecastHistoryX)", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastHistoryVarianceX", + "+", + "pow(Object.Variable(__SmoothCamera.ForecastHistoryX[Index]) - ForecastHistoryMeanX, 2)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastHistoryVarianceY", + "+", + "pow(Object.Variable(__SmoothCamera.ForecastHistoryY[Index]) - ForecastHistoryMeanY, 2)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastHistoryCovariance", + "+", + "(Object.Variable(__SmoothCamera.ForecastHistoryX[Index]) - ForecastHistoryMeanX)\n*\n(Object.Variable(__SmoothCamera.ForecastHistoryY[Index]) - ForecastHistoryMeanY)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Index", + "+", + "1" + ] + } + ] + }, + { + "disabled": true, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "DebuggerTools::ConsoleLog" + }, + "parameters": [ + "\"Variances: \" + ToString(ForecastHistoryVarianceX) + \" \" + ToString(ForecastHistoryVarianceY) + \" \" + ToString(ForecastHistoryCovariance)", + "\"info\"", + "\"SmoothCamera\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "abs(ForecastHistoryVarianceX)", + "<", + "1" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "abs(ForecastHistoryVarianceY)", + "<", + "1" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastedX", + "=", + "DelayedCenterX" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastedY", + "=", + "DelayedCenterY" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "abs(ForecastHistoryVarianceX)", + ">=", + "1" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "abs(ForecastHistoryVarianceY)", + ">=", + "1" + ] + } + ] + } + ], + "actions": [], + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Linear function parameters", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "y = A * x + B\n\nA = Covariance / VarianceX\nB = MeanY - A * MeanX" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "abs(ForecastHistoryVarianceX)", + ">=", + "abs(ForecastHistoryVarianceY)" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastHistoryLinearA", + "=", + "ForecastHistoryCovariance / ForecastHistoryVarianceX" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastHistoryLinearB", + "=", + "ForecastHistoryMeanY - ForecastHistoryLinearA * ForecastHistoryMeanX" + ] + } + ], + "events": [ + { + "disabled": true, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "DebuggerTools::ConsoleLog" + }, + "parameters": [ + "\"Linear: \" + ToString(ForecastHistoryLinearA) + \" \" + ToString(ForecastHistoryLinearB)", + "\"info\"", + "\"SmoothCamera\"" + ] + } + ] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Projection", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SmoothCamera::SmoothCamera::ProjectHistoryEnds" + }, + "parameters": [ + "Object", + "Behavior", + "Object.Variable(__SmoothCamera.ForecastHistoryX[0])", + "Object.Variable(__SmoothCamera.ForecastHistoryY[0])", + "Object.Variable(__SmoothCamera.ForecastHistoryX[Object.VariableChildCount(__SmoothCamera.ForecastHistoryX) - 1])", + "Object.Variable(__SmoothCamera.ForecastHistoryY[Object.VariableChildCount(__SmoothCamera.ForecastHistoryY) - 1])", + "" + ] + } + ] + } + ], + "parameters": [] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Axis permutation to avoid a ratio between 2 numbers near 0." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "abs(ForecastHistoryVarianceX)", + "<", + "abs(ForecastHistoryVarianceY)" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastHistoryLinearA", + "=", + "ForecastHistoryCovariance / ForecastHistoryVarianceY" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastHistoryLinearB", + "=", + "ForecastHistoryMeanX - ForecastHistoryLinearA * ForecastHistoryMeanY" + ] + } + ], + "events": [ + { + "disabled": true, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "DebuggerTools::ConsoleLog" + }, + "parameters": [ + "\"Linear: \" + ToString(ForecastHistoryLinearA) + \" \" + ToString(ForecastHistoryLinearB)", + "\"info\"", + "\"SmoothCamera\"" + ] + } + ] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Projection", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SmoothCamera::SmoothCamera::ProjectHistoryEnds" + }, + "parameters": [ + "Object", + "Behavior", + "Object.Variable(__SmoothCamera.ForecastHistoryY[0])", + "Object.Variable(__SmoothCamera.ForecastHistoryX[0])", + "Object.Variable(__SmoothCamera.ForecastHistoryY[Object.VariableChildCount(__SmoothCamera.ForecastHistoryY) - 1])", + "Object.Variable(__SmoothCamera.ForecastHistoryX[Object.VariableChildCount(__SmoothCamera.ForecastHistoryX) - 1])", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Permute back axis" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Index", + "=", + "ProjectedOldestX" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ProjectedOldestX", + "=", + "ProjectedOldestY" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ProjectedOldestY", + "=", + "Index" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Index", + "=", + "ProjectedNewestX" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ProjectedNewestX", + "=", + "ProjectedNewestY" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ProjectedNewestY", + "=", + "Index" + ] + } + ] + } + ], + "parameters": [] + }, + { + "disabled": true, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "DebuggerTools::ConsoleLog" + }, + "parameters": [ + "\"Oldest: \" + ToString(ProjectedOldestX) + \" \" + ToString(ProjectedOldestY)", + "\"info\"", + "\"SmoothCamera\"" + ] + } + ] + }, + { + "disabled": true, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "DebuggerTools::ConsoleLog" + }, + "parameters": [ + "\"Newest: \" + ToString(ProjectedNewestX) + \" \" + ToString(ProjectedNewestY)", + "\"info\"", + "\"SmoothCamera\"" + ] + } + ] + } + ] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Forecasted position", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastedX", + "=", + "ProjectedNewestX + ( ProjectedNewestX - ProjectedOldestX) * Object.Behavior::ForecastTimeRatio()" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ForecastedY", + "=", + "ProjectedNewestY + ( ProjectedNewestY - ProjectedOldestY) * Object.Behavior::ForecastTimeRatio()" + ] + } + ] + }, + { + "disabled": true, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "DebuggerTools::ConsoleLog" + }, + "parameters": [ + "\"Forecasted: \" + ToString(ForecastedX) + \" \" + ToString(ForecastedY)", + "\"info\"", + "\"SmoothCamera\"" + ] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [] + } + ] + } + ], + "parameters": [] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Project history ends position to have the vector on the line from linear regression. This function is only called by UpdateForecastedPosition.", + "fullName": "Project history ends", + "functionType": "Action", + "group": "Private", + "name": "ProjectHistoryEnds", + "private": true, + "sentence": "Project history oldest: _PARAM2_;_PARAM3_ and newest position: _PARAM4_;_PARAM5_ of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Perpendicular line:\npA = -1/a; \npB = -pA * x + y\n\nIntersection:\n/ ProjectedY = a * ProjectedX + b\n\\ ProjectedY = pA * ProjectedX + b\n\nSolution that is cleaned out from indeterminism (like 0 / 0 or infinity / infinity):\nProjectedX= (x + (y - b) * a) / (a² + 1)\nProjectedY = y + (x * a - y + b) / (a² + 1)" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ProjectedNewestX", + "=", + "(NewestX + (NewestY - ForecastHistoryLinearB) * ForecastHistoryLinearA) / (1 + pow(ForecastHistoryLinearA, 2))" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ProjectedNewestY", + "=", + "NewestY + (NewestX * ForecastHistoryLinearA - NewestY \n+ ForecastHistoryLinearB) / (1 + pow(ForecastHistoryLinearA, 2))" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ProjectedOldestX", + "=", + "(OldestX + (OldestY - ForecastHistoryLinearB) * ForecastHistoryLinearA) / (1 + pow(ForecastHistoryLinearA, 2))" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ProjectedOldestY", + "=", + "OldestY + (OldestX * ForecastHistoryLinearA - OldestY \n+ ForecastHistoryLinearB) / (1 + pow(ForecastHistoryLinearA, 2))" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + }, + { + "description": "OldestX", + "name": "OldestX", + "type": "expression" + }, + { + "description": "OldestY", + "name": "OldestY", + "type": "expression" + }, + { + "description": "Newest X", + "name": "NewestX", + "type": "expression" + }, + { + "description": "Newest Y", + "name": "NewestY", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the ratio between forecast time and the duration of the history. This function is only called by UpdateForecastedPosition.", + "fullName": "Forecast time ratio", + "functionType": "Expression", + "group": "Private", + "name": "ForecastTimeRatio", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "- ForecastTime / (Object.Variable(__SmoothCamera.ForecastHistoryTime[0]) - Object.Variable(__SmoothCamera.ForecastHistoryTime[Object.VariableChildCount(__SmoothCamera.ForecastHistoryTime) - 1]))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothCamera", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "propertyDescriptors": [ + { + "value": "0.9", + "type": "Number", + "label": "Leftward catch-up speed (in ratio per second)", + "description": "", + "group": "Catch-up speed", + "extraInformation": [], + "name": "LeftwardSpeed" + }, + { + "value": "0.9", + "type": "Number", + "label": "Rightward catch-up speed (in ratio per second)", + "description": "", + "group": "Catch-up speed", + "extraInformation": [], + "name": "RightwardSpeed" + }, + { + "value": "0.9", + "type": "Number", + "label": "Upward catch-up speed (in ratio per second)", + "description": "", + "group": "Catch-up speed", + "extraInformation": [], + "name": "UpwardSpeed" + }, + { + "value": "0.9", + "type": "Number", + "label": "Downward catch-up speed (in ratio per second)", + "description": "", + "group": "Catch-up speed", + "extraInformation": [], + "name": "DownwardSpeed" + }, + { + "value": "true", + "type": "Boolean", + "label": "Follow on X axis", + "description": "", + "group": "", + "extraInformation": [], + "name": "FollowOnX" + }, + { + "value": "true", + "type": "Boolean", + "label": "Follow on Y axis", + "description": "", + "group": "", + "extraInformation": [], + "name": "FollowOnY" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Follow free area left border", + "description": "", + "group": "Position", + "extraInformation": [], + "advanced": true, + "name": "FollowFreeAreaLeft" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Follow free area right border", + "description": "", + "group": "Position", + "extraInformation": [], + "advanced": true, + "name": "FollowFreeAreaRight" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Follow free area top border", + "description": "", + "group": "Position", + "extraInformation": [], + "advanced": true, + "name": "FollowFreeAreaTop" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Follow free area bottom border", + "description": "", + "group": "Position", + "extraInformation": [], + "advanced": true, + "name": "FollowFreeAreaBottom" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Camera offset X", + "description": "", + "group": "Position", + "extraInformation": [], + "advanced": true, + "name": "CameraOffsetX" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Camera offset Y", + "description": "", + "group": "Position", + "extraInformation": [], + "advanced": true, + "name": "CameraOffsetY" + }, + { + "value": "0", + "type": "Number", + "unit": "Second", + "label": "Camera delay", + "description": "", + "group": "Timing", + "extraInformation": [], + "deprecated": true, + "name": "CameraDelay" + }, + { + "value": "0", + "type": "Number", + "unit": "Second", + "label": "Forecast time", + "description": "", + "group": "Timing", + "extraInformation": [], + "deprecated": true, + "name": "ForecastTime" + }, + { + "value": "0", + "type": "Number", + "unit": "Second", + "label": "Forecast history duration", + "description": "", + "group": "Timing", + "extraInformation": [], + "deprecated": true, + "name": "ForecastHistoryDuration" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "LogLeftwardSpeed" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "LogRightwardSpeed" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "LogDownwardSpeed" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "LogUpwardSpeed" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "DelayedCenterX" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "DelayedCenterY" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "ForecastHistoryMeanX" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "ForecastHistoryMeanY" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "ForecastHistoryVarianceX" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "ForecastHistoryCovariance" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "ForecastHistoryLinearA" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "ForecastHistoryLinearB" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "ForecastedX" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "ForecastedY" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "ProjectedNewestX" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "ProjectedNewestY" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "ProjectedOldestX" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "ProjectedOldestY" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "ForecastHistoryVarianceY" + }, + { + "value": "", + "type": "Number", + "label": "Index (local variable)", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "Index" + }, + { + "value": "0", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "CameraDelayCatchUpSpeed" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "CameraExtraDelay" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "WaitingSpeedXMax" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "WaitingSpeedYMax" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "WaitingEnd" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "CameraDelayCatchUpDuration" + }, + { + "value": "9000", + "type": "Number", + "unit": "PixelSpeed", + "label": "Leftward maximum speed", + "description": "", + "group": "Maximum speed", + "extraInformation": [], + "advanced": true, + "name": "LeftwardSpeedMax" + }, + { + "value": "9000", + "type": "Number", + "unit": "PixelSpeed", + "label": "Rightward maximum speed", + "description": "", + "group": "Maximum speed", + "extraInformation": [], + "advanced": true, + "name": "RightwardSpeedMax" + }, + { + "value": "9000", + "type": "Number", + "unit": "PixelSpeed", + "label": "Upward maximum speed", + "description": "", + "group": "Maximum speed", + "extraInformation": [], + "advanced": true, + "name": "UpwardSpeedMax" + }, + { + "value": "9000", + "type": "Number", + "unit": "PixelSpeed", + "label": "Downward maximum speed", + "description": "", + "group": "Maximum speed", + "extraInformation": [], + "advanced": true, + "name": "DownwardSpeedMax" + }, + { + "value": "", + "type": "Number", + "label": "OldX (local variable)", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "OldX" + }, + { + "value": "", + "type": "Number", + "label": "OldY (local variable)", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "OldY" + }, + { + "value": "", + "type": "Boolean", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "IsCalledManually" + } + ], + "sharedPropertyDescriptors": [] + }, + { + "description": "Smoothly scroll to follow a character and stabilize the camera when jumping.", + "fullName": "Smooth platformer camera", + "name": "SmoothPlatformerCamera", + "objectType": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "PlatformBehavior::IsJumping" + }, + "parameters": [ + "Object", + "PlatformerCharacter" + ] + }, + { + "type": { + "inverted": true, + "value": "PlatformBehavior::IsFalling" + }, + "parameters": [ + "Object", + "PlatformerCharacter" + ] + } + ], + "actions": [ + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetFollowFreeAreaBottom" + }, + "parameters": [ + "Object", + "SmoothCamera", + "FloorFollowFreeAreaTop", + "" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetFollowFreeAreaTop" + }, + "parameters": [ + "Object", + "SmoothCamera", + "FloorFollowFreeAreaBottom", + "" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetUpwardSpeed" + }, + "parameters": [ + "Object", + "SmoothCamera", + "FloorUpwardSpeed", + "" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetDownwardSpeed" + }, + "parameters": [ + "Object", + "SmoothCamera", + "FloorDownwardSpeed", + "" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetUpwardSpeedMax" + }, + "parameters": [ + "Object", + "SmoothCamera", + "FloorUpwardSpeedMax", + "" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetDownwardSpeedMax" + }, + "parameters": [ + "Object", + "SmoothCamera", + "FloorDownwardSpeedMax", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "PlatformBehavior::IsJumping" + }, + "parameters": [ + "Object", + "PlatformerCharacter" + ] + }, + { + "type": { + "value": "PlatformBehavior::IsFalling" + }, + "parameters": [ + "Object", + "PlatformerCharacter" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetFollowFreeAreaBottom" + }, + "parameters": [ + "Object", + "SmoothCamera", + "AirFollowFreeAreaTop", + "" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetFollowFreeAreaTop" + }, + "parameters": [ + "Object", + "SmoothCamera", + "AirFollowFreeAreaBottom", + "" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetUpwardSpeed" + }, + "parameters": [ + "Object", + "SmoothCamera", + "AirUpwardSpeed", + "" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetDownwardSpeed" + }, + "parameters": [ + "Object", + "SmoothCamera", + "AirDownwardSpeed", + "" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetUpwardSpeedMax" + }, + "parameters": [ + "Object", + "SmoothCamera", + "AirUpwardSpeedMax", + "" + ] + }, + { + "type": { + "value": "SmoothCamera::SmoothCamera::SetDownwardSpeedMax" + }, + "parameters": [ + "Object", + "SmoothCamera", + "AirDownwardSpeedMax", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "SmoothCamera::SmoothPlatformerCamera", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "Platformer character behavior", + "description": "", + "group": "", + "extraInformation": [ + "PlatformBehavior::PlatformerObjectBehavior" + ], + "name": "PlatformerCharacter" + }, + { + "value": "", + "type": "Behavior", + "label": "Smooth camera behavior", + "description": "", + "group": "", + "extraInformation": [ + "SmoothCamera::SmoothCamera" + ], + "name": "SmoothCamera" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "JumpOriginY" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Follow free area top in the air", + "description": "", + "group": "Position", + "extraInformation": [], + "name": "AirFollowFreeAreaTop" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Follow free area bottom in the air", + "description": "", + "group": "Position", + "extraInformation": [], + "name": "AirFollowFreeAreaBottom" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Follow free area top on the floor", + "description": "", + "group": "Position", + "extraInformation": [], + "name": "FloorFollowFreeAreaTop" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Follow free area bottom on the floor", + "description": "", + "group": "Position", + "extraInformation": [], + "name": "FloorFollowFreeAreaBottom" + }, + { + "value": "0.95", + "type": "Number", + "label": "Upward speed in the air (in ratio per second)", + "description": "", + "group": "Catch-up speed", + "extraInformation": [], + "name": "AirUpwardSpeed" + }, + { + "value": "0.95", + "type": "Number", + "label": "Downward speed in the air (in ratio per second)", + "description": "", + "group": "Catch-up speed", + "extraInformation": [], + "name": "AirDownwardSpeed" + }, + { + "value": "0.9", + "type": "Number", + "label": "Upward speed on the floor (in ratio per second)", + "description": "", + "group": "Catch-up speed", + "extraInformation": [], + "name": "FloorUpwardSpeed" + }, + { + "value": "0.9", + "type": "Number", + "label": "Downward speed on the floor (in ratio per second)", + "description": "", + "group": "Catch-up speed", + "extraInformation": [], + "name": "FloorDownwardSpeed" + }, + { + "value": "9000", + "type": "Number", + "unit": "PixelSpeed", + "label": "Upward maximum speed in the air", + "description": "", + "group": "Maximum speed", + "extraInformation": [], + "name": "AirUpwardSpeedMax" + }, + { + "value": "9000", + "type": "Number", + "unit": "PixelSpeed", + "label": "Downward maximum speed in the air", + "description": "", + "group": "Maximum speed", + "extraInformation": [], + "name": "AirDownwardSpeedMax" + }, + { + "value": "9000", + "type": "Number", + "unit": "PixelSpeed", + "label": "Upward maximum speed on the floor", + "description": "", + "group": "Maximum speed", + "extraInformation": [], + "name": "FloorUpwardSpeedMax" + }, + { + "value": "9000", + "type": "Number", + "unit": "PixelSpeed", + "label": "Downward maximum speed on the floor", + "description": "", + "group": "Maximum speed", + "extraInformation": [], + "name": "FloorDownwardSpeedMax" + } + ], + "sharedPropertyDescriptors": [] + } + ], + "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": "", + "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" + } + ] + } + ] + }, + { + "author": "", + "category": "Input", + "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.", + "dimension": "3D", + "version": "1.1.1", + "description": "3D platformer and 3D shooter keyboard controls. Add one of the \"mapper\" behaviors on an object having the \"3D physics character\" behavior so that it is controlled by the keyboard.", + "origin": { + "identifier": "PhysicsCharacter3DKeyMapper", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [], + "eventsBasedBehaviors": [ + { + "description": "Control a 3D physics character with a keyboard for a platformer or a top-down game.", + "fullName": "3D platformer keyboard mapper", + "name": "Platformer3DKeyboardMapper", + "objectType": "", + "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": [] + } + ], + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics character", + "description": "", + "group": "", + "extraInformation": [ + "Physics3D::PhysicsCharacter3D" + ], + "name": "PhysicsCharacter3D" + }, + { + "value": "Space", + "type": "KeyboardKey", + "label": "Jump key", + "description": "", + "group": "", + "extraInformation": [], + "name": "Jump" + }, + { + "value": "w", + "type": "KeyboardKey", + "label": "Up key", + "description": "", + "group": "", + "extraInformation": [], + "name": "Up" + }, + { + "value": "a", + "type": "KeyboardKey", + "label": "Left key", + "description": "", + "group": "", + "extraInformation": [], + "name": "Left" + }, + { + "value": "s", + "type": "KeyboardKey", + "label": "Down key", + "description": "", + "group": "", + "extraInformation": [], + "name": "Down" + }, + { + "value": "d", + "type": "KeyboardKey", + "label": "Right key", + "description": "", + "group": "", + "extraInformation": [], + "name": "Right" + }, + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "CameraAngle" + }, + { + "value": "", + "type": "Boolean", + "label": "Camera is locked for the frame", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "LockedCameraAngle" + } + ], + "sharedPropertyDescriptors": [] + }, + { + "description": "Control a 3D physics character with a keyboard for a first or third person shooter.", + "fullName": "3D shooter keyboard mapper", + "name": "Shooter3DKeyboardMapper", + "objectType": "", + "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": [] + } + ], + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D physics character", + "description": "", + "group": "", + "extraInformation": [ + "Physics3D::PhysicsCharacter3D" + ], + "name": "PhysicsCharacter3D" + }, + { + "value": "Space", + "type": "KeyboardKey", + "label": "Jump key", + "description": "", + "group": "", + "extraInformation": [], + "name": "Jump" + }, + { + "value": "w", + "type": "KeyboardKey", + "label": "Up key", + "description": "", + "group": "", + "extraInformation": [], + "name": "Up" + }, + { + "value": "a", + "type": "KeyboardKey", + "label": "Left key", + "description": "", + "group": "", + "extraInformation": [], + "name": "Left" + }, + { + "value": "s", + "type": "KeyboardKey", + "label": "Down key", + "description": "", + "group": "", + "extraInformation": [], + "name": "Down" + }, + { + "value": "d", + "type": "KeyboardKey", + "label": "Right key", + "description": "", + "group": "", + "extraInformation": [], + "name": "Right" + } + ], + "sharedPropertyDescriptors": [] + } + ], + "eventsBasedObjects": [] + } + ], + "externalLayouts": [], + "tests": [ + { + "name": "Talking to a villager", + "description": "Walks to an NPC and presses X: the dialog opens.", + "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 npc = harness.getObjects('NPC').reduce((best, one) => (one.x < best.x ? one : best));", + "const player = harness.getRuntimeObject(harness.getObjects('Player')[0].id);", + "player.setPosition(npc.x, npc.y + 120);", + "harness.setKeyPressed('Up', true);", + "await harness.stepUntil(() => Math.hypot(harness.getObjects('Player')[0].x - npc.x, harness.getObjects('Player')[0].y - npc.y) < 70, { ...DT, maxFrames: 30 });", + "harness.setKeyPressed('Up', false);", + "// \"x\" talks to the villager the player is touching.", + "harness.setKeyPressed('x', true);", + "const opened = await harness.stepUntil(() => harness.getCurrentRuntimeScene().getLayer('Dialog layer').isVisible(), { ...DT, maxFrames: 10 });", + "harness.releaseAllInputs();", + "harness.assert(opened, 'Walking into a villager opens the dialog.');" + ] + } + ] +} diff --git a/examples/starting-expanded-3d-top-down-rpg/theme-slots.json b/examples/starting-expanded-3d-top-down-rpg/theme-slots.json new file mode 100644 index 000000000..51da1146c --- /dev/null +++ b/examples/starting-expanded-3d-top-down-rpg/theme-slots.json @@ -0,0 +1,59 @@ +{ + "version": 1, + "objects": { + "scene:Game Scene/Player": "character.player", + "scene:Game Scene/NPC": "character.npc", + "scene:Game Scene/Building_Obstacle": "env.wall", + "scene:Game Scene/GrassTufts": { + "Model": "nature.grass" + }, + "scene:Game Scene/House": "building.tower", + "scene:Game Scene/HouseLarge": "building.tower", + "scene:Game Scene/Inn": "building.tower", + "scene:Game Scene/Blacksmith": "building.tower", + "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.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": {}, + "ignoredObjects": [ + "scene:Game Scene/Camera", + "scene:Game Scene/Well", + "scene:Game Scene/MarketStand", + "scene:Game Scene/Bridge", + "scene:Game Scene/MapLimit", + "scene:Game Scene/RiverBank", + "object:Water3D::Water3D/AreaPlaceholder", + "object:Terrain3D::Terrain3D/AreaPlaceholder", + "object:Terrain3D::TerrainGrass/AreaPlaceholder", + "object:Terrain3D::ScatteredModels/AreaPlaceholder" + ] +} diff --git a/examples/starting-expanded-first-person-survival-crafting/README.md b/examples/starting-expanded-first-person-survival-crafting/README.md new file mode 100644 index 000000000..f9bd36dbb --- /dev/null +++ b/examples/starting-expanded-first-person-survival-crafting/README.md @@ -0,0 +1,3 @@ +# 3D First Person Survival Crafting Expanded + +A 3D physics-based first-person survival crafting game in an open forest on hills made with a 3D terrain, around a camp in a clearing. The player can look around, walk, jump and fall. Trees and boulders can be clicked to harvest resources, and objects can be picked up, stored in an inventory and placed back onto the ground using raycasting. The inventory uses UI icons to display its contents and select the item to use. This setup works well for any game where the player gathers resources and crafts things with them. The game supports both keyboard/mouse controls and mobile touch controls. diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/Bonfire.glb b/examples/starting-expanded-first-person-survival-crafting/assets/Bonfire.glb new file mode 100644 index 000000000..c9287691d Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/Bonfire.glb differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/Bush 1.glb b/examples/starting-expanded-first-person-survival-crafting/assets/Bush 1.glb new file mode 100644 index 000000000..52eca18b2 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/Bush 1.glb differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/Bush Berries 1.glb b/examples/starting-expanded-first-person-survival-crafting/assets/Bush Berries 1.glb new file mode 100644 index 000000000..a618f90c2 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/Bush Berries 1.glb differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/Flat dark joystick border.png b/examples/starting-expanded-first-person-survival-crafting/assets/Flat dark joystick border.png new file mode 100644 index 000000000..b4e83e112 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/Flat dark joystick border.png differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/Flat dark joystick thumb.png b/examples/starting-expanded-first-person-survival-crafting/assets/Flat dark joystick thumb.png new file mode 100644 index 000000000..8acf179f8 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/Flat dark joystick thumb.png differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/Flowers.glb b/examples/starting-expanded-first-person-survival-crafting/assets/Flowers.glb new file mode 100644 index 000000000..bd90ef960 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/Flowers.glb differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/NotoSansArabicUI-Black.ttf b/examples/starting-expanded-first-person-survival-crafting/assets/NotoSansArabicUI-Black.ttf new file mode 100644 index 000000000..9541c2cf3 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/NotoSansArabicUI-Black.ttf differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/Placement thud.wav b/examples/starting-expanded-first-person-survival-crafting/assets/Placement thud.wav new file mode 100644 index 000000000..2af4f56d2 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/Placement thud.wav differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/Plant 1.glb b/examples/starting-expanded-first-person-survival-crafting/assets/Plant 1.glb new file mode 100644 index 000000000..57b6b270b Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/Plant 1.glb differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/SelectionBox.png b/examples/starting-expanded-first-person-survival-crafting/assets/SelectionBox.png new file mode 100644 index 000000000..ac521735f Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/SelectionBox.png differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/Skybox_Back.png b/examples/starting-expanded-first-person-survival-crafting/assets/Skybox_Back.png new file mode 100644 index 000000000..aa0891ef2 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/Skybox_Back.png differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/Skybox_Bottom.png b/examples/starting-expanded-first-person-survival-crafting/assets/Skybox_Bottom.png new file mode 100644 index 000000000..16e34f3da Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/Skybox_Bottom.png differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/Skybox_Front.png b/examples/starting-expanded-first-person-survival-crafting/assets/Skybox_Front.png new file mode 100644 index 000000000..018d5faac Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/Skybox_Front.png differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/Skybox_Left.png b/examples/starting-expanded-first-person-survival-crafting/assets/Skybox_Left.png new file mode 100644 index 000000000..e4053f388 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/Skybox_Left.png differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/Skybox_Right.png b/examples/starting-expanded-first-person-survival-crafting/assets/Skybox_Right.png new file mode 100644 index 000000000..d14c192b1 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/Skybox_Right.png differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/Skybox_Top.png b/examples/starting-expanded-first-person-survival-crafting/assets/Skybox_Top.png new file mode 100644 index 000000000..e83ef6a1b Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/Skybox_Top.png differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/Small_rock_1.glb b/examples/starting-expanded-first-person-survival-crafting/assets/Small_rock_1.glb new file mode 100644 index 000000000..4ee996aff Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/Small_rock_1.glb differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/StarterToggle_Keyboard.png b/examples/starting-expanded-first-person-survival-crafting/assets/StarterToggle_Keyboard.png new file mode 100644 index 000000000..aabe87250 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/StarterToggle_Keyboard.png differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/StarterToggle_Touch.png b/examples/starting-expanded-first-person-survival-crafting/assets/StarterToggle_Touch.png new file mode 100644 index 000000000..204462798 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/StarterToggle_Touch.png differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/StartingInventorySlot.png b/examples/starting-expanded-first-person-survival-crafting/assets/StartingInventorySlot.png new file mode 100644 index 000000000..f93f37cf0 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/StartingInventorySlot.png differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/StartingWall.png b/examples/starting-expanded-first-person-survival-crafting/assets/StartingWall.png new file mode 100644 index 000000000..b3a097887 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/StartingWall.png differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/Target round button.png b/examples/starting-expanded-first-person-survival-crafting/assets/Target round button.png new file mode 100644 index 000000000..e61579880 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/Target round button.png differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/Tent.glb b/examples/starting-expanded-first-person-survival-crafting/assets/Tent.glb new file mode 100644 index 000000000..622237958 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/Tent.glb differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/TerrainDirt.jpg b/examples/starting-expanded-first-person-survival-crafting/assets/TerrainDirt.jpg new file mode 100644 index 000000000..1cd44111a Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/TerrainDirt.jpg differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/TerrainDirtNormal.jpg b/examples/starting-expanded-first-person-survival-crafting/assets/TerrainDirtNormal.jpg new file mode 100644 index 000000000..c70e71e2c Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/TerrainDirtNormal.jpg differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/TerrainGrass.jpg b/examples/starting-expanded-first-person-survival-crafting/assets/TerrainGrass.jpg new file mode 100644 index 000000000..eff7e872f Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/TerrainGrass.jpg differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/TerrainGrassNormal.jpg b/examples/starting-expanded-first-person-survival-crafting/assets/TerrainGrassNormal.jpg new file mode 100644 index 000000000..f4b1de953 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/TerrainGrassNormal.jpg differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/TerrainMoss.jpg b/examples/starting-expanded-first-person-survival-crafting/assets/TerrainMoss.jpg new file mode 100644 index 000000000..2843ac3a8 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/TerrainMoss.jpg differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/TerrainMossNormal.jpg b/examples/starting-expanded-first-person-survival-crafting/assets/TerrainMossNormal.jpg new file mode 100644 index 000000000..bb3ee4de7 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/TerrainMossNormal.jpg differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/TerrainRock.jpg b/examples/starting-expanded-first-person-survival-crafting/assets/TerrainRock.jpg new file mode 100644 index 000000000..d838e88fb Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/TerrainRock.jpg differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/TerrainRockNormal.jpg b/examples/starting-expanded-first-person-survival-crafting/assets/TerrainRockNormal.jpg new file mode 100644 index 000000000..7c8288e66 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/TerrainRockNormal.jpg differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/Top arrow button.png b/examples/starting-expanded-first-person-survival-crafting/assets/Top arrow button.png new file mode 100644 index 000000000..e69188046 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/Top arrow button.png differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/Tree Stump.glb b/examples/starting-expanded-first-person-survival-crafting/assets/Tree Stump.glb new file mode 100644 index 000000000..60af7c38b Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/Tree Stump.glb differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/Wood Log Moss.glb b/examples/starting-expanded-first-person-survival-crafting/assets/Wood Log Moss.glb new file mode 100644 index 000000000..36ae1c6d4 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/Wood Log Moss.glb differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/big_rock.glb b/examples/starting-expanded-first-person-survival-crafting/assets/big_rock.glb new file mode 100644 index 000000000..f6af913ce Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/big_rock.glb differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/crosshair037.png b/examples/starting-expanded-first-person-survival-crafting/assets/crosshair037.png new file mode 100644 index 000000000..73c9f032d Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/crosshair037.png differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/log.glb b/examples/starting-expanded-first-person-survival-crafting/assets/log.glb new file mode 100644 index 000000000..c1fc3cd0c Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/log.glb differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/log.png b/examples/starting-expanded-first-person-survival-crafting/assets/log.png new file mode 100644 index 000000000..31e60e471 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/log.png differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/small_rock.png b/examples/starting-expanded-first-person-survival-crafting/assets/small_rock.png new file mode 100644 index 000000000..a29da2c96 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/small_rock.png differ diff --git a/examples/starting-expanded-first-person-survival-crafting/assets/starting_tree.glb b/examples/starting-expanded-first-person-survival-crafting/assets/starting_tree.glb new file mode 100644 index 000000000..919be2fe3 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/assets/starting_tree.glb differ diff --git a/examples/starting-expanded-first-person-survival-crafting/preview.png b/examples/starting-expanded-first-person-survival-crafting/preview.png new file mode 100644 index 000000000..3460ea423 Binary files /dev/null and b/examples/starting-expanded-first-person-survival-crafting/preview.png differ diff --git a/examples/starting-expanded-first-person-survival-crafting/starting-expanded-first-person-survival-crafting.json b/examples/starting-expanded-first-person-survival-crafting/starting-expanded-first-person-survival-crafting.json new file mode 100644 index 000000000..06c57aa21 --- /dev/null +++ b/examples/starting-expanded-first-person-survival-crafting/starting-expanded-first-person-survival-crafting.json @@ -0,0 +1,44944 @@ +{ + "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": "a59f1606-418e-5dc9-bf9a-dbaa54d04e40", + "scaleMode": "linear", + "sizeOnStartupMode": "", + "templateSlug": "starting-first-person", + "version": "1.0.0", + "name": "3D First Person Survival Crafting Expanded", + "description": "A first-person survival crafting game in an open forest on hills made with a 3D terrain, with trees and boulders to gather.", + "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": [], + "extensionProperties": [], + "platforms": [ + { + "name": "GDevelop JS platform" + } + ], + "currentPlatform": "GDevelop JS platform" + }, + "resources": { + "resources": [ + { + "file": "assets/StartingWall.png", + "kind": "image", + "metadata": "", + "name": "assets\\StartingWall.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": "StarterToggle_Keyboard.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/StarterToggle_Touch.png", + "kind": "image", + "metadata": "", + "name": "StarterToggle_Touch.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/NotoSansArabicUI-Black.ttf", + "kind": "font", + "metadata": "", + "name": "assets\\NotoSansArabicUI-Black.ttf", + "userAdded": false + }, + { + "file": "assets/crosshair037.png", + "kind": "image", + "metadata": "", + "name": "assets\\crosshair037.png", + "smoothed": false, + "userAdded": false + }, + { + "file": "assets/StartingInventorySlot.png", + "kind": "image", + "metadata": "", + "name": "assets\\StartingInventorySlot.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/SelectionBox.png", + "kind": "image", + "metadata": "", + "name": "assets\\SelectionBox.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/small_rock.png", + "kind": "image", + "metadata": "", + "name": "assets\\small_rock.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/log.png", + "kind": "image", + "metadata": "", + "name": "assets\\log.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/log.glb", + "kind": "model3D", + "metadata": "", + "name": "assets\\log.glb", + "userAdded": false + }, + { + "file": "assets/Small_rock_1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets\\Small_rock_1.glb", + "userAdded": false + }, + { + "file": "assets/Target round button.png", + "kind": "image", + "metadata": "", + "name": "assets\\Target round button.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/big_rock.glb", + "kind": "model3D", + "metadata": "", + "name": "assets\\big_rock.glb", + "userAdded": false + }, + { + "file": "assets/starting_tree.glb", + "kind": "model3D", + "metadata": "", + "name": "assets\\starting_tree.glb", + "userAdded": false + }, + { + "file": "assets/Placement thud.wav", + "kind": "audio", + "metadata": "", + "name": "assets\\Placement thud.wav", + "preloadAsMusic": false, + "preloadAsSound": true, + "preloadInCache": false, + "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/TerrainMoss.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainMoss.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/TerrainMossNormal.jpg", + "kind": "image", + "metadata": "", + "name": "assets/TerrainMossNormal.jpg", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/Wood Log Moss.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Wood Log Moss.glb", + "userAdded": true + }, + { + "file": "assets/Tree Stump.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Tree Stump.glb", + "userAdded": true + }, + { + "file": "assets/Bonfire.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Bonfire.glb", + "userAdded": true + }, + { + "file": "assets/Tent.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Tent.glb", + "userAdded": true + }, + { + "file": "assets/Bush 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Bush 1.glb", + "userAdded": true + }, + { + "file": "assets/Bush Berries 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Bush Berries 1.glb", + "userAdded": true + }, + { + "file": "assets/Plant 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Plant 1.glb", + "userAdded": true + }, + { + "file": "assets/Flowers.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Flowers.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.3325387091250948, + "windowMask": false, + "selectedLayer": "", + "gameEditorMode": "embedded-game" + }, + "objectsGroups": [ + { + "name": "RaycastSurface", + "objects": [ + { + "name": "Obstacle" + }, + { + "name": "Terrain" + } + ] + }, + { + "name": "Item", + "objects": [ + { + "name": "Log" + }, + { + "name": "Rock" + } + ] + }, + { + "name": "Harvestable", + "objects": [ + { + "name": "Harvest_Rock" + }, + { + "name": "Harvest_Tree" + } + ] + } + ], + "variables": [ + { + "name": "ControlKind", + "persistentUuid": "fcafbc0c-5c3f-40d2-ab75-f3355a38b4f1", + "type": "string", + "value": "Keyboard" + }, + { + "name": "Inventory", + "persistentUuid": "ac8e82c8-c591-4644-92e8-8908cd473ecd", + "type": "array", + "children": [] + }, + { + "name": "UpdateInventoryDisplay", + "persistentUuid": "d1ec9e52-04a9-4ce8-a08f-36d717eb884c", + "type": "boolean", + "value": false + }, + { + "name": "SelectedSlot", + "persistentUuid": "11431277-f3b0-4e4d-a476-2fc08088c4b5", + "type": "number", + "value": 0 + } + ], + "instances": [ + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "MobileControls", + "locked": true, + "name": "MovementJoystick", + "persistentUuid": "757f9f40-246a-49c8-a4d3-fe0022b826a7", + "width": 0, + "x": 170, + "y": 576, + "zOrder": 7, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 0, + "height": 96, + "keepRatio": true, + "layer": "MobileControls", + "locked": true, + "name": "JumpButton", + "persistentUuid": "c641f19e-29c5-4424-be1c-656b5505f1ac", + "width": 96, + "x": 1056, + "y": 608, + "zOrder": 8, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "MobileControls", + "locked": true, + "name": "ControlsToggle", + "persistentUuid": "8f7d9c3d-1567-4a65-867e-d042f38c0e86", + "width": 0, + "x": 32, + "y": 32, + "zOrder": 15, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "UI", + "name": "HighlightText", + "persistentUuid": "5cb0b21d-849d-448a-bfe9-2bb57d78dab5", + "width": 0, + "x": 649, + "y": 364, + "zOrder": 17, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "UI", + "locked": true, + "name": "Crosshair", + "persistentUuid": "3353cc39-b144-451c-b25a-302b6afdd01e", + "width": 0, + "x": 640, + "y": 360, + "zOrder": 18, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 1, + "height": 96, + "keepRatio": true, + "layer": "UI", + "locked": true, + "name": "InventorySlot", + "persistentUuid": "01bfa5a8-ab52-42a0-a59a-0c82f040ed99", + "width": 96, + "x": 848, + "y": 544, + "zOrder": 19, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [ + { + "name": "SlotNumber", + "type": "number", + "value": 4 + } + ] + }, + { + "angle": 0, + "customSize": true, + "depth": 1, + "height": 96, + "keepRatio": true, + "layer": "UI", + "locked": true, + "name": "InventorySlot", + "persistentUuid": "ab712ffb-6632-43e9-822c-cd1a28e8e92b", + "width": 96, + "x": 336, + "y": 544, + "zOrder": 19, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [ + { + "name": "SlotNumber", + "type": "number", + "value": 0 + } + ] + }, + { + "angle": 0, + "customSize": true, + "depth": 1, + "height": 96, + "keepRatio": true, + "layer": "UI", + "locked": true, + "name": "InventorySlot", + "persistentUuid": "f808f482-a07a-4e8a-86b1-4b82b7a2d3f6", + "width": 96, + "x": 464, + "y": 544, + "zOrder": 19, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [ + { + "name": "SlotNumber", + "type": "number", + "value": 1 + } + ] + }, + { + "angle": 0, + "customSize": true, + "depth": 1, + "height": 96, + "keepRatio": true, + "layer": "UI", + "locked": true, + "name": "InventorySlot", + "persistentUuid": "92bd3654-5ead-4db3-aa9a-2261f8c460a1", + "width": 96, + "x": 592, + "y": 544, + "zOrder": 19, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [ + { + "name": "SlotNumber", + "type": "number", + "value": 2 + } + ] + }, + { + "angle": 0, + "customSize": true, + "depth": 1, + "height": 96, + "keepRatio": true, + "layer": "UI", + "locked": true, + "name": "InventorySlot", + "persistentUuid": "4fd04d63-e77f-4ddf-8dd4-e2e7e1667965", + "width": 96, + "x": 720, + "y": 544, + "zOrder": 19, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [ + { + "name": "SlotNumber", + "type": "number", + "value": 3 + } + ] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "keepRatio": true, + "layer": "UI", + "name": "SelectionBox", + "persistentUuid": "6ce82a4d-d58a-41d2-bd63-4ff216180ee9", + "width": 0, + "x": 384, + "y": 591, + "zOrder": 20, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "depth": 0, + "height": 96, + "keepRatio": true, + "layer": "MobileControls", + "locked": true, + "name": "ActionButton", + "persistentUuid": "1b24dfbc-bdc8-4da2-9be8-74dba6d6a564", + "width": 96, + "x": 1160, + "y": 536, + "zOrder": 23, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 6144, + "layer": "", + "locked": false, + "name": "Terrain", + "persistentUuid": "d292de7f-e1f2-566e-acaa-c27160eff873", + "x": -3072, + "y": -3072, + "z": -60, + "zOrder": 0, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 1200 + }, + { + "angle": 0, + "customSize": true, + "height": 200, + "width": 6544, + "layer": "", + "locked": false, + "name": "MapLimit", + "persistentUuid": "12e926cc-520f-58a7-9930-1b175d0baa9a", + "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": "da945f42-4de7-5fd7-97ed-32daa56133c7", + "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": "230f10b6-826d-54cc-b222-0708ecac686a", + "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": "b4d13850-bd64-5544-ba10-4477a1db35a6", + "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": "db41c2aa-bb7f-597f-acad-46a27cbccb38", + "x": -3072, + "y": -3072, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 40 + }, + { + "angle": -90, + "customSize": true, + "height": 50, + "width": 50, + "layer": "", + "locked": false, + "name": "Player", + "persistentUuid": "6f0271ff-410e-5a49-9528-2c535ad7628c", + "x": -25, + "y": 500, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 80 + }, + { + "angle": 0, + "customSize": true, + "height": 128, + "width": 128, + "layer": "", + "locked": false, + "name": "Obstacle", + "persistentUuid": "aa961155-c17f-5ce7-8161-9f46bf2a6b1a", + "x": -64, + "y": 130, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 32 + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Log", + "persistentUuid": "a3557ba6-76ed-5b85-8020-9f33d3ade3a5", + "x": 37, + "y": 175, + "z": 43, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Log", + "persistentUuid": "48e0fbd2-d79f-5fb0-943d-d895aee9f5c8", + "x": 22, + "y": 225, + "z": 43, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Rock", + "persistentUuid": "8b66327b-5024-5bbc-81cc-12872609ff97", + "x": -15, + "y": 174, + "z": 39, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Rock", + "persistentUuid": "5208549c-e357-5dad-9073-3938efc977b1", + "x": -44, + "y": 224, + "z": 37, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "75542bf3-2e6f-5740-9a16-ba78ecfa385c", + "x": -140, + "y": -80, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 114, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "3f410e94-8874-5704-8d64-8c15128c5a8f", + "x": 230, + "y": 30, + "z": -10, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Campfire", + "persistentUuid": "f4711c9e-58f9-5552-a00f-da9ae11ef21f", + "x": 250, + "y": 380, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 60, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Tent", + "persistentUuid": "cfe91753-1ace-55f6-85ce-4a545d2cd5ab", + "x": -330, + "y": 230, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 195, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "dc938079-a509-57b8-b41c-5dfa4b2cfd42", + "x": 1825.3, + "y": 46, + "z": 50.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 299, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "ac602d27-8a1f-5f16-881d-07b5f6c4a589", + "x": -295.3, + "y": -2300.5, + "z": 8.2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 257, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "3dd8559d-3793-5ac3-92eb-aaeb00e7bc00", + "x": -487.1, + "y": -1494.2, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 195, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "4e9eb94d-172d-5180-b2bb-535dd8d4c1e2", + "x": 1609.9, + "y": -988.5, + "z": 58.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 172, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "53dc9d9e-0b04-5f74-897f-7dc5d35b19d3", + "x": -1474.9, + "y": -620.6, + "z": 46, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 195, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "714161ed-5e87-52f3-814f-7af2c28a2f50", + "x": -1668.9, + "y": -1603.6, + "z": 186.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 84, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "7ccedbe1-9941-5db4-953d-50a72c606e73", + "x": -1225.6, + "y": 1961.8, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 173, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "8ffe9648-2f33-589a-92ba-50083354cb8c", + "x": -2403.3, + "y": -1381.7, + "z": 19.3, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 142, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "6105993c-d147-5492-8d35-69c5e6280fd3", + "x": -1646.3, + "y": 326.5, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 299, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "88c82fb7-07bd-5688-97e1-65f5eceb09f7", + "x": 1956.6, + "y": 1409.1, + "z": 74.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 327, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "f9b94a5a-d7b2-5422-8ee6-d165fec24522", + "x": -2106.7, + "y": -1557.9, + "z": 53.8, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 102, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "15ac99b3-e122-5512-902e-bbe92dd19701", + "x": 2142.3, + "y": 507.5, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 103, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "72e20bf2-dc72-5b19-aeea-ec3703b1e19b", + "x": 523.3, + "y": -1079.6, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 99, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "03f3c72a-11aa-5f70-9d43-b5335d87e09c", + "x": -1242.5, + "y": 2196.6, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 262, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "cf75b5ba-a143-5400-9f13-b7efa480557b", + "x": -1524.9, + "y": 2410.1, + "z": 22.9, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 103, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "37075a6d-4b1f-5920-8e2a-1211be4228c0", + "x": -1844.7, + "y": -597.5, + "z": 13.7, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 318, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "288584ed-6f6d-5f1f-abad-f0ffb3b28d56", + "x": 1694.5, + "y": 1005.3, + "z": 144.8, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 186, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "46f3851a-385c-53a5-96ca-4f9a92192482", + "x": -2362.9, + "y": 2249.7, + "z": 4.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 225, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "6b475404-5224-5089-ab16-a5d4356258d0", + "x": -1270.1, + "y": 395.8, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 308, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "f99e11be-e13b-5dc9-95ea-6a91961ef908", + "x": 255.8, + "y": -1655.1, + "z": 6.8, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 173, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "219f17e0-7947-5091-bd82-0976dc1d24e1", + "x": -5.1, + "y": 1963.9, + "z": 54, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 98, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "43844a5b-057d-5d1f-a72c-393c80951207", + "x": 1185.1, + "y": -1726.5, + "z": 155.7, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 298, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "29f4f6c4-d5c3-5d80-acb3-9b97082651b6", + "x": -2115.7, + "y": 191.8, + "z": 30.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 135, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "0bb7a1fd-1950-5bc5-81b7-909e504b9271", + "x": -1407.9, + "y": -99.8, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 253, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "b7adb3b5-6351-5da5-844c-9f5b6f83d120", + "x": -1498.5, + "y": -1882.1, + "z": 86, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 230, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "78eebe3d-9f70-59a8-a9d6-b55c462f7baa", + "x": 2382.6, + "y": -857.8, + "z": 10.9, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 31, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "1a901266-ea15-5c42-a77c-4d86f6361e7c", + "x": 801.4, + "y": 1964.2, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 329, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "4aafe869-f26b-53cb-8f8c-a84e2750b723", + "x": -2040, + "y": 1790.1, + "z": 8.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 74, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "e339b84c-4f7a-58d7-920e-51f76b529465", + "x": 2356.6, + "y": -1822.3, + "z": 2.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 341, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "65626ee8-76fc-5fed-8aac-3ab95899089b", + "x": 776.1, + "y": -1251.8, + "z": 17.7, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 307, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "7dca0485-5d77-5590-af36-192357d2e929", + "x": -833.1, + "y": 517.5, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 226, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "4e76af63-4f1b-5ebf-8738-1b92d97af649", + "x": 410.5, + "y": 1322.6, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 236, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "cbbf5025-9b34-50da-8477-ea02fc5c299b", + "x": -2200.6, + "y": 449.1, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 34, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "d069e90f-eca3-55c6-9386-7615aa4175b1", + "x": -1203.4, + "y": -1622.8, + "z": 149.2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "0065dbee-baf0-5e71-8cd6-ab3a7dc01367", + "x": -1785, + "y": 53.4, + "z": -1.2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 237, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "a45f1339-b02a-531b-ada2-c421e1da31bf", + "x": 1874.2, + "y": 1826.6, + "z": -1.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 256, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "b3aee893-6703-5129-8f16-44a9ea8f4c46", + "x": -2398.7, + "y": -460.4, + "z": 20.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 150, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "266dde61-bb88-5cf2-8915-ac3c5f30058a", + "x": 420.5, + "y": -641.4, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 323, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "3c741b53-5862-5cb5-80c7-a194dc9ed2c8", + "x": 501.1, + "y": 1025.3, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 59, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "3d24a539-77da-56db-b06d-26cb937fdfdf", + "x": -1589.7, + "y": 2034.8, + "z": -1.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 207, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "37f424e9-0186-5360-b3c1-e74cff7fc42b", + "x": -1898.2, + "y": 1510, + "z": 114.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 328, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "694d08f7-a15a-5906-a396-87edb7f597e0", + "x": -424.2, + "y": 1583.3, + "z": 4.5, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 331, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "0f38f0dc-5af4-5204-a8e7-c6820ace9358", + "x": 32.1, + "y": -335.5, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 52, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "ee157fbd-6c3a-542b-b12e-210fa622bc90", + "x": -687.2, + "y": -1025.4, + "z": 1.2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 327, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "efabfc2b-d489-5ca3-9c83-c8308346ecbc", + "x": -443.2, + "y": -600.9, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 190, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "c3afcf94-acba-56dd-be62-a35f8d868ac1", + "x": -57, + "y": -2427.4, + "z": 40.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 178, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "fc9b5309-35ff-5eed-8d4b-6b424ebaaefe", + "x": 2218.8, + "y": -2283.7, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 103, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "5cbd3bc4-96a7-596b-b9f9-e72028a6c380", + "x": 1981, + "y": 704.9, + "z": 5.8, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 35, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "8a833ae2-5db7-5a14-9d9c-4de3021298a0", + "x": -1772, + "y": -1275.6, + "z": 212.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 130, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "170c78bd-c68f-5a74-8a45-77f8dd1c729c", + "x": -1001.3, + "y": 2094.4, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 154, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "72349cfd-c2c8-58e2-aad1-3d8ad28e460a", + "x": -2403.6, + "y": -2268.3, + "z": 19.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 158, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "5f11177a-0956-51db-9069-8beb7129be23", + "x": 1661.9, + "y": 1636.9, + "z": 63.2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 209, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "509c6762-11f8-59ce-9765-8cc7e9a61eb5", + "x": 689.3, + "y": -1849, + "z": -1.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 24, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "d930f7d7-a0e1-5553-b652-a16f7f61bf4a", + "x": 2329.6, + "y": 1801.2, + "z": -1.7, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 222, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "ab7c807e-c8b3-5bdb-b622-2010d66eefdf", + "x": 2372.9, + "y": 769, + "z": 6.9, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 158, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "f24211b6-4f06-52b3-abfd-87303808e89e", + "x": -401.4, + "y": 2111.3, + "z": 28.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 270, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "bd56b6f0-ad8f-528b-8a9c-f104b345fe01", + "x": 181.5, + "y": -1123.4, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 115, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "c3339d62-749d-5a71-ba18-41fc14a30ee7", + "x": 732.1, + "y": 400.3, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 150, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "501057c3-7212-56a5-a265-127bfbd2680c", + "x": 2179.5, + "y": -1355.2, + "z": -1.8, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 143, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "bcfe5af0-9fc6-5d29-aaa2-215d1d67fb4f", + "x": 1458.1, + "y": -1695.1, + "z": 190.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 167, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "d707b456-7aa8-53e3-84f3-beae34a54f2a", + "x": -665.9, + "y": 360.9, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 212, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "0413c337-4f72-5e57-ba6a-2664306df513", + "x": 1003.6, + "y": 488.7, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 48, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "f9a6b6f8-736f-521f-836c-a3ca9cf4cbcb", + "x": -931, + "y": 2422.9, + "z": 29.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 338, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "790fd719-f803-5963-9286-ea8a3df8d078", + "x": 802.2, + "y": -220.8, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 73, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "275edec8-b952-502e-a368-fec63ed4a964", + "x": -505.4, + "y": -1798.2, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 140, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "03ce58bf-a600-5a32-9b10-8e65d0c405cc", + "x": -2269.8, + "y": 1276.3, + "z": 6.2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 161, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "3765d660-0704-52b0-8909-44fb1fb28c06", + "x": -1846.1, + "y": 900.6, + "z": 72, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 241, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "a62d1d04-b815-5a4f-803b-7d8fdaa1c9c1", + "x": 1585.3, + "y": 2002, + "z": -2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 73, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "a48db998-51c9-5867-8113-4753036968b7", + "x": -2418.3, + "y": 100.9, + "z": 62.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 149, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Tree", + "persistentUuid": "8e23f25a-eca8-5667-9b90-f69067399961", + "x": -2131.1, + "y": -326.7, + "z": 55.2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 116, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "4138dbd5-0526-59b8-921e-16a277df1749", + "x": 1511.1, + "y": -2251.6, + "z": -12, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 314, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "04dfeaf9-ddfa-507b-9809-9d52fa77ccb3", + "x": -117.4, + "y": 766.8, + "z": -12, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 56, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "2bf2ade2-9b94-58b2-8478-f74313c08f44", + "x": 1996.5, + "y": -748.1, + "z": -12, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 146, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "fa92dca9-e8b0-58a0-acfc-fd25170134e3", + "x": 983.4, + "y": -1645.2, + "z": 56.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 326, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "4f3c83b5-8518-5744-85c9-908ae02e5a6f", + "x": 607.3, + "y": -1406, + "z": -12, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 162, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "3d0e1e18-ff48-5e30-a089-107be174b107", + "x": 1581.6, + "y": 50.3, + "z": -12, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 287, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "1834966d-fe50-586e-8783-972ee2b1fad6", + "x": 930.7, + "y": 1600.7, + "z": -12, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 78, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "b095f256-c091-5b58-9000-7bfa0b364175", + "x": -840.4, + "y": 1670, + "z": -12, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 106, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "bce9ec85-b916-554b-bb19-4c71507fa1d8", + "x": -263.5, + "y": 1004.3, + "z": -12, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 350, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "15264aae-85c3-5c08-aefa-23d5072abf93", + "x": 1204.9, + "y": -2261.8, + "z": -11.9, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 300, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "97331a48-8a78-5a2a-ae21-4dcbf3963b1c", + "x": -137.1, + "y": -1716.2, + "z": 6.9, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 101, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "d1307d91-33a5-5174-b084-54b5e086f3f3", + "x": -2445.7, + "y": 495.4, + "z": -4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 100, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "ef848a36-85b2-5575-bd68-f6bf52f2d318", + "x": 67.5, + "y": 2230, + "z": -12, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 96, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "4d6024e8-6544-5a9e-b25d-7afccd0dd2f2", + "x": 1747.5, + "y": -2370.5, + "z": -12, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 302, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "1d4f6de6-6772-5d85-bac0-4c635772fdfd", + "x": -803.9, + "y": 1204.5, + "z": -12, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 95, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "835ceb3f-b10f-56e5-9947-7265cff33305", + "x": 1435, + "y": 766.3, + "z": 14, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 124, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "94030727-c6fb-5bd4-8093-0eb1a175c676", + "x": -727.8, + "y": -1524, + "z": -11.4, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 111, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "e678349e-8197-58b1-ba01-4488d321b015", + "x": 1202.9, + "y": 732.3, + "z": -11.5, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 138, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "0f8bbd40-b3b9-50eb-af6c-217c6f0f8d5b", + "x": 2084.8, + "y": -1848, + "z": -12, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 155, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "2e0db891-9702-5f39-ba55-50b2a5b75c59", + "x": 1140.9, + "y": 2036.6, + "z": -12, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 91, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "58ed1f2e-fd3a-576a-a96e-efebf58c3949", + "x": 2412.3, + "y": 1368, + "z": -9.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 357, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Harvest_Rock", + "persistentUuid": "65a5f3dd-dde8-5969-92d8-9d8e2a662cbc", + "x": -242.1, + "y": -993.4, + "z": -12, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 6144, + "layer": "", + "locked": true, + "name": "Bushes", + "persistentUuid": "996f20c1-93f3-5dfb-b2a8-647203de24a1", + "x": -3072, + "y": -3072, + "z": -60, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 6144, + "layer": "", + "locked": true, + "name": "BerryBushes", + "persistentUuid": "3831a3d6-63c7-546e-8c68-1ebd20c1dfe4", + "x": -3072, + "y": -3072, + "z": -60, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 6144, + "layer": "", + "locked": true, + "name": "Plants", + "persistentUuid": "bfef60e1-3e83-57a5-a5fb-04b5508a623a", + "x": -3072, + "y": -3072, + "z": -60, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 6144, + "width": 6144, + "layer": "", + "locked": true, + "name": "Flowers", + "persistentUuid": "8879d030-b082-5b5d-a596-83d6e4dc7f1f", + "x": -3072, + "y": -3072, + "z": -60, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 36, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Stump", + "persistentUuid": "7e13481f-52c6-5c35-a97e-809fd15cd641", + "x": -1237.8, + "y": -2034.9, + "z": 2.5, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 293, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "FallenLog", + "persistentUuid": "4bc5a086-1c86-5a93-8060-ce7ed3fe11ce", + "x": 1793.3, + "y": -569.6, + "z": -3, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 3, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Stump", + "persistentUuid": "7c78f7d2-da25-52b5-beeb-f848737e62b4", + "x": 1690.5, + "y": -181.2, + "z": 1.3, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 198, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "FallenLog", + "persistentUuid": "35798a88-b5e6-5888-806f-2cc4c7885ec1", + "x": -2211.8, + "y": -1043.2, + "z": 1.1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 216, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Stump", + "persistentUuid": "7b4c7139-7d76-52d2-a43b-15c9e128b409", + "x": 1926.1, + "y": -2088.5, + "z": -3, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 360, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Stump", + "persistentUuid": "5b81fbe2-2c33-5865-a210-02b289639393", + "x": -1339.9, + "y": -2374.9, + "z": -2.6, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 198, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "FallenLog", + "persistentUuid": "b7a3c515-7b11-566f-801e-78f6a08e9cd1", + "x": -1901, + "y": 599.6, + "z": -3, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 350, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Stump", + "persistentUuid": "a8a2e78b-d3ae-5bb2-9c19-64924e9551c5", + "x": -2166.1, + "y": -1941.1, + "z": -3, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 224, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "FallenLog", + "persistentUuid": "5aa4a820-07ef-5f83-9ed0-f480fc120f61", + "x": -1650.6, + "y": 1249.7, + "z": 179, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 221, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Stump", + "persistentUuid": "13aabc97-2689-5726-b1a6-7bb6c71988c5", + "x": 1321.1, + "y": 385.9, + "z": -3, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "objects": [ + { + "assetStoreId": "", + "name": "Obstacle", + "persistentUuid": "b5cbd456-bec7-41f0-99fe-2120c8aefa9f", + "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": "e71bd69f896d6c7531b48c65ceb5da25071d4fbdeb518aeceecba8d21f34ed8d", + "name": "MovementJoystick", + "persistentUuid": "88063f68-789e-4045-958c-f6ec9a803a1a", + "type": "SpriteMultitouchJoystick::SpriteMultitouchJoystick", + "variant": "", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "DeadZoneRadius": 0.1 + }, + "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": false, + "assetStoreId": "9c727020616afdd6ba786b8af206a90481f07db0ca175ed6a4cc5b7e01c66d06", + "name": "JumpButton", + "persistentUuid": "bfbf22a6-18ac-4a9f-b8e9-cac00993ee62", + "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.2, + "FocusedScale": 1.25, + "FadeInDuration": 0.02, + "FadeOutDuration": 0.05, + "PressedScale": 1.15, + "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": 40 + }, + { + "x": 10, + "y": 10 + }, + { + "x": 40, + "y": 0 + }, + { + "x": 70, + "y": 10 + }, + { + "x": 80, + "y": 40 + }, + { + "x": 70, + "y": 70 + }, + { + "x": 40, + "y": 80 + }, + { + "x": 10, + "y": 70 + } + ] + ] + } + ] + } + ] + } + ] + }, + { + "assetStoreId": "", + "name": "Player", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "isFolded": true, + "name": "FirstPersonMultitouchMapper", + "type": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper", + "Object3D": "Object3D", + "ControllerIdentifier": 2, + "CameraStick": "Secondary", + "HorizontalRotationSpeedMax": 110, + "HorizontalRotationAcceleration": 120, + "HorizontalRotationDeceleration": 600, + "VerticalRotationSpeedMax": 80, + "VerticalRotationAcceleration": 120, + "VerticalRotationDeceleration": 480, + "VerticalAngleMin": -90, + "VerticalAngleMax": 90, + "OffsetZ": 0, + "CurrentRotationSpeedZ": 0, + "CurrentRotationSpeedY": 0 + }, + { + "isFolded": true, + "name": "FirstPersonPointerMapper", + "type": "MousePointerLock::FirstPersonPointerMapper", + "Object3D": "Object3D", + "HorizontalSpeed": 100, + "VerticalSpeed": 80, + "VerticalAngleMin": -90, + "VerticalAngleMax": 90, + "OffsetZ": 0, + "ShouldLock": false + }, + { + "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.3, + "gravity": 800, + "fallingSpeedMax": 450, + "forwardAcceleration": 650, + "forwardDeceleration": 1600, + "forwardSpeedMax": 300, + "sidewaysAcceleration": 650, + "sidewaysDeceleration": 1600, + "sidewaysSpeedMax": 400, + "slopeMaxAngle": 50, + "stairHeightMax": 20, + "shouldBindObjectAndForwardAngle": true, + "canBePushed": false + }, + { + "isFolded": true, + "name": "Shooter3DKeyboardMapper", + "type": "PhysicsCharacter3DKeyMapper::Shooter3DKeyboardMapper", + "PhysicsCharacter3D": "PhysicsCharacter3D", + "Jump": "Space", + "Up": "w", + "Left": "a", + "Down": "s", + "Right": "d" + }, + { + "isFolded": true, + "name": "Shooter3DMultitouchMapper", + "type": "SpriteMultitouchJoystick::Shooter3DMultitouchMapper", + "PhysicsCharacter3D": "PhysicsCharacter3D", + "ControllerIdentifier": 1, + "JoystickIdentifier": "Primary", + "JumpButton": "A" + } + ], + "content": { + "width": 100, + "height": 100, + "depth": 100, + "enableTextureTransparency": false, + "facesOrientation": "Z", + "frontFaceResourceName": "", + "backFaceResourceName": "", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "", + "rightFaceResourceName": "", + "topFaceResourceName": "", + "bottomFaceResourceName": "", + "frontFaceVisible": true, + "backFaceVisible": false, + "leftFaceVisible": true, + "rightFaceVisible": true, + "topFaceVisible": true, + "bottomFaceVisible": true, + "frontFaceResourceRepeat": false, + "backFaceResourceRepeat": false, + "leftFaceResourceRepeat": false, + "rightFaceResourceRepeat": false, + "topFaceResourceRepeat": false, + "bottomFaceResourceRepeat": false, + "materialType": "StandardWithoutMetalness", + "isReceivingShadow": true, + "isCastingShadow": true + } + }, + { + "assetStoreId": "", + "name": "ControlsToggle", + "type": "SpriteToggleSwitch::SpriteToggleSwitch", + "variant": "Controls toggle", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "IsChecked": false + } + }, + { + "assetStoreId": "", + "bold": false, + "italic": false, + "name": "HighlightText", + "smoothed": true, + "type": "TextObject::Text", + "underlined": false, + "variables": [ + { + "name": "TextToUse", + "type": "string", + "value": "" + } + ], + "effects": [], + "behaviors": [], + "string": "Highlight Text", + "font": "assets\\NotoSansArabicUI-Black.ttf", + "textAlignment": "left", + "characterSize": 16, + "color": { + "b": 255, + "g": 255, + "r": 255 + }, + "content": { + "bold": false, + "isOutlineEnabled": true, + "isShadowEnabled": false, + "italic": false, + "outlineColor": "0;0;0", + "outlineThickness": 4, + "shadowAngle": 90, + "shadowBlurRadius": 2, + "shadowColor": "0;0;0", + "shadowDistance": 4, + "shadowOpacity": 127, + "smoothed": true, + "underlined": false, + "text": "Highlight Text", + "font": "assets\\NotoSansArabicUI-Black.ttf", + "textAlignment": "left", + "verticalTextAlignment": "top", + "characterSize": 16, + "lineHeight": 0, + "color": "255;255;255" + } + }, + { + "adaptCollisionMaskAutomatically": true, + "assetStoreId": "", + "name": "Crosshair", + "type": "Sprite", + "updateIfNotVisible": false, + "variables": [], + "effects": [], + "behaviors": [], + "animations": [ + { + "name": "", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": true, + "image": "assets\\crosshair037.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 32, + "y": 32 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [ + [ + { + "x": 20, + "y": 20 + }, + { + "x": 44, + "y": 20 + }, + { + "x": 44, + "y": 44 + }, + { + "x": 20, + "y": 44 + } + ] + ] + } + ] + } + ] + } + ] + }, + { + "assetStoreId": "", + "bottomMargin": 32, + "height": 160, + "leftMargin": 32, + "name": "InventorySlot", + "persistentUuid": "b35141e6-c155-4e0a-ba01-1b132adfd591", + "rightMargin": 32, + "texture": "assets\\StartingInventorySlot.png", + "tiled": false, + "topMargin": 32, + "type": "PanelSpriteObject::PanelSprite", + "width": 160, + "variables": [ + { + "name": "SlotNumber", + "type": "number", + "value": 0 + }, + { + "name": "Selected", + "type": "boolean", + "value": false + } + ], + "effects": [], + "behaviors": [ + { + "name": "ButtonFSM", + "type": "ButtonStates::ButtonFSM", + "ShouldCheckHovering": true, + "State": "Idle", + "TouchId": 0, + "TouchIsInside": false, + "MouseIsInside": false, + "Index": 2.0247e-320 + } + ] + }, + { + "adaptCollisionMaskAutomatically": true, + "assetStoreId": "", + "name": "InventoryIcons", + "type": "Sprite", + "updateIfNotVisible": false, + "variables": [], + "effects": [], + "behaviors": [], + "animations": [ + { + "name": "Rock", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": true, + "image": "assets\\small_rock.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 48, + "y": 48 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [ + [ + { + "x": 6, + "y": 16 + }, + { + "x": 91, + "y": 16 + }, + { + "x": 91, + "y": 81 + }, + { + "x": 6, + "y": 81 + } + ] + ] + } + ] + } + ] + }, + { + "name": "Log", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": true, + "image": "assets\\log.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 48, + "y": 48 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [ + [ + { + "x": 6, + "y": 16 + }, + { + "x": 91, + "y": 16 + }, + { + "x": 91, + "y": 81 + }, + { + "x": 6, + "y": 81 + } + ] + ] + } + ] + } + ] + } + ] + }, + { + "assetStoreId": "", + "bold": false, + "italic": false, + "name": "SlotText", + "smoothed": true, + "type": "TextObject::Text", + "underlined": false, + "variables": [ + { + "name": "TextToUse", + "type": "string", + "value": "" + } + ], + "effects": [], + "behaviors": [], + "string": "Highlight Text", + "font": "assets\\NotoSansArabicUI-Black.ttf", + "textAlignment": "left", + "characterSize": 16, + "color": { + "b": 255, + "g": 255, + "r": 255 + }, + "content": { + "bold": false, + "isOutlineEnabled": true, + "isShadowEnabled": false, + "italic": false, + "outlineColor": "0;0;0", + "outlineThickness": 4, + "shadowAngle": 90, + "shadowBlurRadius": 2, + "shadowColor": "0;0;0", + "shadowDistance": 4, + "shadowOpacity": 127, + "smoothed": true, + "underlined": false, + "text": "Highlight Text", + "font": "assets\\NotoSansArabicUI-Black.ttf", + "textAlignment": "left", + "verticalTextAlignment": "top", + "characterSize": 16, + "lineHeight": 0, + "color": "255;255;255" + } + }, + { + "adaptCollisionMaskAutomatically": true, + "assetStoreId": "", + "name": "SelectionBox", + "persistentUuid": "b20ad62c-2487-435c-8139-7cd4b5912121", + "type": "Sprite", + "updateIfNotVisible": false, + "variables": [], + "effects": [], + "behaviors": [], + "animations": [ + { + "name": "", + "useMultipleDirections": false, + "directions": [ + { + "looping": false, + "timeBetweenFrames": 0.08, + "sprites": [ + { + "hasCustomCollisionMask": true, + "image": "assets\\SelectionBox.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 64, + "y": 64 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [ + [ + { + "x": 3, + "y": 3 + }, + { + "x": 125, + "y": 3 + }, + { + "x": 125, + "y": 125 + }, + { + "x": 3, + "y": 125 + } + ] + ] + } + ] + } + ] + } + ] + }, + { + "assetStoreId": "", + "name": "Log", + "type": "Scene3D::Model3DObject", + "variables": [ + { + "name": "Held", + "type": "boolean", + "value": false + }, + { + "name": "PickUp", + "type": "boolean", + "value": false + }, + { + "name": "JustPlaced", + "type": "boolean", + "value": false + } + ], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Dynamic", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "shapeOrientation": "Z", + "shapeDimensionA": 30, + "shapeDimensionB": 7, + "shapeDimensionC": 7, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 0.5, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "ObjectCenter", + "crossfadeDuration": 0.10000000149011612, + "depth": 32, + "height": 32, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets\\log.glb", + "originLocation": "BottomCenterZ", + "rotationX": 0, + "rotationY": 0, + "rotationZ": 0, + "width": 32, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "Rock", + "type": "Scene3D::Model3DObject", + "variables": [ + { + "name": "Held", + "type": "boolean", + "value": false + }, + { + "name": "PickUp", + "type": "boolean", + "value": false + }, + { + "name": "JustPlaced", + "type": "boolean", + "value": false + } + ], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Dynamic", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "shapeOrientation": "Z", + "shapeDimensionA": 26, + "shapeDimensionB": 28, + "shapeDimensionC": 14, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 0.5, + "friction": 0.3, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "ObjectCenter", + "crossfadeDuration": 0.10000000149011612, + "depth": 32, + "height": 32, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets\\Small_rock_1.glb", + "originLocation": "BottomCenterZ", + "rotationX": 0, + "rotationY": 0, + "rotationZ": 0, + "width": 32, + "animations": [] + } + }, + { + "adaptCollisionMaskAutomatically": true, + "assetStoreId": "523c449a51200d76f5572c755155325f5583aa5a0229949273647f2ac9db8c34", + "name": "ActionButton", + "persistentUuid": "ad627da2-e753-4dbe-8f2b-24445614ca70", + "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.2, + "FocusedScale": 1.25, + "FadeInDuration": 0.02, + "FadeOutDuration": 0.05, + "PressedScale": 1.15, + "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": "Harvest_Rock", + "persistentUuid": "766570a5-f55e-490a-83cc-9752237db74d", + "type": "Scene3D::Model3DObject", + "variables": [ + { + "name": "Triggered", + "type": "boolean", + "value": false + }, + { + "name": "ResultingObject", + "type": "string", + "value": "Rock" + } + ], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Sphere", + "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 + }, + { + "name": "ShakeModel3D", + "type": "ShakeObject3D::ShakeModel3D", + "TranslationAmplitudeX": 0, + "TranslationAmplitudeY": 0, + "TranslationAmplitudeZ": 5, + "RotationAmplitudeX": 5, + "RotationAmplitudeY": 5, + "RotationAmplitudeZ": 5, + "Time": 5.00000000000364, + "Duration": 5.00000000000364, + "StartEasingDuration": 5.00000000000364, + "StopEasingDuration": 5.00000000000364, + "Frequency": 2, + "DeltaX": 5.00000000000364, + "DeltaY": 5.00000000000364, + "DeltaZ": 5.00000000000364, + "DeltaAngleX": 5.00000000000364, + "DeltaAngleY": 5.00000000000364, + "DeltaAngleZ": 5.00000000000364, + "NoiseTime": 5.00000000000364, + "Object3D": "Object3D", + "IsStartingAtCreation": false + } + ], + "content": { + "centerLocation": "ModelOrigin", + "crossfadeDuration": 0.10000000149011612, + "depth": 137, + "height": 141, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets\\big_rock.glb", + "originLocation": "ModelOrigin", + "rotationX": 0, + "rotationY": 0, + "rotationZ": 0, + "width": 147, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "Harvest_Tree", + "type": "Scene3D::Model3DObject", + "variables": [ + { + "name": "Triggered", + "type": "boolean", + "value": false + }, + { + "name": "ResultingObject", + "type": "string", + "value": "Log" + } + ], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Static", + "bullet": false, + "fixedRotation": false, + "shape": "Sphere", + "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 + }, + { + "name": "ShakeModel3D", + "type": "ShakeObject3D::ShakeModel3D", + "TranslationAmplitudeX": 0, + "TranslationAmplitudeY": 0, + "TranslationAmplitudeZ": 5, + "RotationAmplitudeX": 5, + "RotationAmplitudeY": 5, + "RotationAmplitudeZ": 5, + "Time": 5.00000000000364, + "Duration": 5.00000000000364, + "StartEasingDuration": 5.00000000000364, + "StopEasingDuration": 5.00000000000364, + "Frequency": 2, + "DeltaX": 5.00000000000364, + "DeltaY": 5.00000000000364, + "DeltaZ": 5.00000000000364, + "DeltaAngleX": 5.00000000000364, + "DeltaAngleY": 5.00000000000364, + "DeltaAngleZ": 5.00000000000364, + "NoiseTime": 5.00000000000364, + "Object3D": "Object3D", + "IsStartingAtCreation": false + } + ], + "content": { + "centerLocation": "ModelOrigin", + "crossfadeDuration": 0.10000000149011612, + "depth": 195, + "height": 128, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets\\starting_tree.glb", + "originLocation": "ModelOrigin", + "rotationX": 0, + "rotationY": 0, + "rotationZ": 0, + "width": 128, + "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": 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": 38, + "NormalMapStrength": 1, + "HeightBlending": 0.8, + "TextureVariation": 0.5, + "Edits": "[{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.05},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.05},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.05},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.05},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5,\"radius\":1.5,\"z\":0.05},{\"tool\":\"raise\",\"x\":0,\"y\":0,\"toX\":1,\"toY\":0,\"radius\":0.1221,\"height\":0.4167},{\"tool\":\"raise\",\"x\":0,\"y\":1,\"toX\":1,\"toY\":1,\"radius\":0.1221,\"height\":0.4167},{\"tool\":\"raise\",\"x\":0,\"y\":0,\"toX\":0,\"toY\":1,\"radius\":0.1221,\"height\":0.4167},{\"tool\":\"raise\",\"x\":1,\"y\":0,\"toX\":1,\"toY\":1,\"radius\":0.1221,\"height\":0.4167},{\"tool\":\"raise\",\"x\":0.3372,\"y\":0,\"radius\":0.1139,\"height\":0.3},{\"tool\":\"raise\",\"x\":0.7441,\"y\":0,\"radius\":0.0977,\"height\":0.2667},{\"tool\":\"raise\",\"x\":1,\"y\":0.3698,\"radius\":0.1139,\"height\":0.3333},{\"tool\":\"raise\",\"x\":1,\"y\":0.7604,\"radius\":0.0814,\"height\":0.25},{\"tool\":\"raise\",\"x\":0,\"y\":0.5488,\"radius\":0.1058,\"height\":0.3167},{\"tool\":\"raise\",\"x\":0.5814,\"y\":1,\"radius\":0.1139,\"height\":0.2833},{\"tool\":\"raise\",\"x\":0.2559,\"y\":0.2884,\"radius\":0.1465,\"height\":0.2167},{\"tool\":\"raise\",\"x\":0.7279,\"y\":0.2559,\"radius\":0.1302,\"height\":0.1833},{\"tool\":\"raise\",\"x\":0.7604,\"y\":0.6953,\"radius\":0.1139,\"height\":0.15},{\"tool\":\"raise\",\"x\":0.2396,\"y\":0.7116,\"radius\":0.1221,\"height\":0.1667},{\"tool\":\"raise\",\"x\":0.5,\"y\":0.1745,\"radius\":0.0814,\"height\":0.1},{\"tool\":\"raise\",\"x\":0.4674,\"y\":0.8092,\"radius\":0.0732,\"height\":0.075},{\"tool\":\"raise\",\"x\":0.8418,\"y\":0.4837,\"radius\":0.0814,\"height\":0.1167},{\"tool\":\"raise\",\"x\":0.1419,\"y\":0.4837,\"radius\":0.0732,\"height\":0.0917},{\"tool\":\"smooth\",\"x\":0.5,\"y\":0.5,\"radius\":0.4232,\"strength\":0.3},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5326,\"radius\":0.0846,\"z\":0.05},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5326,\"radius\":0.0846,\"z\":0.05},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5326,\"radius\":0.0846,\"z\":0.05},{\"tool\":\"flatten\",\"x\":0.5,\"y\":0.5326,\"radius\":0.0846,\"z\":0.05},{\"tool\":\"paint\",\"x\":0.5,\"y\":0.5326,\"toX\":0.4539,\"toY\":0.4865,\"radius\":0.0117,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.4539,\"y\":0.4865,\"toX\":0.4079,\"toY\":0.4404,\"radius\":0.0117,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.4079,\"y\":0.4404,\"toX\":0.3618,\"toY\":0.3944,\"radius\":0.0117,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3618,\"y\":0.3944,\"toX\":0.3159,\"toY\":0.3485,\"radius\":0.0117,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.3159,\"y\":0.3485,\"toX\":0.2699,\"toY\":0.3024,\"radius\":0.0117,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.2699,\"y\":0.3024,\"toX\":0.2559,\"toY\":0.2884,\"radius\":0.0117,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5,\"y\":0.5326,\"toX\":0.5516,\"toY\":0.4928,\"radius\":0.0117,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5516,\"y\":0.4928,\"toX\":0.5996,\"toY\":0.4491,\"radius\":0.0117,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5996,\"y\":0.4491,\"toX\":0.6392,\"toY\":0.3973,\"radius\":0.0117,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.6392,\"y\":0.3973,\"toX\":0.6751,\"toY\":0.3431,\"radius\":0.0117,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.6751,\"y\":0.3431,\"toX\":0.7095,\"toY\":0.2878,\"radius\":0.0117,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.7095,\"y\":0.2878,\"toX\":0.7279,\"toY\":0.2559,\"radius\":0.0117,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5,\"y\":0.5326,\"toX\":0.5378,\"toY\":0.5854,\"radius\":0.0107,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5378,\"y\":0.5854,\"toX\":0.5858,\"toY\":0.6291,\"radius\":0.0107,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5858,\"y\":0.6291,\"toX\":0.6421,\"toY\":0.6618,\"radius\":0.0107,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.6421,\"y\":0.6618,\"toX\":0.7021,\"toY\":0.6865,\"radius\":0.0107,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.7021,\"y\":0.6865,\"toX\":0.7604,\"toY\":0.6953,\"radius\":0.0107,\"layer\":2,\"strength\":1},{\"tool\":\"paint\",\"x\":0.5,\"y\":0.5326,\"radius\":0.0488,\"layer\":2,\"strength\":0.5}]", + "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/TerrainMoss.jpg", + "Layer4NormalMap": "assets/TerrainMossNormal.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": 18, + "BladeWidth": 2, + "BaseColor": "44;80;30", + "TipColor": "120;150;60", + "FadeDistance": 2500 + } + }, + { + "assetStoreId": "351881c13d4cbbb24ef54381fb88d1533a8d2528d5531e11db2dbcb42331d20f", + "name": "FallenLog", + "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": 43.8, + "height": 57.1, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Wood Log Moss.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": -90, + "width": 170, + "animations": [] + } + }, + { + "assetStoreId": "dc3a1e6ffe647adc55464dd6584f19c4c9afe77e31dfa51676ce7cfce44cc72e", + "name": "Stump", + "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": 41.1, + "height": 90, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Tree Stump.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": -90, + "width": 68.3, + "animations": [] + } + }, + { + "assetStoreId": "b7ab98ad06406dd305c2106d9454aca2ea4ae9d317e7c103d9306b1b3c4c12f0", + "name": "Campfire", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 30.3, + "height": 98, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Bonfire.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 110, + "animations": [] + } + }, + { + "assetStoreId": "5d7b9f2351d945aa352f42d3b01651c97bb7b719e6a1bcedb3b27f4256abe068", + "name": "Tent", + "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": 212.6, + "height": 249.3, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Tent.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": -90, + "width": 260, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "Bushes", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Model": "assets/Bush 1.glb", + "Size": 81, + "Density": 0.012, + "Seed": 1, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.5,\"y\":0.5326,\"radius\":0.0846},{\"tool\":\"erase\",\"x\":0.5,\"y\":0.5316,\"radius\":0.0212},{\"tool\":\"erase\",\"x\":0.4772,\"y\":0.487,\"radius\":0.0146},{\"tool\":\"erase\",\"x\":0.5374,\"y\":0.5049,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5407,\"y\":0.5618,\"radius\":0.0114},{\"tool\":\"erase\",\"x\":0.4463,\"y\":0.5374,\"radius\":0.0277},{\"tool\":\"erase\",\"x\":0.7971,\"y\":0.5075,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4519,\"y\":0.1256,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4207,\"y\":0.2568,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.762,\"y\":0.3391,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2599,\"y\":0.399,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2284,\"y\":0.239,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3005,\"y\":0.8193,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1088,\"y\":0.2751,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2321,\"y\":0.5531,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8185,\"y\":0.7294,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1571,\"y\":0.2464,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8487,\"y\":0.5826,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5852,\"y\":0.3243,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2978,\"y\":0.8575,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2518,\"y\":0.8923,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1998,\"y\":0.4028,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.7758,\"y\":0.6636,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1154,\"y\":0.8662,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2933,\"y\":0.5644,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5416,\"y\":0.2306,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4992,\"y\":0.8196,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6929,\"y\":0.219,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1557,\"y\":0.5312,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2708,\"y\":0.4838,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2561,\"y\":0.1937,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8878,\"y\":0.3604,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6304,\"y\":0.8197,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.168,\"y\":0.7914,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8836,\"y\":0.2034,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6263,\"y\":0.2963,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3644,\"y\":0.5842,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5668,\"y\":0.7153,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1418,\"y\":0.5731,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3041,\"y\":0.2359,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2095,\"y\":0.5087,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.805,\"y\":0.7973,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1096,\"y\":0.4251,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5684,\"y\":0.3956,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5816,\"y\":0.6669,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2413,\"y\":0.8312,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1911,\"y\":0.7458,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.431,\"y\":0.7577,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5052,\"y\":0.4454,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3881,\"y\":0.3331,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4279,\"y\":0.4022,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4907,\"y\":0.1049,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8611,\"y\":0.1283,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8224,\"y\":0.6147,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2116,\"y\":0.2924,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.337,\"y\":0.8409,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1088,\"y\":0.1308,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.7705,\"y\":0.7664,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6122,\"y\":0.1991,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8792,\"y\":0.7932,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8862,\"y\":0.6252,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4347,\"y\":0.8436,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5295,\"y\":0.3172,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6192,\"y\":0.5652,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8547,\"y\":0.2794,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.7373,\"y\":0.2241,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3916,\"y\":0.5587,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6633,\"y\":0.5795,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3485,\"y\":0.8944,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6306,\"y\":0.4641,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4177,\"y\":0.2073,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1306,\"y\":0.7077,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1995,\"y\":0.6466,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.758,\"y\":0.8258,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1064,\"y\":0.5164,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1531,\"y\":0.4468,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.7459,\"y\":0.1335,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.4809,\"y\":0.6248,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.825,\"y\":0.3782,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.6601,\"y\":0.2322,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.5989,\"y\":0.2712,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.7574,\"y\":0.5082,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.6515,\"y\":0.7605,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.3632,\"y\":0.7718,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.4571,\"y\":0.6635,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.6961,\"y\":0.1319,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.4777,\"y\":0.2207,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.1019,\"y\":0.5806,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.511,\"y\":0.863,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.7844,\"y\":0.1142,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.3692,\"y\":0.696,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.7336,\"y\":0.6247,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.3815,\"y\":0.252,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.6958,\"y\":0.6192,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.8393,\"y\":0.1992,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.6857,\"y\":0.8315,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.8926,\"y\":0.7227,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.4606,\"y\":0.3383,\"radius\":0.0171}]", + "CastShadow": false, + "DrawDistance": 3000, + "MaxSlope": 35 + } + }, + { + "assetStoreId": "", + "name": "BerryBushes", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Model": "assets/Bush Berries 1.glb", + "Size": 82, + "Density": 0.008, + "Seed": 2, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.5,\"y\":0.5326,\"radius\":0.0846},{\"tool\":\"erase\",\"x\":0.5,\"y\":0.5316,\"radius\":0.0212},{\"tool\":\"erase\",\"x\":0.4772,\"y\":0.487,\"radius\":0.0146},{\"tool\":\"erase\",\"x\":0.5374,\"y\":0.5049,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5407,\"y\":0.5618,\"radius\":0.0114},{\"tool\":\"erase\",\"x\":0.4463,\"y\":0.5374,\"radius\":0.0277},{\"tool\":\"erase\",\"x\":0.7971,\"y\":0.5075,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4519,\"y\":0.1256,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4207,\"y\":0.2568,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.762,\"y\":0.3391,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2599,\"y\":0.399,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2284,\"y\":0.239,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3005,\"y\":0.8193,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1088,\"y\":0.2751,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2321,\"y\":0.5531,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8185,\"y\":0.7294,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1571,\"y\":0.2464,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8487,\"y\":0.5826,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5852,\"y\":0.3243,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2978,\"y\":0.8575,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2518,\"y\":0.8923,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1998,\"y\":0.4028,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.7758,\"y\":0.6636,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1154,\"y\":0.8662,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2933,\"y\":0.5644,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5416,\"y\":0.2306,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4992,\"y\":0.8196,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6929,\"y\":0.219,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1557,\"y\":0.5312,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2708,\"y\":0.4838,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2561,\"y\":0.1937,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8878,\"y\":0.3604,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6304,\"y\":0.8197,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.168,\"y\":0.7914,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8836,\"y\":0.2034,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6263,\"y\":0.2963,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3644,\"y\":0.5842,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5668,\"y\":0.7153,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1418,\"y\":0.5731,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3041,\"y\":0.2359,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2095,\"y\":0.5087,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.805,\"y\":0.7973,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1096,\"y\":0.4251,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5684,\"y\":0.3956,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5816,\"y\":0.6669,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2413,\"y\":0.8312,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1911,\"y\":0.7458,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.431,\"y\":0.7577,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5052,\"y\":0.4454,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3881,\"y\":0.3331,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4279,\"y\":0.4022,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4907,\"y\":0.1049,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8611,\"y\":0.1283,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8224,\"y\":0.6147,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2116,\"y\":0.2924,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.337,\"y\":0.8409,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1088,\"y\":0.1308,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.7705,\"y\":0.7664,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6122,\"y\":0.1991,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8792,\"y\":0.7932,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8862,\"y\":0.6252,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4347,\"y\":0.8436,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5295,\"y\":0.3172,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6192,\"y\":0.5652,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8547,\"y\":0.2794,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.7373,\"y\":0.2241,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3916,\"y\":0.5587,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6633,\"y\":0.5795,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3485,\"y\":0.8944,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6306,\"y\":0.4641,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4177,\"y\":0.2073,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1306,\"y\":0.7077,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1995,\"y\":0.6466,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.758,\"y\":0.8258,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1064,\"y\":0.5164,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1531,\"y\":0.4468,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.7459,\"y\":0.1335,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.4809,\"y\":0.6248,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.825,\"y\":0.3782,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.6601,\"y\":0.2322,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.5989,\"y\":0.2712,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.7574,\"y\":0.5082,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.6515,\"y\":0.7605,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.3632,\"y\":0.7718,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.4571,\"y\":0.6635,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.6961,\"y\":0.1319,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.4777,\"y\":0.2207,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.1019,\"y\":0.5806,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.511,\"y\":0.863,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.7844,\"y\":0.1142,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.3692,\"y\":0.696,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.7336,\"y\":0.6247,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.3815,\"y\":0.252,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.6958,\"y\":0.6192,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.8393,\"y\":0.1992,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.6857,\"y\":0.8315,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.8926,\"y\":0.7227,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.4606,\"y\":0.3383,\"radius\":0.0171}]", + "CastShadow": false, + "DrawDistance": 3000, + "MaxSlope": 35 + } + }, + { + "assetStoreId": "", + "name": "Plants", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Model": "assets/Plant 1.glb", + "Size": 30, + "Density": 0.012, + "Seed": 3, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.5,\"y\":0.5326,\"radius\":0.0846},{\"tool\":\"erase\",\"x\":0.5,\"y\":0.5316,\"radius\":0.0212},{\"tool\":\"erase\",\"x\":0.4772,\"y\":0.487,\"radius\":0.0146},{\"tool\":\"erase\",\"x\":0.5374,\"y\":0.5049,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5407,\"y\":0.5618,\"radius\":0.0114},{\"tool\":\"erase\",\"x\":0.4463,\"y\":0.5374,\"radius\":0.0277},{\"tool\":\"erase\",\"x\":0.7971,\"y\":0.5075,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4519,\"y\":0.1256,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4207,\"y\":0.2568,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.762,\"y\":0.3391,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2599,\"y\":0.399,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2284,\"y\":0.239,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3005,\"y\":0.8193,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1088,\"y\":0.2751,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2321,\"y\":0.5531,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8185,\"y\":0.7294,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1571,\"y\":0.2464,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8487,\"y\":0.5826,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5852,\"y\":0.3243,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2978,\"y\":0.8575,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2518,\"y\":0.8923,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1998,\"y\":0.4028,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.7758,\"y\":0.6636,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1154,\"y\":0.8662,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2933,\"y\":0.5644,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5416,\"y\":0.2306,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4992,\"y\":0.8196,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6929,\"y\":0.219,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1557,\"y\":0.5312,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2708,\"y\":0.4838,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2561,\"y\":0.1937,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8878,\"y\":0.3604,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6304,\"y\":0.8197,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.168,\"y\":0.7914,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8836,\"y\":0.2034,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6263,\"y\":0.2963,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3644,\"y\":0.5842,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5668,\"y\":0.7153,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1418,\"y\":0.5731,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3041,\"y\":0.2359,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2095,\"y\":0.5087,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.805,\"y\":0.7973,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1096,\"y\":0.4251,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5684,\"y\":0.3956,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5816,\"y\":0.6669,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2413,\"y\":0.8312,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1911,\"y\":0.7458,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.431,\"y\":0.7577,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5052,\"y\":0.4454,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3881,\"y\":0.3331,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4279,\"y\":0.4022,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4907,\"y\":0.1049,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8611,\"y\":0.1283,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8224,\"y\":0.6147,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2116,\"y\":0.2924,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.337,\"y\":0.8409,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1088,\"y\":0.1308,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.7705,\"y\":0.7664,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6122,\"y\":0.1991,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8792,\"y\":0.7932,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8862,\"y\":0.6252,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4347,\"y\":0.8436,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5295,\"y\":0.3172,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6192,\"y\":0.5652,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8547,\"y\":0.2794,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.7373,\"y\":0.2241,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3916,\"y\":0.5587,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6633,\"y\":0.5795,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3485,\"y\":0.8944,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6306,\"y\":0.4641,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4177,\"y\":0.2073,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1306,\"y\":0.7077,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1995,\"y\":0.6466,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.758,\"y\":0.8258,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1064,\"y\":0.5164,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1531,\"y\":0.4468,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.7459,\"y\":0.1335,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.4809,\"y\":0.6248,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.825,\"y\":0.3782,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.6601,\"y\":0.2322,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.5989,\"y\":0.2712,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.7574,\"y\":0.5082,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.6515,\"y\":0.7605,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.3632,\"y\":0.7718,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.4571,\"y\":0.6635,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.6961,\"y\":0.1319,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.4777,\"y\":0.2207,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.1019,\"y\":0.5806,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.511,\"y\":0.863,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.7844,\"y\":0.1142,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.3692,\"y\":0.696,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.7336,\"y\":0.6247,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.3815,\"y\":0.252,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.6958,\"y\":0.6192,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.8393,\"y\":0.1992,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.6857,\"y\":0.8315,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.8926,\"y\":0.7227,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.4606,\"y\":0.3383,\"radius\":0.0171}]", + "CastShadow": false, + "DrawDistance": 3000, + "MaxSlope": 35 + } + }, + { + "assetStoreId": "", + "name": "Flowers", + "type": "Terrain3D::ScatteredModels", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Model": "assets/Flowers.glb", + "Size": 60, + "Density": 0.015, + "Seed": 4, + "Coverage": "Everywhere", + "Edits": "[{\"tool\":\"erase\",\"x\":0.5,\"y\":0.5326,\"radius\":0.0846},{\"tool\":\"erase\",\"x\":0.5,\"y\":0.5316,\"radius\":0.0212},{\"tool\":\"erase\",\"x\":0.4772,\"y\":0.487,\"radius\":0.0146},{\"tool\":\"erase\",\"x\":0.5374,\"y\":0.5049,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5407,\"y\":0.5618,\"radius\":0.0114},{\"tool\":\"erase\",\"x\":0.4463,\"y\":0.5374,\"radius\":0.0277},{\"tool\":\"erase\",\"x\":0.7971,\"y\":0.5075,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4519,\"y\":0.1256,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4207,\"y\":0.2568,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.762,\"y\":0.3391,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2599,\"y\":0.399,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2284,\"y\":0.239,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3005,\"y\":0.8193,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1088,\"y\":0.2751,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2321,\"y\":0.5531,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8185,\"y\":0.7294,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1571,\"y\":0.2464,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8487,\"y\":0.5826,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5852,\"y\":0.3243,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2978,\"y\":0.8575,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2518,\"y\":0.8923,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1998,\"y\":0.4028,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.7758,\"y\":0.6636,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1154,\"y\":0.8662,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2933,\"y\":0.5644,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5416,\"y\":0.2306,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4992,\"y\":0.8196,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6929,\"y\":0.219,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1557,\"y\":0.5312,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2708,\"y\":0.4838,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2561,\"y\":0.1937,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8878,\"y\":0.3604,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6304,\"y\":0.8197,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.168,\"y\":0.7914,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8836,\"y\":0.2034,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6263,\"y\":0.2963,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3644,\"y\":0.5842,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5668,\"y\":0.7153,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1418,\"y\":0.5731,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3041,\"y\":0.2359,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2095,\"y\":0.5087,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.805,\"y\":0.7973,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1096,\"y\":0.4251,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5684,\"y\":0.3956,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5816,\"y\":0.6669,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2413,\"y\":0.8312,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1911,\"y\":0.7458,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.431,\"y\":0.7577,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5052,\"y\":0.4454,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3881,\"y\":0.3331,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4279,\"y\":0.4022,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4907,\"y\":0.1049,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8611,\"y\":0.1283,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8224,\"y\":0.6147,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.2116,\"y\":0.2924,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.337,\"y\":0.8409,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1088,\"y\":0.1308,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.7705,\"y\":0.7664,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6122,\"y\":0.1991,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8792,\"y\":0.7932,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8862,\"y\":0.6252,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4347,\"y\":0.8436,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.5295,\"y\":0.3172,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6192,\"y\":0.5652,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.8547,\"y\":0.2794,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.7373,\"y\":0.2241,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3916,\"y\":0.5587,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6633,\"y\":0.5795,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.3485,\"y\":0.8944,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.6306,\"y\":0.4641,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.4177,\"y\":0.2073,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1306,\"y\":0.7077,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1995,\"y\":0.6466,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.758,\"y\":0.8258,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1064,\"y\":0.5164,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.1531,\"y\":0.4468,\"radius\":0.0163},{\"tool\":\"erase\",\"x\":0.7459,\"y\":0.1335,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.4809,\"y\":0.6248,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.825,\"y\":0.3782,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.6601,\"y\":0.2322,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.5989,\"y\":0.2712,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.7574,\"y\":0.5082,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.6515,\"y\":0.7605,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.3632,\"y\":0.7718,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.4571,\"y\":0.6635,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.6961,\"y\":0.1319,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.4777,\"y\":0.2207,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.1019,\"y\":0.5806,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.511,\"y\":0.863,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.7844,\"y\":0.1142,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.3692,\"y\":0.696,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.7336,\"y\":0.6247,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.3815,\"y\":0.252,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.6958,\"y\":0.6192,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.8393,\"y\":0.1992,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.6857,\"y\":0.8315,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.8926,\"y\":0.7227,\"radius\":0.0171},{\"tool\":\"erase\",\"x\":0.4606,\"y\":0.3383,\"radius\":0.0171}]", + "CastShadow": false, + "DrawDistance": 3000, + "MaxSlope": 35 + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Player" + }, + { + "objectName": "Log" + }, + { + "objectName": "Rock" + }, + { + "objectName": "Harvest_Rock" + }, + { + "objectName": "Harvest_Tree" + }, + { + "objectName": "HighlightText" + }, + { + "objectName": "SlotText" + }, + { + "objectName": "InventorySlot" + }, + { + "objectName": "SelectionBox" + }, + { + "objectName": "InventoryIcons" + }, + { + "objectName": "Crosshair" + }, + { + "objectName": "ControlsToggle" + }, + { + "folderName": "Mobile Controls", + "children": [ + { + "objectName": "MovementJoystick" + }, + { + "objectName": "JumpButton" + }, + { + "objectName": "ActionButton" + } + ] + }, + { + "folderName": "World", + "children": [ + { + "objectName": "Terrain" + }, + { + "objectName": "MapLimit" + }, + { + "objectName": "Grass" + } + ] + }, + { + "folderName": "Nature", + "children": [ + { + "objectName": "Bushes" + }, + { + "objectName": "BerryBushes" + }, + { + "objectName": "Plants" + }, + { + "objectName": "Flowers" + }, + { + "objectName": "FallenLog" + }, + { + "objectName": "Stump" + } + ] + }, + { + "folderName": "Decoration", + "children": [ + { + "objectName": "Campfire" + }, + { + "objectName": "Tent" + }, + { + "objectName": "Obstacle" + } + ] + } + ] + }, + "events": [ + { + "folded": false, + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Hide" + }, + "parameters": [ + "Player" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Hide" + }, + "parameters": [ + "MovementJoystick" + ] + }, + { + "type": { + "value": "Hide" + }, + "parameters": [ + "JumpButton" + ] + }, + { + "type": { + "value": "Hide" + }, + "parameters": [ + "ActionButton" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::ActivateControl" + }, + "parameters": [ + "MovementJoystick", + "", + "" + ] + } + ] + } + ] + }, + { + "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": "Show" + }, + "parameters": [ + "ActionButton", + "" + ] + }, + { + "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": "Hide" + }, + "parameters": [ + "ActionButton" + ] + }, + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "JumpButton", + "MultitouchButton", + "" + ] + } + ] + } + ] + } + ], + "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": "StringVariable" + }, + "parameters": [ + "ControlKind", + "=", + "\"Touch\"" + ] + }, + { + "type": { + "inverted": true, + "value": "IsCursorOnObject" + }, + "parameters": [ + "ControlsToggle", + "", + "", + "" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Not" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "IsCursorOnObject" + }, + "parameters": [ + "InventorySlot", + "", + "", + "" + ] + } + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::HasTouchStartedOnScreenSide" + }, + "parameters": [ + "", + "MovementJoystick", + "\"Left\"", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::TeleportAndPress" + }, + "parameters": [ + "MovementJoystick", + "CursorX(\"UI\")", + "CursorY(\"UI\")", + "" + ] + } + ] + }, + { + "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": [ + "", + "MovementJoystick", + "\"Left\"", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" + }, + "parameters": [ + "JumpButton", + "MultitouchButton", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "ActionButton", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "MousePointerLock::RequestPointerLock" + }, + "parameters": [ + "", + "" + ] + } + ] + } + ] + } + ] + } + ], + "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\"" + ] + }, + { + "type": { + "value": "MouseButtonFromTextPressed" + }, + "parameters": [ + "", + "\"Left\"" + ] + }, + { + "type": { + "inverted": true, + "value": "IsCursorOnObject" + }, + "parameters": [ + "ControlsToggle", + "", + "", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "MousePointerLock::isPointerLocked" + }, + "parameters": [ + "", + "" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Not" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "IsCursorOnObject" + }, + "parameters": [ + "InventorySlot", + "", + "yes", + "" + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "MousePointerLock::RequestPointerLock" + }, + "parameters": [ + "", + "" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Declare initial inventory variables", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::ForEach", + "object": "InventorySlot", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "Inventory[InventorySlot.SlotNumber].Name", + "=", + "\"Empty\"" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Inventory[InventorySlot.SlotNumber].Quantity", + "=", + "0" + ] + } + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Display object name", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringObjectVariable" + }, + "parameters": [ + "HighlightText", + "TextToUse", + "!=", + "\"\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringObjectVariable" + }, + "parameters": [ + "HighlightText", + "TextToUse", + "=", + "\"\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Raycaster3D::RaycastFromCameraCenter" + }, + "parameters": [ + "", + "Item", + "200", + "" + ] + }, + { + "type": { + "value": "BooleanObjectVariable" + }, + "parameters": [ + "Item", + "Held", + "False", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringObjectVariable" + }, + "parameters": [ + "HighlightText", + "TextToUse", + "=", + "Item.ObjectName()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::Value" + }, + "parameters": [ + "HighlightText", + "Text", + "!=", + "HighlightText.TextToUse" + ] + } + ], + "actions": [ + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "HighlightText", + "Text", + "=", + "HighlightText.TextToUse" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Select inventory slot", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsClicked" + }, + "parameters": [ + "InventorySlot", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "SelectedSlot", + "=", + "InventorySlot.SlotNumber" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "UpdateInventoryDisplay", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "AnyKeyPressed" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "ToNumber(SubStr(LastPressedKey(),3,1))-1", + ">", + "-1" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "ToNumber(SubStr(LastPressedKey(),3,1))-1", + "<", + "5" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "SelectedSlot", + "=", + "ToNumber(SubStr(LastPressedKey(),3,1))-1" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "UpdateInventoryDisplay", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "IsMouseWheelScrollingUp" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "SelectedSlot", + "=", + "mod(SelectedSlot+1,5)" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "UpdateInventoryDisplay", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "IsMouseWheelScrollingDown" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "SelectedSlot", + "=", + "mod(SelectedSlot-1,5)" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "UpdateInventoryDisplay", + "True", + "" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Perform action", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "BuiltinCommonInstructions::And" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "ActionButton", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "ControlKind", + "=", + "\"Touch\"" + ] + } + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::And" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "MouseButtonFromTextPressed" + }, + "parameters": [ + "", + "\"Left\"" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "ControlKind", + "=", + "\"Keyboard\"" + ] + } + ] + } + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [], + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Harvest items", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Raycaster3D::RaycastFromCameraCenter" + }, + "parameters": [ + "", + "Harvestable", + "200", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanObjectVariable" + }, + "parameters": [ + "Harvestable", + "Triggered", + "True", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "ActionTaken", + "True", + "" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Pick up items", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ActionTaken", + "=", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Raycaster3D::RaycastFromCameraCenter" + }, + "parameters": [ + "", + "Item", + "200", + "" + ] + }, + { + "type": { + "value": "BooleanObjectVariable" + }, + "parameters": [ + "Item", + "Held", + "False", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanObjectVariable" + }, + "parameters": [ + "Item", + "PickUp", + "True", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "ActionTaken", + "True", + "" + ] + } + ] + } + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Place items", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ActionTaken", + "=", + "" + ] + }, + { + "type": { + "value": "BooleanObjectVariable" + }, + "parameters": [ + "Item", + "Held", + "True", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Raycaster3D::RaycastFromCameraCenter" + }, + "parameters": [ + "", + "RaycastSurface", + "200", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanObjectVariable" + }, + "parameters": [ + "Item", + "JustPlaced", + "True", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "ActionTaken", + "True", + "" + ] + } + ] + } + ] + } + ] + } + ], + "parameters": [] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "ActionTaken", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "PlaySound" + }, + "parameters": [ + "", + "assets\\Placement thud.wav", + "", + "100", + "RandomFloatInRange(0.85,1)" + ] + } + ] + } + ], + "variables": [ + { + "name": "ActionTaken", + "type": "boolean", + "value": false + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Harvested objected", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanObjectVariable" + }, + "parameters": [ + "Harvestable", + "Triggered", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanObjectVariable" + }, + "parameters": [ + "Harvestable", + "Triggered", + "False", + "" + ] + }, + { + "type": { + "value": "ShakeObject3D::ShakeModel3D::Shake" + }, + "parameters": [ + "Harvestable", + "ShakeModel3D", + "0.1", + "0", + "0.05", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "CreateByName" + }, + "parameters": [ + "", + "Item", + "Harvestable.ResultingObject", + "Player.CenterX()+RandomFloatInRange(-10,10)", + "Player.CenterY()+RandomFloatInRange(-10,10)", + "" + ] + }, + { + "type": { + "value": "SetBooleanObjectVariable" + }, + "parameters": [ + "Item", + "PickUp", + "True", + "" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetCenterZ" + }, + "parameters": [ + "Item", + "Object3D", + "=", + "Player.Object3D::Z()+Player.Object3D::Depth()" + ] + } + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Add item to inventory", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanObjectVariable" + }, + "parameters": [ + "Item", + "PickUp", + "True", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Check for slot with the same object name" + }, + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "VariableChildCount(Inventory)", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "SlotFound", + "=", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Inventory[SlotCrawler].Name", + "=", + "Item.ObjectName()" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "SlotFound", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "SlotFound", + "=", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "SlotCrawler", + "+", + "1" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "SlotFound", + "=", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "SlotCrawler", + "=", + "0" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Check if selected slot is empty" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "SlotFound", + "=", + "" + ] + }, + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Inventory[SelectedSlot].Name", + "=", + "\"Empty\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "SlotFound", + "True", + "" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "SlotCrawler", + "=", + "SelectedSlot" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Check for available empty slot" + }, + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "VariableChildCount(Inventory)", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "SlotFound", + "=", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Inventory[SlotCrawler].Name", + "=", + "\"Empty\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "SlotFound", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "SlotFound", + "=", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "SlotCrawler", + "+", + "1" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Add item if slot is found" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "SlotFound", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "Inventory[SlotCrawler].Name", + "=", + "Item.ObjectName()" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Inventory[SlotCrawler].Quantity", + "+", + "1" + ] + }, + { + "type": { + "value": "Delete" + }, + "parameters": [ + "Item", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "UpdateInventoryDisplay", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanObjectVariable" + }, + "parameters": [ + "Item", + "PickUp", + "False", + "" + ] + } + ] + } + ], + "variables": [ + { + "name": "SlotCrawler", + "type": "number", + "value": 0 + }, + { + "name": "SlotFound", + "type": "boolean", + "value": false + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Place object from inventory", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanObjectVariable" + }, + "parameters": [ + "Item", + "Held", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Hide" + }, + "parameters": [ + "Item" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Raycaster3D::RaycastFromCameraCenter" + }, + "parameters": [ + "", + "RaycastSurface", + "200", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Show" + }, + "parameters": [ + "Item", + "" + ] + }, + { + "type": { + "value": "SetXY" + }, + "parameters": [ + "Item", + "=", + "Raycaster3D::IntersectionX()", + "=", + "Raycaster3D::IntersectionY()" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetZ" + }, + "parameters": [ + "Item", + "Object3D", + "=", + "Raycaster3D::IntersectionZ()" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Place item" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanObjectVariable" + }, + "parameters": [ + "Item", + "JustPlaced", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanObjectVariable" + }, + "parameters": [ + "Item", + "Held", + "False", + "" + ] + }, + { + "type": { + "value": "SetBooleanObjectVariable" + }, + "parameters": [ + "Item", + "JustPlaced", + "False", + "" + ] + }, + { + "type": { + "value": "Tween3D::SetTint" + }, + "parameters": [ + "", + "Item", + "Object3D", + "\"255;255;255\"", + "" + ] + }, + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "Item", + "Physics3D", + "yes" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Inventory[SelectedSlot].Quantity", + "-", + "1" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "UpdateInventoryDisplay", + "True", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "Inventory[SelectedSlot].Quantity", + "<=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "Inventory[SelectedSlot].Name", + "=", + "\"Empty\"" + ] + } + ] + } + ] + } + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Update inventory display", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "UpdateInventoryDisplay", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Delete" + }, + "parameters": [ + "InventoryIcons", + "" + ] + }, + { + "type": { + "value": "Delete" + }, + "parameters": [ + "SlotText", + "Text" + ] + }, + { + "type": { + "value": "Delete" + }, + "parameters": [ + "SelectionBox", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "UpdateInventoryDisplay", + "False", + "1" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Create inventory icon and text" + }, + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "VariableChildCount(Inventory)", + "conditions": [ + { + "type": { + "value": "NumberObjectVariable" + }, + "parameters": [ + "InventorySlot", + "SlotNumber", + "=", + "SlotCrawler" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Inventory[SlotCrawler].Name", + "!=", + "\"Empty\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Create" + }, + "parameters": [ + "", + "InventoryIcons", + "InventorySlot.CenterX()", + "InventorySlot.CenterY()", + "\"UI\"" + ] + }, + { + "type": { + "value": "AnimatableCapability::AnimatableBehavior::SetName" + }, + "parameters": [ + "InventoryIcons", + "Animation", + "=", + "Inventory[SlotCrawler].Name" + ] + }, + { + "type": { + "value": "Create" + }, + "parameters": [ + "", + "SlotText", + "InventorySlot.CenterX()+16", + "InventorySlot.CenterY()+16", + "\"UI\"" + ] + }, + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "SlotText", + "Text", + "=", + "Inventory[SlotCrawler].Quantity" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "SlotCrawler", + "+", + "1" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberObjectVariable" + }, + "parameters": [ + "InventorySlot", + "SlotNumber", + "=", + "SelectedSlot" + ] + } + ], + "actions": [ + { + "type": { + "value": "Create" + }, + "parameters": [ + "", + "SelectionBox", + "InventorySlot.CenterX()", + "InventorySlot.CenterY()", + "\"UI\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Create held item" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanObjectVariable" + }, + "parameters": [ + "Item", + "Held", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Delete" + }, + "parameters": [ + "Item", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Inventory[SelectedSlot].Name", + "!=", + "\"Empty\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "CreateByName" + }, + "parameters": [ + "", + "Item", + "Inventory[SelectedSlot].Name", + "0", + "0", + "" + ] + }, + { + "type": { + "value": "SetBooleanObjectVariable" + }, + "parameters": [ + "Item", + "Held", + "True", + "" + ] + }, + { + "type": { + "value": "Tween3D::SetTint" + }, + "parameters": [ + "", + "Item", + "Object3D", + "\"85;85;85\"", + "" + ] + }, + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "Item", + "Physics3D", + "" + ] + }, + { + "type": { + "value": "Hide" + }, + "parameters": [ + "Item" + ] + } + ] + } + ], + "variables": [ + { + "name": "SlotCrawler", + "type": "number", + "value": 0 + } + ] + } + ], + "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::HemisphereLight", + "name": "3D Light", + "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": 3000, + "intensity": 0.75, + "minimumShadowBias": 0.0002, + "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": [] + }, + { + "ambientLightColorB": 200, + "ambientLightColorG": 200, + "ambientLightColorR": 200, + "camera2DPlaneMaxDrawingDistance": 5000, + "camera3DFarPlaneDistance": 10000, + "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": "ButtonFSM", + "type": "ButtonStates::ButtonFSM" + }, + { + "name": "ButtonScaleTween", + "type": "ButtonStates::ButtonScaleTween" + }, + { + "name": "Effect", + "type": "EffectCapability::EffectBehavior" + }, + { + "name": "FirstPersonMultitouchMapper", + "type": "SpriteMultitouchJoystick::FirstPersonMultitouchMapper" + }, + { + "name": "FirstPersonPointerMapper", + "type": "MousePointerLock::FirstPersonPointerMapper" + }, + { + "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": "Resizable", + "type": "ResizableCapability::ResizableBehavior" + }, + { + "name": "Scale", + "type": "ScalableCapability::ScalableBehavior" + }, + { + "name": "ShakeModel3D", + "type": "ShakeObject3D::ShakeModel3D", + "EasingFactor": 358874684797066000000 + }, + { + "name": "Shooter3DKeyboardMapper", + "type": "PhysicsCharacter3DKeyMapper::Shooter3DKeyboardMapper" + }, + { + "name": "Shooter3DMultitouchMapper", + "type": "SpriteMultitouchJoystick::Shooter3DMultitouchMapper" + }, + { + "name": "Text", + "type": "TextContainerCapability::TextContainerBehavior" + }, + { + "name": "Tween", + "type": "Tween::TweenBehavior" + } + ] + } + ], + "externalEvents": [], + "tests": [ + { + "description": "\"w\" walks the player toward what it faces and \"d\" strafes it sideways, without turning it.", + "name": "Walking and strafing with WASD", + "type": "gameplay", + "source": [ + "// First person movement: \"w\" walks toward what the player faces, \"d\"", + "// strafes to its right (Shooter3DKeyboardMapper, camera relative).", + "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);", + "for (const name of [\"MobileControls\",\"UI\",\"Crosshair Layer\"]) {", + " if (!harness.getCurrentRuntimeScene().hasLayer(name)) continue;", + " const layer = harness.getCurrentRuntimeScene().getLayer(name);", + " layer.setCameraX(640);", + " layer.setCameraY(360);", + " layer.setCameraZoom(160 / 1280);", + "}", + "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('Player');", + "", + "const getPlayer = () => harness.getObjects('Player')[0];", + "const characterState = () => getPlayer().behaviors.PhysicsCharacter3D.state;", + "", + "await harness.stepFrames(4);", + "harness.assert(characterState().IsOnFloor === true, 'The player stands on the ground.');", + "", + "const start = getPlayer();", + "const facingRadians = (start.angle * Math.PI) / 180;", + "", + "// Nothing pressed: the player stays where it is.", + "await harness.stepFrames(5);", + "const idle = getPlayer();", + "const drift = Math.hypot(idle.centerX - start.centerX, idle.centerY - start.centerY);", + "harness.assert(", + " drift < 3,", + " 'The player stands still while no key is pressed (drifted ' + drift.toFixed(2) + ').'", + ");", + "", + "// Walk forward.", + "harness.setKeyPressed('w', true);", + "await harness.stepFrames(15);", + "harness.setKeyPressed('w', false);", + "await harness.stepFrames(2);", + "", + "const afterWalk = getPlayer();", + "const walkX = afterWalk.centerX - idle.centerX;", + "const walkY = afterWalk.centerY - idle.centerY;", + "const walked = Math.hypot(walkX, walkY);", + "const forward = walkX * Math.cos(facingRadians) + walkY * Math.sin(facingRadians);", + "console.log(", + " 'walked=' + Math.round(walked) + ' forward=' + Math.round(forward) +", + " ' facing=' + Math.round(start.angle)", + ");", + "harness.assert(", + " walked > 15,", + " 'Holding \"w\" walks the player (moved ' + Math.round(walked) + ' units).'", + ");", + "harness.assert(", + " forward > 0.9 * walked,", + " 'The player walks toward what it is facing, not sideways or backwards.'", + ");", + "", + "// Strafe right.", + "const beforeStrafe = getPlayer();", + "harness.setKeyPressed('d', true);", + "await harness.stepFrames(14);", + "harness.setKeyPressed('d', false);", + "await harness.stepFrames(2);", + "const afterStrafe = getPlayer();", + "const strafeX = afterStrafe.centerX - beforeStrafe.centerX;", + "const strafeY = afterStrafe.centerY - beforeStrafe.centerY;", + "const strafed = Math.hypot(strafeX, strafeY);", + "// The player's right hand side, in scene coordinates.", + "const sideways = -strafeX * Math.sin(facingRadians) + strafeY * Math.cos(facingRadians);", + "console.log('strafed=' + Math.round(strafed) + ' sideways=' + Math.round(sideways));", + "harness.assert(", + " strafed > 15,", + " 'Holding \"d\" moves the player (moved ' + Math.round(strafed) + ' units).'", + ");", + "harness.assert(", + " Math.abs(sideways) > 0.9 * strafed,", + " 'Holding \"d\" strafes sideways instead of walking forward (' +", + " Math.round(sideways) + ' of ' + Math.round(strafed) + ' units sideways).'", + ");", + "harness.assert(", + " Math.abs(afterStrafe.angle - start.angle) < 1,", + " 'Strafing does not turn the player around.'", + ");" + ] + }, + { + "description": "A tree in the crosshair is harvested by acting on it, and the log goes into the inventory.", + "name": "Harvesting a tree fills the inventory", + "type": "gameplay", + "source": [ + "// The core loop: look at something harvestable, act on it, and what comes", + "// out ends up in the inventory.", + "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);", + "for (const name of [\"MobileControls\",\"UI\",\"Crosshair Layer\"]) {", + " if (!harness.getCurrentRuntimeScene().hasLayer(name)) continue;", + " const layer = harness.getCurrentRuntimeScene().getLayer(name);", + " layer.setCameraX(640);", + " layer.setCameraY(360);", + " layer.setCameraZoom(160 / 1280);", + "}", + "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('Log');", + "", + "const getPlayer = () => harness.getObjects('Player')[0];", + "", + "/** The inventory the game keeps in a scene variable, slot by slot. */", + "const inventorySlots = () => {", + " const inventory = harness.getSceneVariable('Inventory');", + " const slots = (inventory && inventory.children) || [];", + " return slots.map((slot) => {", + " const entries = slot.children || [];", + " const read = (name) => {", + " const entry = entries.find((one) => one.name === name);", + " return entry ? entry.value : null;", + " };", + " return { name: read('Name'), quantity: Number(read('Quantity')) };", + " });", + "};", + "const filledSlots = () => inventorySlots().filter((slot) => slot.name !== 'Empty');", + "", + "await harness.stepFrames(6);", + "const player = getPlayer();", + "console.log(", + " 'player=' + Math.round(player.centerX) + ',' + Math.round(player.centerY) +", + " ',' + Math.round(player.centerZ) + ' angle=' + Math.round(player.angle) +", + " ' inventory=' + JSON.stringify(filledSlots())", + ");", + "harness.assert(filledSlots().length === 0, 'The inventory starts empty.');", + "", + "// Arrange: bring a tree into the crosshair. The game harvests whatever the", + "// camera is looking at within 200 units, and the trees are static bodies, so", + "// the tree is moved in front of the player rather than the player turned", + "// toward it. Harvesting it is still up to the game.", + "const tree = harness.getObjects('Harvest_Tree')[0];", + "harness.assert(!!tree, 'There is a tree to harvest.');", + "// Take every other harvestable out of the level, so that whatever is", + "// harvested can only have come from this tree.", + "for (const other of [...harness.getObjects('Harvest_Tree'), ...harness.getObjects('Harvest_Rock')]) {", + " if (other.id !== tree.id) harness.removeObject(other.id);", + "}", + "console.log('harvestablesLeft=' + (harness.getObjects('Harvest_Tree').length + harness.getObjects('Harvest_Rock').length));", + "const facing = (player.angle * Math.PI) / 180;", + "const AHEAD = 120;", + "harness.setObjectPosition(", + " tree.id,", + " tree.x + (player.centerX + Math.cos(facing) * AHEAD - tree.centerX),", + " tree.y + (player.centerY + Math.sin(facing) * AHEAD - tree.centerY),", + " tree.z", + ");", + "await harness.stepFrames(4);", + "", + "const movedTree = harness.getObjects('Harvest_Tree').find((one) => one.id === tree.id);", + "console.log(", + " 'treeInFront=' + Math.round(movedTree.centerX) + ',' + Math.round(movedTree.centerY) +", + " ',' + Math.round(movedTree.centerZ) +", + " ' distance=' + Math.round(Math.hypot(movedTree.centerX - player.centerX, movedTree.centerY - player.centerY))", + ");", + "harness.assert(", + " filledSlots().length === 0,", + " 'Nothing is harvested just by standing in front of the tree.'", + ");", + "", + "// Act on it.", + "harness.setMouseButtonPressed(true);", + "await harness.stepFrames(4);", + "harness.setMouseButtonPressed(false);", + "const harvested = await harness.stepUntil(", + " () => filledSlots().some((slot) => slot.name === 'Log'),", + " { maxFrames: 60 }", + ");", + "", + "console.log('inventoryAfter=' + JSON.stringify(filledSlots()));", + "harness.assert(", + " harvested,", + " 'Acting on the tree puts a log in the inventory (it holds ' +", + " JSON.stringify(filledSlots()) + ').'", + ");", + "", + "// A picture of what the game looks like once this has happened, kept with", + "// the test results.", + "await harness.takeScreenshot('the inventory holding the log that was harvested');" + ] + } + ], + "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": "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": "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": "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": "General", + "dimension": "3D", + "extensionNamespace": "", + "fullName": "3D raycast", + "gdevelopVersion": ">=5.5.222", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXJheS1zdGFydC1hcnJvdyIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGQ9Ik0yMywxMkwxOSwxNlYxM0g2LjgzQzYuNDIsMTQuMTcgNS4zMSwxNSA0LDE1QTMsMyAwIDAsMSAxLDEyQTMsMyAwIDAsMSA0LDlDNS4zMSw5IDYuNDIsOS44MyA2LjgzLDExSDE5VjhMMjMsMTJaIiAvPjwvc3ZnPg==", + "name": "Raycaster3D", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/8419f46b76bce482c14b4c03b4141a64d457e4cdc92686f3470381f5d2694abd_ray-start-arrow.svg", + "shortDescription": "Find 3D objects that cross a line.", + "version": "0.1.8", + "description": [ + "It can be useful to:", + "- Find 3D objects under the pointer", + "- Target objects from a 1st person view", + "- Make AI that detect objects in their field of view" + ], + "origin": { + "identifier": "Raycaster3D", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d", + "recast", + "collision" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onFirstSceneLoaded", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Raycaster3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "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.__raycaster3DExtension) {", + " return;", + "}", + "", + "class Raycaster {", + " raycaster = new THREE.Raycaster();", + " pointer = new THREE.Vector2();", + " /** @type {Array} */", + " raycastResults = [];", + " lastDistance = 0;", + " lastPositionX = 0;", + " lastPositionY = 0;", + " lastPositionZ = 0;", + " lastNormal = new THREE.Vector3();", + "", + " /**", + " * @param objectsLists {Hashtable}", + " * @param objects {gdjs.RuntimeObject[]}", + " * @param pointerX {number}", + " * @param pointerY {number}", + " * @param distanceMax {number}", + " */", + " recastFromCamera(objectsLists, objects, pointerX, pointerY, distanceMax) {", + " if (objects.length === 0) {", + " return false;", + " }", + " const object = objects[0];", + " const layer = object.getInstanceContainer().getLayer(object.getLayer());", + " const camera = layer.getRenderer().getThreeCamera();", + "", + " const raycaster = this.raycaster;", + " const pointer = this.pointer;", + " pointer.x = -1 + 2 * pointerX;", + " pointer.y = 1 - 2 * pointerY;", + " raycaster.setFromCamera(pointer, camera);", + "", + " const scene = object.getRuntimeScene();", + " const inverseWorldScale = scene.getRenderer3DInverseWorldScale ? scene.getRenderer3DInverseWorldScale() : 1;", + " raycaster.far = distanceMax * inverseWorldScale;", + "", + " return this._doRecast(objectsLists, objects);", + " }", + "", + " /**", + " * @param objectsLists {Hashtable}", + " * @param objects {gdjs.RuntimeObject[]}", + " * @param originX {number}", + " * @param originY {number}", + " * @param originZ {number}", + " * @param rotationAngle {number}", + " * @param elevationAngle {number}", + " * @param distanceMax {number}", + " */", + " recastWithAngle(", + " objectsLists,", + " objects,", + " originX,", + " originY,", + " originZ,", + " rotationAngle,", + " elevationAngle,", + " distanceMax", + " ) {", + " if (objects.length === 0) {", + " return false;", + " }", + " const scene = objects[0].getRuntimeScene();", + " const inverseWorldScale = scene.getRenderer3DInverseWorldScale ? scene.getRenderer3DInverseWorldScale() : 1;", + " const raycaster = this.raycaster;", + " raycaster.ray.origin.set(", + " originX * inverseWorldScale,", + " -originY * inverseWorldScale,", + " originZ * inverseWorldScale,", + " );", + " const rotation = rotationAngle * Math.PI / 180;", + " const elevation = elevationAngle * Math.PI / 180;", + " const cosElevation = Math.cos(elevation);", + " raycaster.ray.direction.set(", + " Math.cos(rotation) * cosElevation,", + " -Math.sin(rotation) * cosElevation,", + " Math.sin(elevation),", + " );", + " raycaster.far = distanceMax * inverseWorldScale;", + "", + " return this._doRecast(objectsLists, objects);", + " }", + "", + " /**", + " * @param objectsLists {Hashtable}", + " * @param objects {gdjs.RuntimeObject[]}", + " * @param originX {number}", + " * @param originY {number}", + " * @param originZ {number}", + " * @param targetX {number}", + " * @param targetY {number}", + " * @param targetZ {number}", + " */", + " recastBetweenPosition(", + " objectsLists,", + " objects,", + " originX,", + " originY,", + " originZ,", + " targetX,", + " targetY,", + " targetZ", + " ) {", + " if (objects.length === 0) {", + " return false;", + " }", + " const scene = objects[0].getRuntimeScene();", + " const inverseWorldScale = scene.getRenderer3DInverseWorldScale ? scene.getRenderer3DInverseWorldScale() : 1;", + " const raycaster = this.raycaster;", + " raycaster.ray.origin.set(", + " originX * inverseWorldScale,", + " -originY * inverseWorldScale,", + " originZ * inverseWorldScale,", + " );", + " const deltaX = targetX - originX;", + " const deltaY = targetY - originY;", + " const deltaZ = targetZ - originZ;", + " const deltaLength = Math.hypot(deltaX, deltaY, deltaZ);", + " raycaster.ray.direction.set(", + " deltaX / deltaLength,", + " -deltaY / deltaLength,", + " deltaZ / deltaLength,", + " );", + " raycaster.far = deltaLength * inverseWorldScale;", + "", + " return this._doRecast(objectsLists, objects);", + " }", + "", + " /**", + " * @param objectsLists {Hashtable}", + " * @param objects {gdjs.RuntimeObject[]}", + " */", + " _doRecast(objectsLists, objects) {", + " const scene = objects[0].getRuntimeScene();", + " const worldScale = scene.getRenderer3DWorldScale ? scene.getRenderer3DWorldScale() : 1;", + " const raycastResults = this.raycastResults;", + " let distanceMin = Number.MAX_VALUE;", + " /** @type {gdjs.RuntimeObject | null} */", + " let nearestObject = null;", + " /** @type {THREE.Object3D | null} */", + " let nearestThreeObject = null;", + " for (const object of objects) {", + " raycastResults.length = 0;", + " const threeObject = object.get3DRendererObject();", + " if (!threeObject) {", + " continue;", + " }", + " this.raycaster.intersectObject(threeObject, true, raycastResults);", + " if (raycastResults.length > 0 && raycastResults[0].distance < distanceMin) {", + " const raycastResult = raycastResults[0];", + " distanceMin = raycastResult.distance;", + " nearestObject = object;", + " this.lastDistance = raycastResult.distance * worldScale;", + " this.lastPositionX = raycastResult.point.x * worldScale;", + " this.lastPositionY = -raycastResult.point.y * worldScale;", + " this.lastPositionZ = raycastResult.point.z * worldScale;", + " this.lastNormal.copy(raycastResult.normal);", + " nearestThreeObject = raycastResult.object;", + " }", + " }", + " if (!nearestObject) {", + " return false;", + " }", + " this.lastNormal.transformDirection(nearestThreeObject.matrixWorld);", + " this.lastNormal.y = -this.lastNormal.y;", + " raycastResults.length = 0;", + " gdjs.evtTools.object.pickOnly(", + " objectsLists,", + " nearestObject", + " );", + " return true;", + " }", + "}", + "", + "gdjs.__raycaster3DExtension = {", + " Raycaster,", + " raycaster: new Raycaster(),", + "}", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Sends a ray from the given source position and angle, intersecting the closest object. The intersected object will become the only one taken into account.", + "fullName": "Raycast", + "functionType": "Condition", + "group": "Collision", + "name": "RaycastWithAngle", + "sentence": "Cast a ray from _PARAM2_; _PARAM3_; _PARAM4_ toward a rotation of _PARAM5_°, an elevation of _PARAM6_° and max distance of _PARAM7_ against _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "eventsFunctionContext.returnValue =", + " gdjs.__raycaster3DExtension.raycaster.recastWithAngle(", + " eventsFunctionContext.getObjectsLists(\"Object\"),", + " objects,", + " eventsFunctionContext.getArgument(\"OriginX\"),", + " eventsFunctionContext.getArgument(\"OriginY\"),", + " eventsFunctionContext.getArgument(\"OriginZ\"),", + " eventsFunctionContext.getArgument(\"RotationAngle\"),", + " eventsFunctionContext.getArgument(\"ElevationAngle\"),", + " eventsFunctionContext.getArgument(\"DistanceMax\")", + " );", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Objects to test against the ray", + "name": "Object", + "type": "objectList" + }, + { + "description": "Ray source X position", + "name": "OriginX", + "type": "expression" + }, + { + "description": "Ray source Y position", + "name": "OriginY", + "type": "expression" + }, + { + "description": "Ray source Z position", + "name": "OriginZ", + "type": "expression" + }, + { + "description": "Rotation angle (in degrees)", + "name": "RotationAngle", + "type": "expression" + }, + { + "description": "Elevation angle (in degrees)", + "name": "ElevationAngle", + "type": "expression" + }, + { + "description": "Ray maximum distance (in pixels)", + "name": "DistanceMax", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Sends a ray from the given source position to the final point, intersecting the closest object. The intersected object will become the only one taken into account.", + "fullName": "Raycast to a position", + "functionType": "Condition", + "group": "Collision", + "name": "RaycastBetweenPosition", + "sentence": "Cast a ray from _PARAM2_; _PARAM3_; _PARAM4_ to _PARAM5_; _PARAM6_; _PARAM7_ against _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "eventsFunctionContext.returnValue =", + " gdjs.__raycaster3DExtension.raycaster.recastBetweenPosition(", + " eventsFunctionContext.getObjectsLists(\"Object\"),", + " objects,", + " eventsFunctionContext.getArgument(\"OriginX\"),", + " eventsFunctionContext.getArgument(\"OriginY\"),", + " eventsFunctionContext.getArgument(\"OriginZ\"),", + " eventsFunctionContext.getArgument(\"TargetX\"),", + " eventsFunctionContext.getArgument(\"TargetY\"),", + " eventsFunctionContext.getArgument(\"TargetZ\")", + " );", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Objects to test against the ray", + "name": "Object", + "type": "objectList" + }, + { + "description": "Ray source X position", + "name": "OriginX", + "type": "expression" + }, + { + "description": "Ray source Y position", + "name": "OriginY", + "type": "expression" + }, + { + "description": "Ray source Z position", + "name": "OriginZ", + "type": "expression" + }, + { + "description": "Ray target X position", + "name": "TargetX", + "type": "expression" + }, + { + "description": "Ray target Y position", + "name": "TargetY", + "type": "expression" + }, + { + "description": "Ray target Z position", + "name": "TargetZ", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Sends a ray from the center of the camera, intersecting the closest object. The intersected object will become the only one taken into account.", + "fullName": "Raycast from camera center", + "functionType": "Condition", + "group": "Collision", + "name": "RaycastFromCameraCenter", + "sentence": "Cast a ray from the camera center to a maximum distance of _PARAM2_ against _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "eventsFunctionContext.returnValue =", + " gdjs.__raycaster3DExtension.raycaster.recastFromCamera(", + " eventsFunctionContext.getObjectsLists(\"Object\"),", + " objects,", + " 0.5,", + " 0.5,", + " eventsFunctionContext.getArgument(\"DistanceMax\")", + " );", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Objects to test against the ray", + "name": "Object", + "type": "objectList" + }, + { + "description": "Ray maximum distance (in pixels)", + "name": "DistanceMax", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Sends a ray from the given source point on the camera screen, intersecting the closest object. The intersected object will become the only one taken into account.", + "fullName": "Raycast from a camera point", + "functionType": "Condition", + "group": "Collision", + "name": "RaycastFromCameraPoint", + "sentence": "Cast a ray from the camera point _PARAM2_; _PARAM3_ to a maximum distance of _PARAM4_ against _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "eventsFunctionContext.returnValue =", + " gdjs.__raycaster3DExtension.raycaster.recastFromCamera(", + " eventsFunctionContext.getObjectsLists(\"Object\"),", + " objects,", + " eventsFunctionContext.getArgument(\"PointerX\"),", + " eventsFunctionContext.getArgument(\"PointerY\"),", + " eventsFunctionContext.getArgument(\"DistanceMax\")", + " );", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Objects to test against the ray", + "name": "Object", + "type": "objectList" + }, + { + "description": "X position on the screen (from 0 to 1)", + "name": "PointerX", + "type": "expression" + }, + { + "description": "Y position on the screen (from 0 to 1)", + "name": "PointerY", + "type": "expression" + }, + { + "description": "Ray maximum distance (in pixels)", + "name": "DistanceMax", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Sends a ray from the cursor on the camera screen, intersecting the closest object. The intersected object will become the only one taken into account.", + "fullName": "Raycast from cursor", + "functionType": "Condition", + "group": "Collision", + "name": "RaycastFromCameraCursor", + "sentence": "Cast a ray from the cursor on 2D layer: _PARAM2_ to a maximum distance of _PARAM3_ against _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { camera, input } = gdjs.evtTools;", + "", + "/** @type {string} */", + "//@ts-ignore", + "const layer2D = eventsFunctionContext.getArgument(\"Layer2D\");", + "", + "const cameraMinX = camera.getCameraBorderLeft(runtimeScene, layer2D, 0);", + "const cameraMaxX = camera.getCameraBorderRight(runtimeScene, layer2D, 0);", + "const cameraMinY = camera.getCameraBorderTop(runtimeScene, layer2D, 0);", + "const cameraMaxY = camera.getCameraBorderBottom(runtimeScene, layer2D, 0);", + "", + "const cursorX = input.getCursorX(runtimeScene, layer2D, 0);", + "const cursorY = input.getCursorY(runtimeScene, layer2D, 0);", + "", + "eventsFunctionContext.returnValue =", + " gdjs.__raycaster3DExtension.raycaster.recastFromCamera(", + " eventsFunctionContext.getObjectsLists(\"Object\"),", + " objects,", + " (cursorX - cameraMinX) / (cameraMaxX - cameraMinX),", + " (cursorY - cameraMinY) / (cameraMaxY - cameraMinY),", + " eventsFunctionContext.getArgument(\"DistanceMax\")", + " );", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Objects to test against the ray", + "name": "Object", + "type": "objectList" + }, + { + "description": "2D layer", + "name": "Layer2D", + "type": "layer" + }, + { + "description": "Ray maximum distance (in pixels)", + "name": "DistanceMax", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the last recast intersection distance.", + "fullName": "Last recast distance", + "functionType": "ExpressionAndCondition", + "name": "Distance", + "sentence": "the last recast intersection distance", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "eventsFunctionContext.returnValue = gdjs.__raycaster3DExtension.raycaster.lastDistance;", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [], + "objectGroups": [] + }, + { + "description": "Return the last recast intersection position on X axis.", + "fullName": "Last recast X intersection", + "functionType": "Expression", + "name": "IntersectionX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "eventsFunctionContext.returnValue = gdjs.__raycaster3DExtension.raycaster.lastPositionX;", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [], + "objectGroups": [] + }, + { + "description": "Return the last recast intersection position on Y axis.", + "fullName": "Last recast Y intersection", + "functionType": "Expression", + "name": "IntersectionY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "eventsFunctionContext.returnValue = gdjs.__raycaster3DExtension.raycaster.lastPositionY;", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [], + "objectGroups": [] + }, + { + "description": "Return the last recast intersection position on Z axis.", + "fullName": "Last recast Z intersection", + "functionType": "Expression", + "name": "IntersectionZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "eventsFunctionContext.returnValue = gdjs.__raycaster3DExtension.raycaster.lastPositionZ;", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [], + "objectGroups": [] + }, + { + "description": "Return the last recast intersection normal on X axis.", + "fullName": "Last recast X normal", + "functionType": "Expression", + "name": "NormalX", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "eventsFunctionContext.returnValue = gdjs.__raycaster3DExtension.raycaster.lastNormal.x;", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [], + "objectGroups": [] + }, + { + "description": "Return the last recast intersection normal on X axis.", + "fullName": "Last recast X normal", + "functionType": "Expression", + "name": "NormalY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "eventsFunctionContext.returnValue = gdjs.__raycaster3DExtension.raycaster.lastNormal.y;", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [], + "objectGroups": [] + }, + { + "description": "Return the last recast intersection normal on Z axis.", + "fullName": "Last recast Z normal", + "functionType": "Expression", + "name": "NormalZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "eventsFunctionContext.returnValue = gdjs.__raycaster3DExtension.raycaster.lastNormal.z;", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onFirstSceneLoaded" + }, + { + "functionName": "DefineHelperClasses" + }, + { + "folderName": "Collision", + "children": [ + { + "functionName": "RaycastWithAngle" + }, + { + "functionName": "RaycastBetweenPosition" + }, + { + "functionName": "RaycastFromCameraCenter" + }, + { + "functionName": "RaycastFromCameraPoint" + }, + { + "functionName": "RaycastFromCameraCursor" + } + ] + }, + { + "functionName": "Distance" + }, + { + "functionName": "IntersectionX" + }, + { + "functionName": "IntersectionY" + }, + { + "functionName": "IntersectionZ" + }, + { + "functionName": "NormalX" + }, + { + "functionName": "NormalY" + }, + { + "functionName": "NormalZ" + } + ] + }, + "eventsBasedBehaviors": [], + "eventsBasedObjects": [] + }, + { + "author": "", + "category": "Visual effect", + "dimension": "", + "extensionNamespace": "", + "fullName": "Advanced 3D tween", + "gdevelopVersion": ">=5.5.222", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXRlbm5pcy1iYWxsIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTEyLDJDMTQuNSwyIDE2Ljc1LDIuOSAxOC41LDQuNEMxNi4zNiw2LjIzIDE1LDguOTYgMTUsMTJDMTUsMTUuMDQgMTYuMzYsMTcuNzcgMTguNSwxOS42QzE2Ljc1LDIxLjEgMTQuNSwyMiAxMiwyMkM5LjUsMjIgNy4yNSwyMS4xIDUuNSwxOS42QzcuNjQsMTcuNzcgOSwxNS4wNCA5LDEyQzksOC45NiA3LjY0LDYuMjMgNS41LDQuNEM3LjI1LDIuOSA5LjUsMiAxMiwyTTIyLDEyQzIyLDE0LjMyIDIxLjIxLDE2LjQ1IDE5Ljg4LDE4LjE1QzE4LjEyLDE2LjY4IDE3LDE0LjQ3IDE3LDEyQzE3LDkuNTMgMTguMTIsNy4zMiAxOS44OCw1Ljg1QzIxLjIxLDcuNTUgMjIsOS42OCAyMiwxMk0yLDEyQzIsOS42OCAyLjc5LDcuNTUgNC4xMiw1Ljg1QzUuODgsNy4zMiA3LDkuNTMgNywxMkM3LDE0LjQ3IDUuODgsMTYuNjggNC4xMiwxOC4xNUMyLjc5LDE2LjQ1IDIsMTQuMzIgMiwxMloiIC8+PC9zdmc+", + "name": "Tween3D", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/30bc03f9cf5f2f42960246a605352a6937ae6b603f0177396a83193849a4724a_tennis-ball.svg", + "shortDescription": "Smoothly squash, strentch, tint and wobble 3D models.", + "version": "0.3.1", + "description": [ + "Change 3D models proportions over time to make smooth animations like wobbling.", + "", + "It's used in this example:", + "* a 3D Bomberman-like game ([open the project online](https://editor.gdevelop.io/?project=example://3d-bomber-bunny))" + ], + "origin": { + "identifier": "Tween3D", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d", + "tween", + "smooth", + "interpolation", + "wobble", + "color" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2", + "dt0tRnf2kHWJnjkrpnzTzNj9Yc63" + ], + "changelog": [ + { + "version": "0.1.0", + "breaking": "- The Tween behavior is now required by the 3D Tween behavior. Add the Tween behavior on any object that uses the 3D Tween behavior." + } + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onFirstSceneLoaded", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "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.__tween3DExtension) {", + " return;", + "}", + "", + "class Tint {", + " /**", + " * Materials by material `id`.", + " * @type {Map}", + " */", + " materials;", + " usageCount = 0;", + "", + " /**", + " * @param materials {Map}", + " */", + " constructor(materials) {", + " this.materials = materials;", + " }", + "", + " /**", + " * @param object {gdjs.RuntimeObject3D}", + " */", + " applyTo(object) {", + " object.get3DRendererObject().traverse((mesh) => {", + " if (mesh.material) {", + " const material = mesh.material;", + " const originalId = material.originalId || material.id;", + " const tintedMaterial = this.materials.get(originalId);", + " if (tintedMaterial) {", + " mesh.material = tintedMaterial;", + " }", + " }", + " });", + " }", + "", + " /**", + " * @param tintColor {number}", + " * @param originalTint {Tint}", + " */", + " setColor(tintColor, originalTint) {", + " const originalMaterials = originalTint.materials;", + " for (const [materialId, material] of this.materials) {", + " const originalMaterial = originalMaterials.get(materialId);", + " if (!originalMaterial) {", + " // A new material may have been changed by another extension.", + " continue;", + " }", + " const originalColor = originalMaterial.color;", + "", + " const color = material.color;", + " color.setHex(tintColor);", + " color.r *= originalColor.r;", + " color.g *= originalColor.g;", + " color.b *= originalColor.b;", + " }", + " }", + "", + " dispose() {", + " for (const material of this.materials.values()) {", + " material.dispose();", + " }", + " }", + "}", + "", + "class TintManager {", + " /**", + " * Tints by color hex.", + " * @type {Map}", + " */", + " tints = new Map();", + " /**", + " * Materials by material `id`.", + " * @type {Tint}", + " */", + " originalTint;", + " /**", + " * @type {Array}", + " */", + " recycledTints = [];", + "", + " /**", + " * @param {gdjs.RuntimeObject3D} object", + " */", + " constructor(object) {", + " const materials = new Map();", + " object.get3DRendererObject().traverse((mesh) => {", + " if (mesh.material && mesh.material.color) {", + " const material = mesh.material;", + " materials.set(material.id, material);", + " }", + " });", + " this.originalTint = new Tint(materials);", + " }", + "", + " /**", + " * @param {gdjs.RuntimeObject3D} object", + " * @param {number} color", + " */", + " applyTint(object, color) {", + " const previousColor = this.getTint(object);", + " if (color === previousColor) {", + " return;", + " }", + " if (previousColor !== 0xffffff) {", + " const previousTint = this.tints.get(previousColor);", + " previousTint.usageCount--;", + " if (previousTint.usageCount === 0) {", + " this.tints.delete(previousColor);", + " this.recycledTints.push(previousTint);", + " }", + " }", + " let tint = color === 0xffffff ? this.originalTint : this.tints.get(color);", + " if (!tint) {", + " tint = this.createTint(object, color);", + " this.tints.set(color, tint);", + " }", + " tint.applyTo(object);", + " tint.usageCount++;", + " object.__tint3DExtension_tint = color;", + " }", + "", + " /**", + " * @param {gdjs.RuntimeObject3D} object", + " * @return {number}", + " */", + " getTint(object) {", + " return object.__tint3DExtension_tint === undefined ? 0xffffff : object.__tint3DExtension_tint;", + " }", + "", + " /**", + " * @param {gdjs.RuntimeObject3D} object", + " * @param {number} color", + " */", + " createTint(object, color) {", + " let tint;", + " if (this.recycledTints.length > 0) {", + " tint = this.recycledTints.pop();", + " tint.usageCount = 0;", + " }", + " else {", + " const materials = new Map();", + " for (const [originalId, material] of this.originalTint.materials) {", + " const tintedMaterial = material.clone();", + " tintedMaterial.originalId = originalId;", + " materials.set(originalId, tintedMaterial);", + " }", + " tint = new Tint(materials);", + " }", + " tint.setColor(color, this.originalTint);", + " return tint;", + " }", + "", + " dispose() {", + " for (const tint of this.tints.values()) {", + " tint.dispose();", + " }", + " this.tints.clear();", + " for (const tint of this.recycledTints) {", + " tint.dispose();", + " }", + " this.recycledTints.length = 0;", + " }", + "}", + "", + "gdjs.registerObjectDeletedFromSceneCallback(function (runtimeScene, deletedObject) {", + " const extension = runtimeScene.__tween3DExtension;", + " if (!extension) {", + " return;", + " }", + " const managers = extension.tintManagers;", + " const manager = managers.get(deletedObject.getName());", + " if (manager) {", + " manager.applyTint(deletedObject, 0xffffff);", + " }", + "});", + "", + "", + "/**", + " * @param {number} r", + " * @param {number} g", + " * @param {number} b", + " * @return {number[]}", + " */", + "const rgbToHsl = (r, g, b) => {", + " r /= 255;", + " g /= 255;", + " b /= 255;", + " let v = Math.max(r, g, b),", + " c = v - Math.min(r, g, b),", + " f = 1 - Math.abs(v + v - c - 1);", + " let h =", + " c &&", + " (v === r ? (g - b) / c : v === g ? 2 + (b - r) / c : 4 + (r - g) / c);", + " return [", + " Math.round(60 * (h < 0 ? h + 6 : h)),", + " Math.round((f ? c / f : 0) * 100),", + " Math.round(((v + v - c) / 2) * 100),", + " ];", + "};", + "", + "/**", + " * @param {number} h", + " * @param {number} s", + " * @param {number} l", + " * @return {number[]}", + " */", + "const hslToRgb = (h, s, l) => {", + " h = h %= 360;", + " if (h < 0) {", + " h += 360;", + " }", + " s = s / 100;", + " l = l / 100;", + " const a = s * Math.min(l, 1 - l);", + " const f = (n = 0, k = (n + h / 30) % 12) =>", + " l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);", + " return [", + " Math.round(f(0) * 255),", + " Math.round(f(8) * 255),", + " Math.round(f(4) * 255),", + " ];", + "};", + "", + "gdjs.__tween3DExtension = {", + " TintManager,", + " rgbToHsl,", + " hslToRgb,", + "};", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Change the tint of an object. The default color is white.", + "fullName": "Tint color", + "functionType": "Action", + "group": "Color", + "name": "SetTint", + "sentence": "Change tint of _PARAM1_ to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const color = gdjs.rgbOrHexStringToNumber(eventsFunctionContext.getArgument(\"Color\") || \"255; 255;255\");", + "", + "if (!runtimeScene.__tween3DExtension) {", + " runtimeScene.__tween3DExtension = {", + " tintManagers: new Map()", + " };", + "}", + "const { tintManagers } = runtimeScene.__tween3DExtension;", + "for (const object of objects) {", + " let manager = tintManagers.get(object.getName());", + " if (!manager) {", + " const { TintManager } = gdjs.__tween3DExtension;", + " manager = new TintManager(object);", + " tintManagers.set(object.getName(), manager);", + " }", + " manager.applyTint(object, color);", + "}", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "Object capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Color", + "name": "Color", + "type": "color" + } + ], + "objectGroups": [] + }, + { + "description": "Change the tint of an object.", + "fullName": "Tint color (by number)", + "functionType": "Action", + "group": "Color", + "name": "SetTintNumber", + "private": true, + "sentence": "Change tint of _PARAM1_ to _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const color = eventsFunctionContext.getArgument(\"Color\");", + "", + "if (!runtimeScene.__tween3DExtension) {", + " runtimeScene.__tween3DExtension = {", + " tintManagers: new Map()", + " };", + "}", + "const { tintManagers } = runtimeScene.__tween3DExtension;", + "for (const object of objects) {", + " let manager = tintManagers.get(object.getName());", + " if (!manager) {", + " const { TintManager } = gdjs.__tween3DExtension;", + " manager = new TintManager(object);", + " tintManagers.set(object.getName(), manager);", + " }", + " manager.applyTint(object, color);", + "}", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "Object capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Color", + "name": "Color", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Blend two RGB colors by applying a weighted mean.", + "fullName": "Blend RGB colors", + "functionType": "Expression", + "name": "RgbMean", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { rgbToHsl, hslToRgb } = gdjs.__tween3DExtension;", + "", + "const [r1, g1, b1] = gdjs.hexNumberToRGBArray(eventsFunctionContext.getArgument(\"Color1\"));", + "const [r2, g2, b2] = gdjs.hexNumberToRGBArray(eventsFunctionContext.getArgument(\"Color2\"));", + "const ratio = eventsFunctionContext.getArgument(\"Ratio\");", + "", + "const [h1, s1, l1] = rgbToHsl(r1, g1, b1);", + "const [h2, s2, l2] = rgbToHsl(r2, g2, b2);", + "", + "const h = h1 + ratio * (h2 - h1);", + "const s = s1 + ratio * (s2 - s1);", + "const l = l1 + ratio * (l2 - l1);", + "", + "const [r, g, b] = hslToRgb(h, s, l);", + "", + "eventsFunctionContext.returnValue = gdjs.rgbToHexNumber(r, g, b);", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "First RGB color", + "name": "Color1", + "type": "expression" + }, + { + "description": "Second RGB color", + "name": "Color2", + "type": "expression" + }, + { + "description": "Ratio", + "longDescription": "Range: 0 to 1, where 0 gives the first color and 1 gives the second color", + "name": "Ratio", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "fullName": "RGB to number", + "functionType": "Expression", + "name": "RgbToNumber", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "eventsFunctionContext.returnValue = gdjs.rgbOrHexStringToNumber(eventsFunctionContext.getArgument(\"Color\"));", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Color", + "name": "Color", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onSceneUnloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (!runtimeScene.__tween3DExtension) {", + " return;", + "}", + "const { tintManagers } = runtimeScene.__tween3DExtension;", + "", + "for (const manager of tintManagers.values()) {", + " manager.dispose();", + "}", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "the stretch on Z axis. The object volume is conserved. A stretch of 2 makes the object appear 2 times longer on Z in proportion to the dimensions on X and Y.", + "fullName": "Stretch on Z axis", + "functionType": "ExpressionAndCondition", + "name": "StretchZ", + "private": true, + "sentence": "the stretch on Z axis of _PARAM1_ (it no longer does anything, use the one from 3D tween behavior instead)", + "events": [], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Scalable capacity", + "name": "Scale", + "supplementaryInformation": "ScalableCapability::ScalableBehavior", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Stretch on Z axis", + "functionType": "ActionWithOperator", + "getterName": "StretchZ", + "name": "SetStretchZ", + "private": true, + "sentence": "", + "events": [], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Scalable capacity", + "name": "Scale", + "supplementaryInformation": "ScalableCapability::ScalableBehavior", + "type": "behavior" + }, + { + "description": "Stretch ratio", + "name": "StretchRatio", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the stretch on Y axis. The object volume is conserved. A stretch of 2 makes the object appear 2 times longer on Y in proportion to the dimensions on X and Z.", + "fullName": "Stretch on Y axis", + "functionType": "ExpressionAndCondition", + "name": "StretchY", + "private": true, + "sentence": "the stretch on Y axis of _PARAM1_ (it no longer does anything, use the one from 3D tween behavior instead)", + "events": [], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Scalable capacity", + "name": "Scale", + "supplementaryInformation": "ScalableCapability::ScalableBehavior", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Stretch on Z axis", + "functionType": "ActionWithOperator", + "getterName": "StretchY", + "name": "SetStretchY", + "private": true, + "sentence": "", + "events": [], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Scalable capacity", + "name": "Scale", + "supplementaryInformation": "ScalableCapability::ScalableBehavior", + "type": "behavior" + }, + { + "description": "Stretch ratio", + "name": "StretchRatio", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the scale volume.", + "fullName": "Scale volume", + "functionType": "ExpressionAndCondition", + "name": "ScaleVolume", + "private": true, + "sentence": "the scale volume of _PARAM1_ (it no longer does anything, use the one from 3D tween behavior instead)", + "events": [], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Scalable capacity", + "name": "Scale", + "supplementaryInformation": "ScalableCapability::ScalableBehavior", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Stretch on Z axis", + "functionType": "ActionWithOperator", + "getterName": "ScaleVolume", + "name": "SetScaleVolume", + "private": true, + "sentence": "", + "events": [], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + }, + { + "description": "Scalable capacity", + "name": "Scale", + "supplementaryInformation": "ScalableCapability::ScalableBehavior", + "type": "behavior" + }, + { + "description": "Stretch ratio", + "name": "StretchRatio", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Exponentially tween bewteen 2 values according to an easing function.", + "fullName": "Ease exponentially", + "functionType": "Expression", + "name": "EaseExp", + "private": true, + "sentence": "the exponent-tweened value of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "exp(Tween::Ease(Easing, log(InitialValue), log(TargetedValue), Weighting))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Easing", + "name": "Easing", + "type": "string" + }, + { + "description": "Initial value", + "name": "InitialValue", + "type": "expression" + }, + { + "description": "Targeted value", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Weighting", + "name": "Weighting", + "type": "expression" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onFirstSceneLoaded" + }, + { + "functionName": "DefineHelperClasses" + }, + { + "folderName": "Color", + "children": [ + { + "functionName": "SetTint" + }, + { + "functionName": "SetTintNumber" + } + ] + }, + { + "functionName": "RgbMean" + }, + { + "functionName": "RgbToNumber" + }, + { + "functionName": "onSceneUnloading" + }, + { + "functionName": "StretchZ" + }, + { + "functionName": "SetStretchZ" + }, + { + "functionName": "StretchY" + }, + { + "functionName": "SetStretchY" + }, + { + "functionName": "ScaleVolume" + }, + { + "functionName": "SetScaleVolume" + }, + { + "functionName": "EaseExp" + } + ] + }, + "eventsBasedBehaviors": [ + { + "description": "Smoothly squash, strentch and tint 3D models.", + "fullName": "Advanced 3D tween", + "helpPath": "", + "iconUrl": "", + "name": "Tween3D", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the stretch on Z axis. The object volume is conserved. A stretch of 2 makes the object appear 2 times longer on Z in proportion to the dimensions on X and Y.", + "fullName": "Stretch on Z axis", + "functionType": "ExpressionAndCondition", + "group": "Size", + "name": "StretchZ", + "sentence": "the stretch on Z axis", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Object3D::ScaleZ() / sqrt(Object.Scale::X() * Object.Scale::Y())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Stretch on Z axis", + "functionType": "ActionWithOperator", + "getterName": "StretchZ", + "group": "Size", + "name": "SetStretchZ", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CompareArgumentAsNumber" + }, + "parameters": [ + "\"Value\"", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "ModVarScene" + }, + "parameters": [ + "_Tween3D.Volume", + "=", + "Object.Behavior::ScaleVolume()" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetScaleZ" + }, + "parameters": [ + "Object", + "Object3D", + "=", + "pow(Variable(_Tween3D.Volume) * Value * Value, 1/3)" + ] + }, + { + "type": { + "value": "ModVarScene" + }, + "parameters": [ + "_Tween3D.SurfaceRatio", + "=", + "sqrt((Variable(_Tween3D.Volume) / Object.Object3D::ScaleZ()) / (Object.Scale::X() * Object.Scale::Y()))" + ] + }, + { + "type": { + "value": "ScalableCapability::ScalableBehavior::SetX" + }, + "parameters": [ + "Object", + "Scale", + "*", + "Variable(_Tween3D.SurfaceRatio)" + ] + }, + { + "type": { + "value": "ScalableCapability::ScalableBehavior::SetY" + }, + "parameters": [ + "Object", + "Scale", + "*", + "Variable(_Tween3D.SurfaceRatio)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Scalable capacity", + "name": "Scale", + "supplementaryInformation": "ScalableCapability::ScalableBehavior", + "type": "behavior" + }, + { + "description": "Stretch ratio", + "name": "StretchRatio", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the stretch on Y axis. The object volume is conserved. A stretch of 2 makes the object appear 2 times longer on Y in proportion to the dimensions on X and Z.", + "fullName": "Stretch on Y axis", + "functionType": "ExpressionAndCondition", + "group": "Size", + "name": "StretchY", + "sentence": "the stretch on Y axis", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Scale::Y() / sqrt(Object.Scale::X() * Object.Object3D::ScaleZ())" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Stretch on Z axis", + "functionType": "ActionWithOperator", + "getterName": "StretchY", + "group": "Size", + "name": "SetStretchY", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CompareArgumentAsNumber" + }, + "parameters": [ + "\"Value\"", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "ModVarScene" + }, + "parameters": [ + "_Tween3D.Volume", + "=", + "Object.Behavior::ScaleVolume()" + ] + }, + { + "type": { + "value": "ScalableCapability::ScalableBehavior::SetY" + }, + "parameters": [ + "Object", + "Scale", + "=", + "pow(Variable(_Tween3D.Volume) * Value * Value, 1/3)" + ] + }, + { + "type": { + "value": "ModVarScene" + }, + "parameters": [ + "_Tween3D.SurfaceRatio", + "=", + "sqrt((Variable(_Tween3D.Volume) / Object.Object3D::ScaleZ()) / (Object.Scale::X() * Object.Scale::Y()))" + ] + }, + { + "type": { + "value": "ScalableCapability::ScalableBehavior::SetX" + }, + "parameters": [ + "Object", + "Scale", + "*", + "Variable(_Tween3D.SurfaceRatio)" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetScaleZ" + }, + "parameters": [ + "Object", + "Object3D", + "*", + "Variable(_Tween3D.SurfaceRatio)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Scalable capacity", + "name": "Scale", + "supplementaryInformation": "ScalableCapability::ScalableBehavior", + "type": "behavior" + }, + { + "description": "Stretch ratio", + "name": "StretchRatio", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the scale volume.", + "fullName": "Scale volume", + "functionType": "ExpressionAndCondition", + "group": "Size", + "name": "ScaleVolume", + "sentence": "the scale volume", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Scale::X() * Object.Scale::Y() * Object.Object3D::ScaleZ()" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Stretch on Z axis", + "functionType": "ActionWithOperator", + "getterName": "ScaleVolume", + "group": "Size", + "name": "SetScaleVolume", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CompareArgumentAsNumber" + }, + "parameters": [ + "\"Value\"", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "ModVarScene" + }, + "parameters": [ + "_Tween3D.Ratio", + "=", + "pow(Value / Object.Behavior::ScaleVolume(), 1 / 3)" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetScaleZ" + }, + "parameters": [ + "Object", + "Object3D", + "*", + "Variable(_Tween3D.Ratio)" + ] + }, + { + "type": { + "value": "ScalableCapability::ScalableBehavior::SetY" + }, + "parameters": [ + "Object", + "Scale", + "*", + "Variable(_Tween3D.Ratio)" + ] + }, + { + "type": { + "value": "ScalableCapability::ScalableBehavior::SetX" + }, + "parameters": [ + "Object", + "Scale", + "*", + "Variable(_Tween3D.Ratio)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Scalable capacity", + "name": "Scale", + "supplementaryInformation": "ScalableCapability::ScalableBehavior", + "type": "behavior" + }, + { + "description": "Stretch ratio", + "name": "StretchRatio", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Return the color of a 3d object.", + "fullName": "Color of a 3d object", + "functionType": "Expression", + "group": "Color", + "name": "Tint", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "if (!runtimeScene.__tween3DExtension) {", + " runtimeScene.__tween3DExtension = {", + " tintManagers: new Map()", + " };", + "}", + "const { tintManagers } = runtimeScene.__tween3DExtension;", + "if (objects.length === 0) {", + " eventsFunctionContext.returnValue = 0xffffff;", + "}", + "else {", + " const object = objects[0];", + " let manager = tintManagers.get(object.getName());", + " if (!manager) {", + " const { TintManager } = gdjs.__tween3DExtension;", + " manager = new TintManager(object);", + " tintManagers.set(object.getName(), manager);", + " }", + " eventsFunctionContext.returnValue = manager.getTint(object);", + "}", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Tweens an object stretch Z from its current value to a new one. The stretch can never be 0 or less.", + "fullName": "Tween stretch Z", + "functionType": "Action", + "group": "Size", + "name": "TweenStretchZ", + "sentence": "Tween the stretch Z of _PARAM0_ to _PARAM3_ over _PARAM4_ seconds with easing _PARAM5_ as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::StartTween" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "TargetedValue", + "Duration", + "Easing", + "\"StretchZ\"", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Stretch on Z axis", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + } + ], + "objectGroups": [] + }, + { + "description": "Tweens an object stretch Y from its current value to a new one. The stretch can never be 0 or less.", + "fullName": "Tween stretch Y", + "functionType": "Action", + "group": "Size", + "name": "TweenStretchY", + "sentence": "Tween the stretch Y of _PARAM0_ to _PARAM3_ over _PARAM4_ seconds with easing _PARAM5_ as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::StartTween" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "TargetedValue", + "Duration", + "Easing", + "\"StretchY\"", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Stretch on Y axis", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + } + ], + "objectGroups": [] + }, + { + "description": "Tweens an object scale volume from its current value to a new one. The scale volume can never be 0 or less.", + "fullName": "Tween scale volume", + "functionType": "Action", + "group": "Size", + "name": "TweenScaleVolume", + "sentence": "Tween the scale volume of _PARAM0_ to _PARAM3_ over _PARAM4_ seconds with easing _PARAM5_ as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::StartTween" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "TargetedValue", + "Duration", + "Easing", + "\"ScaleVolume\"", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Scale volume", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + } + ], + "objectGroups": [] + }, + { + "description": "Tweens the object color from its current value to a new one.", + "fullName": "Tween object color", + "functionType": "Action", + "group": "Color", + "name": "TweenTint", + "sentence": "Tween the color of _PARAM0_ to _PARAM3_ over _PARAM4_ seconds with easing _PARAM5_ as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::StartTween" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "Tween3D::RgbToNumber(TargetedValue)", + "Duration", + "Easing", + "\"Tint\"", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Color", + "name": "TargetedValue", + "type": "color" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + } + ], + "objectGroups": [] + }, + { + "description": "Tweens an object Z position from its current value to a new one.", + "fullName": "Tween Z position (deprecated)", + "functionType": "Action", + "group": "Position", + "name": "TweenPositionZ", + "private": true, + "sentence": "Tween the Z position of object _PARAM0_ to _PARAM3_ over _PARAM4_ seconds with easing _PARAM5_ as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween::AddObjectPositionZTween2" + }, + "parameters": [ + "Object", + "Tween", + "Object3D", + "TweenIdentifier", + "TargetedValue", + "Easing", + "Duration", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Rotation X in degree", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + } + ], + "objectGroups": [] + }, + { + "description": "Tweens an object depth from its current value to a new one.", + "fullName": "Tween depth (Z size) (deprecated)", + "functionType": "Action", + "group": "Position", + "name": "TweenDepth", + "private": true, + "sentence": "Tween the depth of object _PARAM0_ to _PARAM3_ over _PARAM4_ seconds with easing _PARAM5_ as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween::AddObjectDepthTween2" + }, + "parameters": [ + "Object", + "Tween", + "Object3D", + "TweenIdentifier", + "TargetedValue", + "Easing", + "Duration", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Rotation X in degree", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + } + ], + "objectGroups": [] + }, + { + "description": "Tweens an object X rotation from its current value to a new one.", + "fullName": "Tween X rotation (deprecated)", + "functionType": "Action", + "group": "Angle", + "name": "TweenRotationX", + "private": true, + "sentence": "Tween the X rotation of object _PARAM0_ to _PARAM3_ over _PARAM4_ seconds with easing _PARAM5_ as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween::TweenBehavior::AddObjectRotationXTween" + }, + "parameters": [ + "Object", + "Tween", + "Object3D", + "TweenIdentifier", + "TargetedValue", + "Easing", + "Duration", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Rotation X in degree", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + } + ], + "objectGroups": [] + }, + { + "description": "Tweens an object Y rotation from its current value to a new one.", + "fullName": "Tween rotation Y (deprecated)", + "functionType": "Action", + "group": "Angle", + "name": "TweenRotationY", + "private": true, + "sentence": "Tween the Y rotation of object _PARAM0_ to _PARAM3_ over _PARAM4_ seconds with easing _PARAM5_ as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween::TweenBehavior::AddObjectRotationYTween" + }, + "parameters": [ + "Object", + "Tween", + "Object3D", + "TweenIdentifier", + "TargetedValue", + "Easing", + "Duration", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Rotation Y in degree", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ModVarScene" + }, + "parameters": [ + "_Tween3D.Index", + "=", + "0" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Repeat", + "repeatExpression": "Object.VariableChildCount(_Tween3D.Tweens)", + "conditions": [], + "actions": [ + { + "type": { + "value": "ModVarSceneTxt" + }, + "parameters": [ + "_Tween3D.Identifier", + "=", + "Object.Behavior::TweenIdentifier(Variable(_Tween3D.Index))" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::IsPlaying" + }, + "parameters": [ + "Object", + "Behavior", + "VariableString(_Tween3D.Identifier)", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetTime" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "min(Object.Behavior::Time(VariableString(_Tween3D.Identifier)) + TimeDelta() * LayerTimeScale(Object.Layer()), Object.Behavior::Duration(VariableString(_Tween3D.Identifier)))", + "VariableString(_Tween3D.Identifier)", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Type" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "\"StretchZ\"", + "VariableString(_Tween3D.Identifier)", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetStretchZ" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Object.Behavior::ExponentTweenedValue(VariableString(_Tween3D.Identifier))", + "Object3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Type" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "\"StretchY\"", + "VariableString(_Tween3D.Identifier)", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetStretchY" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Object.Behavior::ExponentTweenedValue(VariableString(_Tween3D.Identifier))", + "Object3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Type" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "\"ScaleVolume\"", + "VariableString(_Tween3D.Identifier)", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetScaleVolume" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Object.Behavior::ExponentTweenedValue(VariableString(_Tween3D.Identifier))", + "Object3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Type" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "\"Tint\"", + "VariableString(_Tween3D.Identifier)", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::SetTintNumber" + }, + "parameters": [ + "Object", + "Object", + "Object3D", + "Object.Behavior::TweenedColor(VariableString(_Tween3D.Identifier))", + "" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ModVarScene" + }, + "parameters": [ + "_Tween3D.Index", + "+", + "1" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the tweened value", + "fullName": "Tweened value", + "functionType": "ExpressionAndCondition", + "name": "TweenedValue", + "private": true, + "sentence": "the value of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Tween::Ease(Object.Behavior::Easing(VariableString(_Tween3D.Identifier)), Object.Behavior::InitialValue(VariableString(_Tween3D.Identifier)), Object.Behavior::TargetedValue(VariableString(_Tween3D.Identifier)), Object.Behavior::Progress(VariableString(_Tween3D.Identifier)))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "the tweened color", + "fullName": "Tweened color", + "functionType": "ExpressionAndCondition", + "name": "TweenedColor", + "private": true, + "sentence": "the color value of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Tween3D::RgbMean(Object.Behavior::InitialValue(VariableString(_Tween3D.Identifier)), Object.Behavior::TargetedValue(VariableString(_Tween3D.Identifier)), \nTween::Ease(Object.Behavior::Easing(VariableString(_Tween3D.Identifier)), 0, 1, Object.Behavior::Progress(VariableString(_Tween3D.Identifier))))\n\n" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "the exponent-tweened value", + "fullName": "Tweened value (exponent)", + "functionType": "ExpressionAndCondition", + "name": "ExponentTweenedValue", + "private": true, + "sentence": "the exponent-tweened value of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Tween3D::EaseExp(Object.Behavior::Easing(VariableString(_Tween3D.Identifier)), Object.Behavior::InitialValue(VariableString(_Tween3D.Identifier)), Object.Behavior::TargetedValue(VariableString(_Tween3D.Identifier)), Object.Behavior::Progress(VariableString(_Tween3D.Identifier)))" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "fullName": "Start tween", + "functionType": "Action", + "name": "StartTween", + "private": true, + "sentence": "Tween the _PARAM6_ of object _PARAM0_ to _PARAM3_ over _PARAM4_ seconds with _PARAM5_ easing as _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "Tween3D::Tween3D::Exists" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::InvalidateTweenIdentifiers" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetTime" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "0", + "TweenIdentifier", + "" + ] + }, + { + "type": { + "value": "Tween3D::Tween3D::SetType" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Type", + "TweenIdentifier", + "" + ] + }, + { + "type": { + "value": "Tween3D::Tween3D::SetDuration" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Duration", + "TweenIdentifier", + "" + ] + }, + { + "type": { + "value": "Tween3D::Tween3D::SetEasing" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Easing", + "TweenIdentifier", + "" + ] + }, + { + "type": { + "value": "Tween3D::Tween3D::SetTargetedValue" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "TargetedValue", + "TweenIdentifier", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Type", + "=", + "\"StretchZ\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetInitialValue" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Object.Behavior::StretchZ()", + "TweenIdentifier", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Type", + "=", + "\"StretchY\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetInitialValue" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Object.Behavior::StretchY()", + "TweenIdentifier", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Type", + "=", + "\"ScaleVolume\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetInitialValue" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Object.Behavior::ScaleVolume()", + "TweenIdentifier", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Type", + "=", + "\"Tint\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetInitialValue" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Object.Behavior::Tint()", + "TweenIdentifier", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Stretch", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + }, + { + "description": "Type", + "name": "Type", + "type": "string" + } + ], + "objectGroups": [] + }, + { + "description": "the tween type", + "fullName": "Type", + "functionType": "ExpressionAndCondition", + "name": "Type", + "private": true, + "sentence": "the type of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "Object.VariableString(_Tween3D.Tweens[TweenIdentifier].Type)" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Type", + "name": "SetType", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ModVarObjetTxt" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens[TweenIdentifier].Type", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "the elapsed time", + "fullName": "Time", + "functionType": "ExpressionAndCondition", + "name": "Time", + "private": true, + "sentence": "the elapsed time of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Variable(_Tween3D.Tweens[TweenIdentifier].Time)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Time", + "name": "SetTime", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ModVarObjet" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens[TweenIdentifier].Time", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "the tween duration", + "fullName": "Duration", + "functionType": "ExpressionAndCondition", + "name": "Duration", + "private": true, + "sentence": "the duration of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Variable(_Tween3D.Tweens[TweenIdentifier].Duration)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Duration", + "name": "SetDuration", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ModVarObjet" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens[TweenIdentifier].Duration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "the tween easing", + "fullName": "Easing", + "functionType": "ExpressionAndCondition", + "name": "Easing", + "private": true, + "sentence": "the easing of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "Object.VariableString(_Tween3D.Tweens[TweenIdentifier].Easing)" + ] + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Easing", + "name": "SetEasing", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ModVarObjetTxt" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens[TweenIdentifier].Easing", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "the tween initial value", + "fullName": "Initial value", + "functionType": "ExpressionAndCondition", + "name": "InitialValue", + "private": true, + "sentence": "the initial value of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Variable(_Tween3D.Tweens[TweenIdentifier].InitialValue)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "InitialValue", + "name": "SetInitialValue", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ModVarObjet" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens[TweenIdentifier].InitialValue", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "the tween targeted value", + "fullName": "Targeted value", + "functionType": "ExpressionAndCondition", + "name": "TargetedValue", + "private": true, + "sentence": "the targeted value of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Variable(_Tween3D.Tweens[TweenIdentifier].TargetedValue)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "TargetedValue", + "name": "SetTargetedValue", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ModVarObjet" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens[TweenIdentifier].TargetedValue", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the tween is paused", + "fullName": "Paused", + "functionType": "Condition", + "name": "IsPaused", + "private": true, + "sentence": "The tween _PARAM2_ of _PARAM0_ is paused", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ObjectVariableAsBoolean" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens[TweenIdentifier].IsPaused", + "True" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "Change if the tween is paused", + "fullName": "Paused", + "functionType": "Action", + "name": "SetPaused", + "private": true, + "sentence": "The tween _PARAM2_ of _PARAM0_ is paused: _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsPaused", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetObjectVariableAsBoolean" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens[TweenIdentifier].IsPaused", + "True" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "BooleanVariable" + }, + "parameters": [ + "IsPaused", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetObjectVariableAsBoolean" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens[TweenIdentifier].IsPaused", + "False" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Paused", + "name": "IsPaused", + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Stop the running tween animation.", + "fullName": "Stop a tween", + "functionType": "Action", + "name": "StopTween", + "sentence": "Stop the tween _PARAM2_ on _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Exists" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetTime" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "Object.Behavior::Duration(TweenIdentifier)", + "TweenIdentifier", + "" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "JumpToEnd", + "True", + "" + ] + } + ], + "actions": [] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + }, + { + "description": "Jump to end", + "name": "JumpToEnd", + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Pause the running tween animation.", + "fullName": "Pause a tween", + "functionType": "Action", + "name": "PauseTween", + "sentence": "Pause the tween _PARAM2_ on _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Exists" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetPaused" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "yes", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "Resume the tween animation.", + "fullName": "Resume a tween", + "functionType": "Action", + "name": "ResumeTween", + "sentence": "Resume the tween _PARAM2_ on _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Exists" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetPaused" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "no", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "Remove the tween animation from the object.", + "fullName": "Remove a tween", + "functionType": "Action", + "name": "RemoveTween", + "sentence": "Remove the tween _PARAM2_ from _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Exists" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ObjectVariableRemoveChild" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens", + "TweenIdentifier" + ] + }, + { + "type": { + "value": "Tween3D::Tween3D::InvalidateTweenIdentifiers" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the tween animation exists.", + "fullName": "Tween exists", + "functionType": "Condition", + "name": "Exists", + "sentence": "Tween _PARAM2_ on _PARAM0_ exists", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ObjectVariableChildExists" + }, + "parameters": [ + "Object", + "_Tween3D.Tweens", + "TweenIdentifier" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the tween animation is currently playing.", + "fullName": "Tween is playing", + "functionType": "Condition", + "name": "IsPlaying", + "sentence": "Tween _PARAM2_ on _PARAM0_ is playing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Exists" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "Tween3D::Tween3D::HasFinished" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "Tween3D::Tween3D::IsPaused" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the tween animation has finished playing.", + "fullName": "Tween finished playing", + "functionType": "Condition", + "name": "HasFinished", + "sentence": "Tween _PARAM2_ on _PARAM0_ has finished playing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Progress" + }, + "parameters": [ + "Object", + "Behavior", + ">=", + "1", + "TweenIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "the progress of a tween (between 0.0 and 1.0).", + "fullName": "Progress of a tween", + "functionType": "ExpressionAndCondition", + "name": "Progress", + "sentence": "the progress of tween _PARAM2_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Tween3D::Tween3D::Exists" + }, + "parameters": [ + "Object", + "Behavior", + "TweenIdentifier", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Object.Behavior::Time(TweenIdentifier) / Object.Behavior::Duration(TweenIdentifier)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Tween identifier", + "name": "TweenIdentifier", + "supplementaryInformation": "objectTween3D", + "type": "identifier" + } + ], + "objectGroups": [] + }, + { + "description": "Return the tween identifier for a given index.", + "fullName": "Tween identifier", + "functionType": "StringExpression", + "name": "TweenIdentifier", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];", + "", + "const childIndex = eventsFunctionContext.getArgument(\"ChildIndex\");", + "", + "object.__Tween3D = object.__Tween3D || {};", + "object.__Tween3D.variableIdentifiers =", + " object.__Tween3D.variableIdentifiers", + " || Object.keys(object.getVariables().get(\"_Tween3D\").getChild(\"Tweens\").getAllChildren());", + "", + "eventsFunctionContext.returnValue = object.__Tween3D.variableIdentifiers[childIndex];", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + }, + { + "description": "Child index", + "name": "ChildIndex", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Invalidate tween identifiers.", + "fullName": "Invalidate tween identifiers", + "functionType": "Action", + "name": "InvalidateTweenIdentifiers", + "private": true, + "sentence": "Invalidate tween identifiers of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];", + "", + "object.__Tween3D = object.__Tween3D || {};", + "object.__Tween3D.variableIdentifiers = null;", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Tween3D", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "folderName": "Size", + "children": [ + { + "functionName": "StretchZ" + }, + { + "functionName": "SetStretchZ" + }, + { + "functionName": "StretchY" + }, + { + "functionName": "SetStretchY" + }, + { + "functionName": "ScaleVolume" + }, + { + "functionName": "SetScaleVolume" + }, + { + "functionName": "TweenStretchZ" + }, + { + "functionName": "TweenStretchY" + }, + { + "functionName": "TweenScaleVolume" + } + ] + }, + { + "folderName": "Color", + "children": [ + { + "functionName": "Tint" + }, + { + "functionName": "TweenTint" + } + ] + }, + { + "folderName": "Position", + "children": [ + { + "functionName": "TweenPositionZ" + }, + { + "functionName": "TweenDepth" + } + ] + }, + { + "folderName": "Angle", + "children": [ + { + "functionName": "TweenRotationX" + }, + { + "functionName": "TweenRotationY" + } + ] + }, + { + "functionName": "doStepPreEvents" + }, + { + "functionName": "TweenedValue" + }, + { + "functionName": "TweenedColor" + }, + { + "functionName": "ExponentTweenedValue" + }, + { + "functionName": "StartTween" + }, + { + "functionName": "Type" + }, + { + "functionName": "SetType" + }, + { + "functionName": "Time" + }, + { + "functionName": "SetTime" + }, + { + "functionName": "Duration" + }, + { + "functionName": "SetDuration" + }, + { + "functionName": "Easing" + }, + { + "functionName": "SetEasing" + }, + { + "functionName": "InitialValue" + }, + { + "functionName": "SetInitialValue" + }, + { + "functionName": "TargetedValue" + }, + { + "functionName": "SetTargetedValue" + }, + { + "functionName": "IsPaused" + }, + { + "functionName": "SetPaused" + }, + { + "functionName": "StopTween" + }, + { + "functionName": "PauseTween" + }, + { + "functionName": "ResumeTween" + }, + { + "functionName": "RemoveTween" + }, + { + "functionName": "Exists" + }, + { + "functionName": "IsPlaying" + }, + { + "functionName": "HasFinished" + }, + { + "functionName": "Progress" + }, + { + "functionName": "TweenIdentifier" + }, + { + "functionName": "InvalidateTweenIdentifiers" + } + ] + }, + "propertyDescriptors": [ + { + "value": "", + "type": "Behavior", + "label": "3D capability", + "extraInformation": [ + "Scene3D::Base3DBehavior" + ], + "choices": [], + "name": "Object3D" + }, + { + "value": "", + "type": "Behavior", + "label": "Scalable capacity", + "extraInformation": [ + "ScalableCapability::ScalableBehavior" + ], + "choices": [], + "name": "Scale" + }, + { + "value": "", + "type": "Behavior", + "label": "Tween", + "extraInformation": [ + "Tween::TweenBehavior" + ], + "choices": [], + "name": "Tween" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Object3D" + }, + { + "propertyName": "Scale" + }, + { + "propertyName": "Tween" + } + ] + } + }, + { + "description": "Squash and stretch a 3D model in loop.", + "fullName": "3D wobble", + "helpPath": "", + "iconUrl": "", + "name": "Wobble", + "objectType": "", + "previewIconUrl": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween3D::Wobble::UpdateScaleVolume" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + }, + { + "type": { + "value": "Tween3D::Wobble::UpdateStretch" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "sentence": "", + "events": [ + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Tween", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "ScaleMinTweenTime", + "<", + "ScaleMinTweenDuration" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMinTweenTime", + "+", + "Object.Behavior::TimeDelta()" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMin", + "=", + "Tween3D::EaseExp(ScaleMinTweenEasing, ScaleMinTweenInitialValue, ScaleMinTweenTarget, ScaleMinTweenTime / ScaleMinTweenDuration)" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "ScaleMinTweenTime", + ">=", + "ScaleMinTweenDuration" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMin", + "=", + "ScaleMinTweenTarget" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "ScaleMaxTweenTime", + "<", + "ScaleMaxTweenDuration" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMaxTweenTime", + "+", + "Object.Behavior::TimeDelta()" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMax", + "=", + "Tween3D::EaseExp(ScaleMaxTweenEasing, ScaleMaxTweenInitialValue, ScaleMaxTweenTarget, ScaleMaxTweenTime / ScaleMaxTweenDuration)" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "ScaleMaxTweenTime", + ">=", + "ScaleMaxTweenDuration" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMin", + "=", + "ScaleMinTweenTarget" + ] + } + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "name": "Wobble", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween3D::Wobble::UpdateScaleVolume" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + }, + { + "type": { + "value": "Tween3D::Wobble::UpdateStretch" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Angle", + "+", + "mod(Object.Behavior::TimeDelta() * 2 * Pi()\n / PeriodDuration, 2 * Pi())" + ] + } + ] + } + ], + "parameters": [] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the minimum scale volume of the object.", + "fullName": "Minimum scale volume", + "functionType": "ExpressionAndCondition", + "group": "3D wobble scale configuration", + "name": "ScaleMin", + "sentence": "the minimum scale volume", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ScaleMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ScaleMin", + "group": "3D wobble scale configuration", + "name": "SetScaleMin", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMin", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the maximum scale volume of the object.", + "fullName": "Maximum scale volume", + "functionType": "ExpressionAndCondition", + "group": "3D wobble scale configuration", + "name": "ScaleMax", + "sentence": "the maximum scale volume", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ScaleMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ScaleMax", + "group": "3D wobble scale configuration", + "name": "SetScaleMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Tweens the minimum scale volume from its current value to a new one. The scale volume can never be 0 or less.", + "fullName": "Tween minimum scale volume", + "functionType": "Action", + "group": "3D wobble scale configuration", + "name": "TweenScaleMin", + "sentence": "Tween the minimum scale volume of object _PARAM0_ to _PARAM2_ over _PARAM3_ seconds with _PARAM4_ easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMinTweenInitialValue", + "=", + "Object.Behavior::ScaleMin()" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMinTweenTime", + "=", + "0" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMinTweenTarget", + "=", + "TargetedValue" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMinTweenDuration", + "=", + "Duration" + ] + }, + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "ScaleMinTweenEasing", + "=", + "Easing" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + }, + { + "description": "Minimum scale volume", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + } + ], + "objectGroups": [] + }, + { + "description": "Tweens the maximum scale volume from its current value to a new one. The scale volume can never be 0 or less.", + "fullName": "Tween maximum scale volume", + "functionType": "Action", + "group": "3D wobble scale configuration", + "name": "TweenScaleMax", + "sentence": "Tween the maximum scale volume of object _PARAM0_ to _PARAM2_ over _PARAM3_ seconds with _PARAM4_ easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMaxTweenInitialValue", + "=", + "Object.Behavior::ScaleMax()" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMaxTweenTime", + "=", + "0" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMaxTweenTarget", + "=", + "TargetedValue" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScaleMaxTweenDuration", + "=", + "Duration" + ] + }, + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "ScaleMaxTweenEasing", + "=", + "Easing" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + }, + { + "description": "Maximum scale volume", + "name": "TargetedValue", + "type": "expression" + }, + { + "description": "Duration (in seconds)", + "name": "Duration", + "type": "expression" + }, + { + "description": "Easing", + "name": "Easing", + "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" + } + ], + "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": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Update scale volume", + "functionType": "Action", + "name": "UpdateScaleVolume", + "private": true, + "sentence": "Update scale volume of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetScaleVolume" + }, + "parameters": [ + "Object", + "Tween3D", + "=", + "exp(log(ScaleMin) + (log(ScaleMax) - log(ScaleMin)) * sin(Angle + ScalePeriodOffset / PeriodDuration * 2 * Pi()))", + "Object3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "Update stretch", + "functionType": "Action", + "name": "UpdateStretch", + "private": true, + "sentence": "Update stretch of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Top", + "=", + "\"Z+\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetStretchZ" + }, + "parameters": [ + "Object", + "Tween3D", + "=", + "exp(log(StretchMin) + (log(StretchMax) - log(StretchMin)) * sin(Angle + StretchPeriodOffset / PeriodDuration * 2 * Pi()))", + "Object3D" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "Top", + "=", + "\"Y-\"" + ] + } + ], + "actions": [ + { + "type": { + "value": "Tween3D::Tween3D::SetStretchY" + }, + "parameters": [ + "Object", + "Tween3D", + "=", + "exp(log(StretchMin) + (log(StretchMax) - log(StretchMin)) * sin(Angle + StretchPeriodOffset / PeriodDuration * 2 * Pi()))", + "Object3D" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the cycle duration of the object.", + "fullName": "Cycle duration", + "functionType": "ExpressionAndCondition", + "group": "3D wobble configuration", + "name": "PeriodDuration", + "sentence": "the cycle duration", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "PeriodDuration" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "PeriodDuration", + "group": "3D wobble configuration", + "name": "SetPeriodDuration", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "PeriodDuration", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the scale cycle offset of the object.", + "fullName": "Scale cycle offset", + "functionType": "ExpressionAndCondition", + "group": "3D wobble scale configuration", + "name": "ScalePeriodOffset", + "sentence": "the scale cycle offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ScalePeriodOffset" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ScalePeriodOffset", + "group": "3D wobble scale configuration", + "name": "SetScalePeriodOffset", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ScalePeriodOffset", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the minimum stretch of the object.", + "fullName": "Minimum stretch", + "functionType": "ExpressionAndCondition", + "group": "3D wobble stretch configuration", + "name": "StretchMin", + "sentence": "the minimum stretch", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "StretchMin" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "StretchMin", + "group": "3D wobble stretch configuration", + "name": "SetStretchMin", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StretchMin", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the maximum stretch of the object.", + "fullName": "Maximum stretch", + "functionType": "ExpressionAndCondition", + "group": "3D wobble stretch configuration", + "name": "StretchMax", + "sentence": "the maximum stretch", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "StretchMax" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "StretchMax", + "group": "3D wobble stretch configuration", + "name": "SetStretchMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StretchMax", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the stretch cycle offset of the object.", + "fullName": "Stretch cycle offset", + "functionType": "ExpressionAndCondition", + "group": "3D wobble stretch configuration", + "name": "StretchPeriodOffset", + "sentence": "the stretch cycle offset", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "StretchPeriodOffset" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "StretchPeriodOffset", + "group": "3D wobble stretch configuration", + "name": "SetStretchPeriodOffset", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StretchPeriodOffset", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "Tween3D::Wobble", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "doStepPreEvents" + }, + { + "folderName": "3D wobble scale configuration", + "children": [ + { + "functionName": "ScaleMin" + }, + { + "functionName": "SetScaleMin" + }, + { + "functionName": "ScaleMax" + }, + { + "functionName": "SetScaleMax" + }, + { + "functionName": "TweenScaleMin" + }, + { + "functionName": "TweenScaleMax" + }, + { + "functionName": "ScalePeriodOffset" + }, + { + "functionName": "SetScalePeriodOffset" + } + ] + }, + { + "functionName": "TimeDelta" + }, + { + "functionName": "UpdateScaleVolume" + }, + { + "functionName": "UpdateStretch" + }, + { + "folderName": "3D wobble configuration", + "children": [ + { + "functionName": "PeriodDuration" + }, + { + "functionName": "SetPeriodDuration" + } + ] + }, + { + "folderName": "3D wobble stretch configuration", + "children": [ + { + "functionName": "StretchMin" + }, + { + "functionName": "SetStretchMin" + }, + { + "functionName": "StretchMax" + }, + { + "functionName": "SetStretchMax" + }, + { + "functionName": "StretchPeriodOffset" + }, + { + "functionName": "SetStretchPeriodOffset" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "0.75", + "type": "Number", + "unit": "Dimensionless", + "label": "Minimum scale volume", + "group": "Scale", + "name": "ScaleMin" + }, + { + "value": "1", + "type": "Number", + "unit": "Dimensionless", + "label": "Maximum scale volume", + "group": "Scale", + "name": "ScaleMax" + }, + { + "value": "1", + "type": "Number", + "unit": "Second", + "label": "Cycle duration", + "name": "PeriodDuration" + }, + { + "value": "0", + "type": "Number", + "unit": "Second", + "label": "Scale cycle offset", + "group": "Scale", + "name": "ScalePeriodOffset" + }, + { + "value": "1", + "type": "Number", + "unit": "Dimensionless", + "label": "Minimum stretch", + "group": "Stretch", + "name": "StretchMin" + }, + { + "value": "1.25", + "type": "Number", + "unit": "Dimensionless", + "label": "Maximum stretch", + "group": "Stretch", + "name": "StretchMax" + }, + { + "value": "-0.25", + "type": "Number", + "unit": "Second", + "label": "Stretch cycle offset", + "group": "Stretch", + "name": "StretchPeriodOffset" + }, + { + "value": "0", + "type": "Number", + "unit": "DegreeAngle", + "label": "", + "hidden": true, + "name": "Angle" + }, + { + "value": "0", + "type": "Number", + "unit": "Dimensionless", + "label": "", + "hidden": true, + "name": "ScaleMinTweenTarget" + }, + { + "value": "0", + "type": "Number", + "unit": "Second", + "label": "", + "hidden": true, + "name": "ScaleMinTweenDuration" + }, + { + "value": "", + "type": "String", + "label": "", + "hidden": true, + "name": "ScaleMinTweenEasing" + }, + { + "value": "0", + "type": "Number", + "unit": "Dimensionless", + "label": "", + "hidden": true, + "name": "ScaleMaxTweenTarget" + }, + { + "value": "0", + "type": "Number", + "unit": "Second", + "label": "", + "hidden": true, + "name": "ScaleMaxTweenDuration" + }, + { + "value": "", + "type": "String", + "label": "", + "hidden": true, + "name": "ScaleMaxTweenEasing" + }, + { + "value": "0", + "type": "Number", + "unit": "Second", + "label": "", + "hidden": true, + "name": "ScaleMinTweenTime" + }, + { + "value": "0", + "type": "Number", + "unit": "Second", + "label": "", + "hidden": true, + "name": "ScaleMaxTweenTime" + }, + { + "value": "0", + "type": "Number", + "label": "", + "hidden": true, + "name": "ScaleMinTweenInitialValue" + }, + { + "value": "0", + "type": "Number", + "label": "", + "hidden": true, + "name": "ScaleMaxTweenInitialValue" + }, + { + "value": "Z+", + "type": "Choice", + "label": "Top orientation", + "choices": [ + { + "label": "Z+", + "value": "Z+" + }, + { + "label": "Y-", + "value": "Y-" + } + ], + "name": "Top" + }, + { + "value": "", + "type": "Behavior", + "label": "3D tween", + "extraInformation": [ + "Tween3D::Tween3D" + ], + "choices": [], + "name": "Tween3D" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "folderName": "Scale", + "children": [ + { + "propertyName": "ScaleMin" + }, + { + "propertyName": "ScaleMax" + }, + { + "propertyName": "ScalePeriodOffset" + } + ] + }, + { + "propertyName": "PeriodDuration" + }, + { + "folderName": "Stretch", + "children": [ + { + "propertyName": "StretchMin" + }, + { + "propertyName": "StretchMax" + }, + { + "propertyName": "StretchPeriodOffset" + } + ] + }, + { + "propertyName": "Angle" + }, + { + "propertyName": "ScaleMinTweenTarget" + }, + { + "propertyName": "ScaleMinTweenDuration" + }, + { + "propertyName": "ScaleMinTweenEasing" + }, + { + "propertyName": "ScaleMaxTweenTarget" + }, + { + "propertyName": "ScaleMaxTweenDuration" + }, + { + "propertyName": "ScaleMaxTweenEasing" + }, + { + "propertyName": "ScaleMinTweenTime" + }, + { + "propertyName": "ScaleMaxTweenTime" + }, + { + "propertyName": "ScaleMinTweenInitialValue" + }, + { + "propertyName": "ScaleMaxTweenInitialValue" + }, + { + "propertyName": "Top" + }, + { + "propertyName": "Tween3D" + } + ] + } + } + ], + "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": "Visual effect", + "extensionNamespace": "", + "gdevelopVersion": ">=5.5.222", + "fullName": "3D object shake", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXZlY3Rvci1kaWZmZXJlbmNlLWFiIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTMsMUMxLjg5LDEgMSwxLjg5IDEsM1Y1SDNWM0g1VjFIM003LDFWM0gxMFYxSDdNMTIsMVYzSDE0VjVIMTZWM0MxNiwxLjg5IDE1LjExLDEgMTQsMUgxMk0xLDdWMTBIM1Y3SDFNMTQsN0MxNCw3IDE0LDExLjY3IDE0LDE0QzExLjY3LDE0IDcsMTQgNywxNEM3LDE0IDcsMTggNywyMEM3LDIxLjExIDcuODksMjIgOSwyMkgyMEMyMS4xMSwyMiAyMiwyMS4xMSAyMiwyMFY5QzIyLDcuODkgMjEuMTEsNyAyMCw3QzE4LDcgMTQsNyAxNCw3TTE2LDlIMjBWMjBIOVYxNkgxNEMxNS4xMSwxNiAxNiwxNS4xMSAxNiwxNFY5TTEsMTJWMTRDMSwxNS4xMSAxLjg5LDE2IDMsMTZINVYxNEgzVjEySDFaIiAvPjwvc3ZnPg==", + "name": "ShakeObject3D", + "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/vector-difference-ab.svg", + "shortDescription": "Shake 3D objects.", + "dimension": "3D", + "version": "2.1.1", + "description": [ + "Shake 3D objects with translation and rotation.", + "", + "The 3D racing game example uses this extension ([open the project online](https://editor.gdevelop.io/?project=example://3d-racing-game))." + ], + "origin": { + "identifier": "ShakeObject3D", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d", + "shaking", + "effect", + "shake", + "translate", + "rotate" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "changelog": [ + { + "version": "2.0.0", + "breaking": "- The behavior for 3D box has been removed. The other behavior can be used for both models and boxes." + } + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onFirstSceneLoaded", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "ShakeObject3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "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._shakeObjectExtension) {", + " 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._shakeObjectExtension = {", + " 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._shakeObjectExtension.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._shakeObjectExtension.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._shakeObjectExtension.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._shakeObjectExtension.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._shakeObjectExtension.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._shakeObjectExtension.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._shakeObjectExtension.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._shakeObjectExtension.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._shakeObjectExtension.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._shakeObjectExtension.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._shakeObjectExtension.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._shakeObjectExtension.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._shakeObjectExtension.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._shakeObjectExtension.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._shakeObjectExtension.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._shakeObjectExtension.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._shakeObjectExtension.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._shakeObjectExtension.noiseManager.getGenerator(name).lacunarity;" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Generator name", + "name": "Name", + "type": "string" + } + ], + "objectGroups": [] + } + ], + "eventsBasedBehaviors": [ + { + "description": "Shake 3D objects with translation and rotation.", + "fullName": "3D shake", + "name": "ShakeModel3D", + "objectType": "", + "quickCustomizationVisibility": "hidden", + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsStartingAtCreation", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ShakeObject3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + }, + { + "type": { + "value": "ShakeObject3D::ShakeModel3D::StartShaking" + }, + "parameters": [ + "Object", + "Behavior", + "0", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ShakeObject3D::ShakeModel3D", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPostEvents", + "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": "ShakeObject3D::ShakeModel3D::IsShaking" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "NoiseTime", + "+", + "TimeDelta() * Frequency" + ] + }, + { + "type": { + "value": "ShakeObject3D::ShakeModel3D::SetSharedPropertyEasingFactor" + }, + "parameters": [ + "Object", + "Behavior", + "=", + "min(Object.Behavior::StartEasingFactor(), Object.Behavior::StopEasingFactor())" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Shake the object.\nSave the object displacement to revert it in onScenePostEvents." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TranslationAmplitudeX", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DeltaX", + "=", + "ShakeObject3D::Noise2d(\"\", NoiseTime, 1000) * TranslationAmplitudeX * EasingFactor" + ] + }, + { + "type": { + "value": "SetX" + }, + "parameters": [ + "Object", + "+", + "DeltaX" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TranslationAmplitudeY", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DeltaY", + "=", + "ShakeObject3D::Noise2d(\"\", NoiseTime, 2000) * TranslationAmplitudeY * EasingFactor" + ] + }, + { + "type": { + "value": "SetY" + }, + "parameters": [ + "Object", + "+", + "DeltaY" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TranslationAmplitudeZ", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DeltaZ", + "=", + "ShakeObject3D::Noise2d(\"\", NoiseTime, 3000) * TranslationAmplitudeZ * EasingFactor" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetZ" + }, + "parameters": [ + "Object", + "Object3D", + "+", + "DeltaZ" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "RotationAmplitudeX", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DeltaAngleX", + "=", + "ShakeObject3D::Noise2d(\"\", NoiseTime, 4000) * RotationAmplitudeX * EasingFactor" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationX" + }, + "parameters": [ + "Object", + "Object3D", + "+", + "DeltaAngleX" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "RotationAmplitudeY", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DeltaAngleY", + "=", + "ShakeObject3D::Noise2d(\"\", NoiseTime, 5000) * RotationAmplitudeY * EasingFactor" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationY" + }, + "parameters": [ + "Object", + "Object3D", + "+", + "DeltaAngleY" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "RotationAmplitudeZ", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "DeltaAngleZ", + "=", + "ShakeObject3D::Noise2d(\"\", NoiseTime, 6000) * RotationAmplitudeZ * EasingFactor" + ] + }, + { + "type": { + "value": "SetAngle" + }, + "parameters": [ + "Object", + "+", + "DeltaAngleZ" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ShakeObject3D::ShakeModel3D", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "doStepPreEvents", + "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": "ShakeObject3D::ShakeModel3D::IsShaking" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TranslationAmplitudeX", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetX" + }, + "parameters": [ + "Object", + "-", + "DeltaX" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TranslationAmplitudeY", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetY" + }, + "parameters": [ + "Object", + "-", + "DeltaY" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TranslationAmplitudeZ", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "Scene3D::Base3DBehavior::SetZ" + }, + "parameters": [ + "Object", + "Object3D", + "-", + "DeltaZ" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "RotationAmplitudeX", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationX" + }, + "parameters": [ + "Object", + "Object3D", + "-", + "DeltaAngleX" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "RotationAmplitudeY", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationY" + }, + "parameters": [ + "Object", + "Object3D", + "-", + "DeltaAngleY" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "RotationAmplitudeZ", + "!=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetAngle" + }, + "parameters": [ + "Object", + "-", + "DeltaAngleZ" + ] + } + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ShakeObject3D::ShakeModel3D", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Shake the object with a linear easing at the start and the end.", + "fullName": "Shake", + "functionType": "Action", + "name": "Shake", + "sentence": "Shake _PARAM0_ for _PARAM2_ seconds with _PARAM3_ seconds of easing to start and _PARAM4_ seconds to stop", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Time", + "=", + "0" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "NoiseTime", + "=", + "TimeFromStart() * Frequency" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Duration", + "=", + "NewDuration" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StartEasingDuration", + "=", + "StartEaseDuration" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StopEasingDuration", + "=", + "StopEaseDuration" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "NewDuration", + "<", + "StartEaseDuration + StopEaseDuration" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StartEasingDuration", + "=", + "StartEaseDuration * NewDuration / (StartEaseDuration + StopEaseDuration)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StopEasingDuration", + "=", + "StopEaseDuration * NewDuration / (StartEaseDuration + StopEaseDuration)" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ShakeObject3D::ShakeModel3D", + "type": "behavior" + }, + { + "description": "Duration (in seconds)", + "name": "NewDuration", + "type": "expression" + }, + { + "description": "Ease duration to start (in seconds)", + "name": "StartEaseDuration", + "type": "expression" + }, + { + "description": "Ease duration to stop (in seconds)", + "name": "StopEaseDuration", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Shake the object with a linear easing at the start and keep shaking until the stop action is used.", + "fullName": "Start shaking", + "functionType": "Action", + "name": "StartShaking", + "sentence": "Start shaking _PARAM0_ with _PARAM2_ seconds of easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Time", + "=", + "0" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "NoiseTime", + "=", + "TimeFromStart() * Frequency" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Duration", + "=", + "1234567890" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StartEasingDuration", + "=", + "EaseDuration" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ShakeObject3D::ShakeModel3D", + "type": "behavior" + }, + { + "description": "Ease duration (in seconds)", + "name": "EaseDuration", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Stop shaking the object with a linear easing.", + "fullName": "Stop shaking", + "functionType": "Action", + "name": "StopShaking", + "sentence": "Stop shaking _PARAM0_ with _PARAM2_ seconds of easing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ShakeObject3D::ShakeModel3D::IsShaking" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Time", + "=", + "0" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Duration", + "=", + "EaseDuration" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "StopEasingDuration", + "=", + "EaseDuration" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ShakeObject3D::ShakeModel3D", + "type": "behavior" + }, + { + "description": "Ease duration (in seconds)", + "name": "EaseDuration", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the object is shaking.", + "fullName": "Is shaking", + "functionType": "Condition", + "name": "IsShaking", + "sentence": "_PARAM0_ is shaking", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "Time", + "<", + "Duration" + ] + }, + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "Time", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ShakeObject3D::ShakeModel3D", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the object is stopping to shake.", + "fullName": "Is stopping to shake", + "functionType": "Condition", + "name": "IsStopping", + "sentence": "_PARAM0_ is stopping to shake", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "ShakeObject3D::ShakeModel3D::IsShaking" + }, + "parameters": [ + "Object", + "Behavior", + "" + ] + }, + { + "type": { + "value": "ShakeObject3D::ShakeModel3D::StopEasingFactor" + }, + "parameters": [ + "Object", + "Behavior", + "<", + "1", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ShakeObject3D::ShakeModel3D", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the shaking frequency of the object.", + "fullName": "Shaking frequency", + "functionType": "ExpressionAndCondition", + "group": "ShakeObject3D configuration", + "name": "Frequency", + "sentence": "the shaking frequency", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Frequency" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ShakeObject3D::ShakeModel3D", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Frequency", + "name": "SetFrequency", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Frequency", + "=", + "Value" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ShakeObject3D::ShakeModel3D", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Return the easing factor according to start properties.", + "fullName": "Start easing factor", + "functionType": "Expression", + "name": "StartEasingFactor", + "private": true, + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "StartEasingDuration", + "<=", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "1" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "StartEasingDuration", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "clamp(Time / StartEasingDuration, 0, 1)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ShakeObject3D::ShakeModel3D", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "Return the easing factor according to stop properties.", + "fullName": "Stop easing factor", + "functionType": "ExpressionAndCondition", + "name": "StopEasingFactor", + "private": true, + "sentence": "stop easing factor", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "StopEasingDuration", + "<=", + "0" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "Time", + "<", + "Duration" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "1" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "Time", + ">=", + "Duration" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "0" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "StopEasingDuration", + ">", + "0" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "clamp((Duration - Time) / StopEasingDuration, 0, 1)" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "type": "object" + }, + { + "description": "Behavior", + "name": "Behavior", + "supplementaryInformation": "ShakeObject3D::ShakeModel3D", + "type": "behavior" + } + ], + "objectGroups": [] + } + ], + "propertyDescriptors": [ + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Translation amplitude on X axis", + "description": "", + "group": "", + "extraInformation": [], + "name": "TranslationAmplitudeX" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Translation amplitude on Y axis", + "description": "", + "group": "", + "extraInformation": [], + "name": "TranslationAmplitudeY" + }, + { + "value": "0", + "type": "Number", + "unit": "Pixel", + "label": "Translation amplitude on Z axis", + "description": "", + "group": "", + "extraInformation": [], + "name": "TranslationAmplitudeZ" + }, + { + "value": "5", + "type": "Number", + "unit": "DegreeAngle", + "label": "Rotation amplitude around X axis", + "description": "", + "group": "", + "extraInformation": [], + "name": "RotationAmplitudeX" + }, + { + "value": "5", + "type": "Number", + "unit": "DegreeAngle", + "label": "Rotation amplitude around Y axis", + "description": "", + "group": "", + "extraInformation": [], + "name": "RotationAmplitudeY" + }, + { + "value": "5", + "type": "Number", + "unit": "DegreeAngle", + "label": "Rotation amplitude around Z axis", + "description": "", + "group": "", + "extraInformation": [], + "name": "RotationAmplitudeZ" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "Time" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "Duration" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "StartEasingDuration" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "StopEasingDuration" + }, + { + "value": "", + "type": "Number", + "label": "Frequency", + "description": "", + "group": "", + "extraInformation": [], + "name": "Frequency" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "DeltaX" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "DeltaY" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "DeltaZ" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "DeltaAngleX" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "DeltaAngleY" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "DeltaAngleZ" + }, + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "NoiseTime" + }, + { + "value": "", + "type": "Behavior", + "label": "3D capability", + "description": "", + "group": "", + "extraInformation": [ + "Scene3D::Base3DBehavior" + ], + "name": "Object3D" + }, + { + "value": "", + "type": "Boolean", + "label": "Start to shake at the object creation", + "description": "", + "group": "", + "extraInformation": [], + "name": "IsStartingAtCreation" + } + ], + "sharedPropertyDescriptors": [ + { + "value": "", + "type": "Number", + "label": "", + "description": "", + "group": "", + "extraInformation": [], + "hidden": true, + "name": "EasingFactor" + } + ] + } + ], + "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-first-person-survival-crafting/theme-slots.json b/examples/starting-expanded-first-person-survival-crafting/theme-slots.json new file mode 100644 index 000000000..d6b57b841 --- /dev/null +++ b/examples/starting-expanded-first-person-survival-crafting/theme-slots.json @@ -0,0 +1,60 @@ +{ + "version": 1, + "objects": { + "scene:Game Scene/Obstacle": { + "front": "env.wall", + "left": "env.wall", + "right": "env.wall", + "top": "env.wall", + "bottom": "env.wall" + }, + "scene:Game Scene/Log": "resource.log", + "scene:Game Scene/Rock": "nature.rock.small", + "scene:Game Scene/Harvest_Rock": "nature.rock.big", + "scene:Game Scene/Harvest_Tree": "nature.tree", + "scene:Game Scene/Bushes": { + "Model": "nature.grass" + }, + "scene:Game Scene/BerryBushes": { + "Model": "nature.grass" + }, + "scene:Game Scene/FallenLog": "resource.log", + "scene:Game Scene/Stump": "nature.rock.small", + "scene:Game Scene/Tent": "building.tower", + "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.ground", + "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/Player", + "scene:Game Scene/Plants", + "scene:Game Scene/Flowers", + "scene:Game Scene/Campfire", + "scene:Game Scene/MapLimit", + "object:Terrain3D::Terrain3D/AreaPlaceholder", + "object:Terrain3D::TerrainGrass/AreaPlaceholder", + "object:Terrain3D::ScatteredModels/AreaPlaceholder" + ] +}