diff --git a/.circleci/config.yml b/.circleci/config.yml index 4887a5051..c66f88d0e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -33,12 +33,38 @@ jobs: - checkout - run: npm install - run: git clone git@github.com:4ian/GDevelop.git --depth 1 - - run: cd GDevelop/newIDE/app && npm install + - run: + name: Generate the cache key of GDevelop dependencies + # Computed before the install, which can rewrite the lock files. + command: cat GDevelop/newIDE/app/package-lock.json GDevelop/GDJS/package-lock.json | shasum -a 256 > gdevelop-dependencies-checksum.txt + - restore_cache: + keys: + - gdevelop-dependencies-v1-{{ checksum "gdevelop-dependencies-checksum.txt" }} + # fallback to using the latest cache if no exact match is found + - gdevelop-dependencies-v1- + - run: cd GDevelop/newIDE/app && npm install --no-audit --no-fund + - save_cache: + paths: + - GDevelop/newIDE/app/node_modules + - GDevelop/GDJS/node_modules + key: gdevelop-dependencies-v1-{{ checksum "gdevelop-dependencies-checksum.txt" }} - persist_to_workspace: root: . paths: - node_modules # For npm install. - - GDevelop/newIDE/app # As we have a dependency on GDevelop sources. + # What scripts/lib/GDevelopCoreLoader.js loads from GDevelop (not the + # whole newIDE/app: its node_modules are more than 1GB, long to persist + # and to attach in every job). + # Without it, the files of GDevelop would take the "type" of the + # package.json of this repository. + - GDevelop/newIDE/app/package.json + - GDevelop/newIDE/app/public/libGD.js + - GDevelop/newIDE/app/public/libGD.wasm + - GDevelop/newIDE/app/resources/GDJS + - GDevelop/newIDE/app/src + # The packages required by newIDE/app/src/JsExtensionsLoader. + - GDevelop/newIDE/app/node_modules/lodash + - GDevelop/newIDE/app/node_modules/node-require-function tests: docker: - image: cimg/node:20.13.1 @@ -77,6 +103,11 @@ jobs: - attach_workspace: at: . - aws-cli/setup + - run: + # The deploy uploads more than a thousand small files: the default of + # 10 concurrent requests makes it take minutes. + name: Upload more files in parallel + command: aws configure set default.s3.max_concurrent_requests 64 - run: npm run deploy -- --cf-zoneid $CLOUDFLARE_ZONE_ID --cf-token $CLOUDFLARE_TOKEN deploy-staging: docker: @@ -94,6 +125,11 @@ jobs: - attach_workspace: at: . - aws-cli/setup + - run: + # The deploy uploads more than a thousand small files: the default of + # 10 concurrent requests makes it take minutes. + name: Upload more files in parallel + command: aws configure set default.s3.max_concurrent_requests 64 - run: npm run deploy -- --staging --cf-zoneid $CLOUDFLARE_ZONE_ID --cf-token $CLOUDFLARE_TOKEN # Run the gameplay tests of the example games with a real GDevelop: the diff --git a/examples/3d-endless-terrain/3d-endless-terrain.json b/examples/3d-endless-terrain/3d-endless-terrain.json index 2377883a4..b9ba18f03 100644 --- a/examples/3d-endless-terrain/3d-endless-terrain.json +++ b/examples/3d-endless-terrain/3d-endless-terrain.json @@ -8498,8 +8498,8 @@ "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMTksMTkgMjQsMTkgMjQsMjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSI2LDIzIDExLDIzIDExLDE4ICIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTI0LDE5LjRjLTAuNywwLjgtMS40LDEuNi0yLjIsMi40Yy03LDctMTUuMywxMC4yLTE4LjUsN3MtMC4xLTExLjUsNy0xOC41czE1LjMtMTAuMiwxOC41LTcNCgljMS40LDEuNCwxLjYsMy42LDAuOCw2LjMiLz4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xMSwyMi41Yy0wLjMtMC4yLTAuNS0wLjUtMC44LTAuOGMtNy03LTEwLjItMTUuMy03LTE4LjVzMTEuNS0wLjEsMTguNSw3czEwLjIsMTUuMyw3LDE4LjUNCgljLTEuNywxLjctNC44LDEuNi04LjQsMC4xIi8+DQo8L3N2Zz4NCg==", "name": "MousePointerLock", "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Virtual Reality/Virtual Reality_360_rotate_vr_movement.svg", - "shortDescription": "This behavior removes the limit on the distance the mouse can move and hides the cursor.", - "version": "0.3.0", + "shortDescription": "Lock and hide mouse pointer for unlimited movement (e.g., FPS mouse look).", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -8547,7 +8547,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -8795,16 +8795,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -8951,7 +8981,7 @@ "functionType": "Condition", "name": "isEmulatingPointerLock", "private": true, - "sentence": "The mouse pointer is lock is emulated", + "sentence": "The mouse pointer lock is emulated", "events": [ { "type": "BuiltinCommonInstructions::Standard", @@ -8987,6 +9017,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -9293,6 +9348,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -9314,7 +9381,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { @@ -9957,32 +10024,7 @@ "name": "ShouldLock" } ], - "propertiesFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "propertyName": "Object3D" - }, - { - "propertyName": "HorizontalSpeed" - }, - { - "propertyName": "VerticalSpeed" - }, - { - "propertyName": "VerticalAngleMin" - }, - { - "propertyName": "VerticalAngleMax" - }, - { - "propertyName": "OffsetZ" - }, - { - "propertyName": "ShouldLock" - } - ] - } + "sharedPropertyDescriptors": [] } ], "eventsBasedObjects": [] diff --git a/examples/3d-first-person/3d-first-person.json b/examples/3d-first-person/3d-first-person.json index 2bbcc98eb..74f7fec7e 100644 --- a/examples/3d-first-person/3d-first-person.json +++ b/examples/3d-first-person/3d-first-person.json @@ -3356,8 +3356,8 @@ "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMTksMTkgMjQsMTkgMjQsMjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSI2LDIzIDExLDIzIDExLDE4ICIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTI0LDE5LjRjLTAuNywwLjgtMS40LDEuNi0yLjIsMi40Yy03LDctMTUuMywxMC4yLTE4LjUsN3MtMC4xLTExLjUsNy0xOC41czE1LjMtMTAuMiwxOC41LTcNCgljMS40LDEuNCwxLjYsMy42LDAuOCw2LjMiLz4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xMSwyMi41Yy0wLjMtMC4yLTAuNS0wLjUtMC44LTAuOGMtNy03LTEwLjItMTUuMy03LTE4LjVzMTEuNS0wLjEsMTguNSw3czEwLjIsMTUuMyw3LDE4LjUNCgljLTEuNywxLjctNC44LDEuNi04LjQsMC4xIi8+DQo8L3N2Zz4NCg==", "name": "MousePointerLock", "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Virtual Reality/Virtual Reality_360_rotate_vr_movement.svg", - "shortDescription": "This behavior removes the limit on the distance the mouse can move and hides the cursor.", - "version": "0.3.0", + "shortDescription": "Lock and hide mouse pointer for unlimited movement (e.g., FPS mouse look).", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -3405,7 +3405,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -3653,16 +3653,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -3809,7 +3839,7 @@ "functionType": "Condition", "name": "isEmulatingPointerLock", "private": true, - "sentence": "The mouse pointer is lock is emulated", + "sentence": "The mouse pointer lock is emulated", "events": [ { "type": "BuiltinCommonInstructions::Standard", @@ -3845,6 +3875,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -4151,6 +4206,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -4172,7 +4239,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { @@ -4815,32 +4882,7 @@ "name": "ShouldLock" } ], - "propertiesFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "propertyName": "Object3D" - }, - { - "propertyName": "HorizontalSpeed" - }, - { - "propertyName": "VerticalSpeed" - }, - { - "propertyName": "VerticalAngleMin" - }, - { - "propertyName": "VerticalAngleMax" - }, - { - "propertyName": "OffsetZ" - }, - { - "propertyName": "ShouldLock" - } - ] - } + "sharedPropertyDescriptors": [] } ], "eventsBasedObjects": [] diff --git a/examples/3d-island-terrain/3d-island-terrain.json b/examples/3d-island-terrain/3d-island-terrain.json index 705fee01a..0b24084dc 100644 --- a/examples/3d-island-terrain/3d-island-terrain.json +++ b/examples/3d-island-terrain/3d-island-terrain.json @@ -2164,6 +2164,51 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Start with touch controls on touchscreens." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "SystemInfo::HasTouchScreen" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::SetChecked" + }, + "parameters": [ + "ControlsToggle", + "yes", + "" + ] + } + ] + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -2710,18 +2755,11 @@ "conditions": [ { "type": { - "value": "MouseButtonFromTextPressed" + "value": "HasAnyTouchOrMouseStarted" }, "parameters": [ - "", - "\"Left\"" + "" ] - }, - { - "type": { - "value": "BuiltinCommonInstructions::Once" - }, - "parameters": [] } ], "actions": [], @@ -2740,17 +2778,6 @@ "\"Left\"", "" ] - }, - { - "type": { - "inverted": true, - "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" - }, - "parameters": [ - "JumpButton", - "MultitouchButton", - "" - ] } ], "actions": [ @@ -17239,7 +17266,7 @@ "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", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -17287,7 +17314,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -17535,16 +17562,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -17727,6 +17784,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -18033,6 +18115,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -18054,7 +18148,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { diff --git a/examples/3d-platformer/3d-platformer.json b/examples/3d-platformer/3d-platformer.json index 15c192026..91ee83401 100644 --- a/examples/3d-platformer/3d-platformer.json +++ b/examples/3d-platformer/3d-platformer.json @@ -19919,7 +19919,6 @@ { "author": "PANDAKO-GitHub", "category": "Input", - "dimension": "", "extensionNamespace": "", "fullName": "Mouse Pointer Lock", "gdevelopVersion": ">=5.5.222", @@ -19927,8 +19926,8 @@ "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMTksMTkgMjQsMTkgMjQsMjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSI2LDIzIDExLDIzIDExLDE4ICIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTI0LDE5LjRjLTAuNywwLjgtMS40LDEuNi0yLjIsMi40Yy03LDctMTUuMywxMC4yLTE4LjUsN3MtMC4xLTExLjUsNy0xOC41czE1LjMtMTAuMiwxOC41LTcNCgljMS40LDEuNCwxLjYsMy42LDAuOCw2LjMiLz4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xMSwyMi41Yy0wLjMtMC4yLTAuNS0wLjUtMC44LTAuOGMtNy03LTEwLjItMTUuMy03LTE4LjVzMTEuNS0wLjEsMTguNSw3czEwLjIsMTUuMyw3LDE4LjUNCgljLTEuNywxLjctNC44LDEuNi04LjQsMC4xIi8+DQo8L3N2Zz4NCg==", "name": "MousePointerLock", "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Virtual Reality/Virtual Reality_360_rotate_vr_movement.svg", - "shortDescription": "This behavior removes the limit on the distance the mouse can move and hides the cursor.", - "version": "0.3.0", + "shortDescription": "Lock and hide mouse pointer for unlimited movement (e.g., FPS mouse look).", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -19976,7 +19975,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -20224,16 +20223,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -20416,6 +20445,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -20697,68 +20751,12 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "onFirstSceneLoaded" - }, - { - "functionName": "onScenePostEvents" - }, - { - "functionName": "RequestPointerLock" - }, - { - "functionName": "ExitPointerLock" - }, - { - "functionName": "isPointerLocked" - }, - { - "functionName": "isPointerActuallyLocked" - }, - { - "functionName": "isEmulatingPointerLock" - }, - { - "functionName": "IsMoving" - }, - { - "functionName": "MovementX" - }, - { - "functionName": "SetMovementX" - }, - { - "functionName": "MovementY" - }, - { - "functionName": "SetMovementY" - }, - { - "functionName": "TouchX" - }, - { - "functionName": "TouchY" - }, - { - "functionName": "TouchSpeedFactor" - }, - { - "functionName": "SetTouchSpeedFactor" - } - ] - }, "eventsBasedBehaviors": [ { "description": "Control camera rotations with a mouse.", "fullName": "First person camera mouse mapper", - "helpPath": "", - "iconUrl": "", "name": "FirstPersonPointerMapper", "objectType": "", - "previewIconUrl": "", "eventsFunctions": [ { "fullName": "", @@ -20778,6 +20776,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -20799,7 +20809,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { @@ -21033,7 +21043,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "HorizontalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetHorizontalSpeed", "sentence": "", "events": [ @@ -21114,7 +21123,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalSpeed", "sentence": "", "events": [ @@ -21195,7 +21203,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMin", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMin", "sentence": "", "events": [ @@ -21276,7 +21283,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMax", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMax", "sentence": "", "events": [ @@ -21357,7 +21363,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "OffsetZ", - "group": "First person camera mouse mapper configuration", "name": "SetOffsetZ", "sentence": "", "events": [ @@ -21394,57 +21399,6 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "doStepPreEvents" - }, - { - "folderName": "Layers and cameras", - "children": [ - { - "functionName": "LookFromObjectEyes" - } - ] - }, - { - "folderName": "First person camera mouse mapper configuration", - "children": [ - { - "functionName": "HorizontalSpeed" - }, - { - "functionName": "SetHorizontalSpeed" - }, - { - "functionName": "VerticalSpeed" - }, - { - "functionName": "SetVerticalSpeed" - }, - { - "functionName": "VerticalAngleMin" - }, - { - "functionName": "SetVerticalAngleMin" - }, - { - "functionName": "VerticalAngleMax" - }, - { - "functionName": "SetVerticalAngleMax" - }, - { - "functionName": "OffsetZ" - }, - { - "functionName": "SetOffsetZ" - } - ] - } - ] - }, "propertyDescriptors": [ { "value": "", @@ -21498,32 +21452,7 @@ "name": "ShouldLock" } ], - "propertiesFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "propertyName": "Object3D" - }, - { - "propertyName": "HorizontalSpeed" - }, - { - "propertyName": "VerticalSpeed" - }, - { - "propertyName": "VerticalAngleMin" - }, - { - "propertyName": "VerticalAngleMax" - }, - { - "propertyName": "OffsetZ" - }, - { - "propertyName": "ShouldLock" - } - ] - } + "sharedPropertyDescriptors": [] } ], "eventsBasedObjects": [] diff --git a/examples/3d-shooting-gallery/3d-shooting-gallery.json b/examples/3d-shooting-gallery/3d-shooting-gallery.json index 4444bba1b..3fd54aa75 100644 --- a/examples/3d-shooting-gallery/3d-shooting-gallery.json +++ b/examples/3d-shooting-gallery/3d-shooting-gallery.json @@ -11666,8 +11666,8 @@ "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMTksMTkgMjQsMTkgMjQsMjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSI2LDIzIDExLDIzIDExLDE4ICIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTI0LDE5LjRjLTAuNywwLjgtMS40LDEuNi0yLjIsMi40Yy03LDctMTUuMywxMC4yLTE4LjUsN3MtMC4xLTExLjUsNy0xOC41czE1LjMtMTAuMiwxOC41LTcNCgljMS40LDEuNCwxLjYsMy42LDAuOCw2LjMiLz4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xMSwyMi41Yy0wLjMtMC4yLTAuNS0wLjUtMC44LTAuOGMtNy03LTEwLjItMTUuMy03LTE4LjVzMTEuNS0wLjEsMTguNSw3czEwLjIsMTUuMyw3LDE4LjUNCgljLTEuNywxLjctNC44LDEuNi04LjQsMC4xIi8+DQo8L3N2Zz4NCg==", "name": "MousePointerLock", "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Virtual Reality/Virtual Reality_360_rotate_vr_movement.svg", - "shortDescription": "This behavior removes the limit on the distance the mouse can move and hides the cursor.", - "version": "0.3.0", + "shortDescription": "Lock and hide mouse pointer for unlimited movement (e.g., FPS mouse look).", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -11715,7 +11715,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -11963,16 +11963,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -12119,7 +12149,7 @@ "functionType": "Condition", "name": "isEmulatingPointerLock", "private": true, - "sentence": "The mouse pointer is lock is emulated", + "sentence": "The mouse pointer lock is emulated", "events": [ { "type": "BuiltinCommonInstructions::Standard", @@ -12155,6 +12185,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -12461,6 +12516,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -12482,7 +12549,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { diff --git a/examples/3d-tile-based-city-builder/3d-tile-based-city-builder.json b/examples/3d-tile-based-city-builder/3d-tile-based-city-builder.json index 6bc9b62fe..500effaf1 100644 --- a/examples/3d-tile-based-city-builder/3d-tile-based-city-builder.json +++ b/examples/3d-tile-based-city-builder/3d-tile-based-city-builder.json @@ -2074,8 +2074,8 @@ "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMTksMTkgMjQsMTkgMjQsMjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSI2LDIzIDExLDIzIDExLDE4ICIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTI0LDE5LjRjLTAuNywwLjgtMS40LDEuNi0yLjIsMi40Yy03LDctMTUuMywxMC4yLTE4LjUsN3MtMC4xLTExLjUsNy0xOC41czE1LjMtMTAuMiwxOC41LTcNCgljMS40LDEuNCwxLjYsMy42LDAuOCw2LjMiLz4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xMSwyMi41Yy0wLjMtMC4yLTAuNS0wLjUtMC44LTAuOGMtNy03LTEwLjItMTUuMy03LTE4LjVzMTEuNS0wLjEsMTguNSw3czEwLjIsMTUuMyw3LDE4LjUNCgljLTEuNywxLjctNC44LDEuNi04LjQsMC4xIi8+DQo8L3N2Zz4NCg==", "name": "MousePointerLock", "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Virtual Reality/Virtual Reality_360_rotate_vr_movement.svg", - "shortDescription": "This behavior removes the limit on the distance the mouse can move and hides the cursor.", - "version": "0.3.0", + "shortDescription": "Lock and hide mouse pointer for unlimited movement (e.g., FPS mouse look).", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -2123,7 +2123,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -2371,16 +2371,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -2527,7 +2557,7 @@ "functionType": "Condition", "name": "isEmulatingPointerLock", "private": true, - "sentence": "The mouse pointer is lock is emulated", + "sentence": "The mouse pointer lock is emulated", "events": [ { "type": "BuiltinCommonInstructions::Standard", @@ -2563,6 +2593,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -2869,6 +2924,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -2890,7 +2957,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { diff --git a/examples/3d-wall-sliding/3d-wall-sliding.json b/examples/3d-wall-sliding/3d-wall-sliding.json index 859560a58..a96e2b495 100644 --- a/examples/3d-wall-sliding/3d-wall-sliding.json +++ b/examples/3d-wall-sliding/3d-wall-sliding.json @@ -3714,8 +3714,8 @@ "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMTksMTkgMjQsMTkgMjQsMjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSI2LDIzIDExLDIzIDExLDE4ICIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTI0LDE5LjRjLTAuNywwLjgtMS40LDEuNi0yLjIsMi40Yy03LDctMTUuMywxMC4yLTE4LjUsN3MtMC4xLTExLjUsNy0xOC41czE1LjMtMTAuMiwxOC41LTcNCgljMS40LDEuNCwxLjYsMy42LDAuOCw2LjMiLz4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xMSwyMi41Yy0wLjMtMC4yLTAuNS0wLjUtMC44LTAuOGMtNy03LTEwLjItMTUuMy03LTE4LjVzMTEuNS0wLjEsMTguNSw3czEwLjIsMTUuMyw3LDE4LjUNCgljLTEuNywxLjctNC44LDEuNi04LjQsMC4xIi8+DQo8L3N2Zz4NCg==", "name": "MousePointerLock", "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Virtual Reality/Virtual Reality_360_rotate_vr_movement.svg", - "shortDescription": "This behavior removes the limit on the distance the mouse can move and hides the cursor.", - "version": "0.3.0", + "shortDescription": "Lock and hide mouse pointer for unlimited movement (e.g., FPS mouse look).", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -3763,7 +3763,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -4011,16 +4011,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -4167,7 +4197,7 @@ "functionType": "Condition", "name": "isEmulatingPointerLock", "private": true, - "sentence": "The mouse pointer is lock is emulated", + "sentence": "The mouse pointer lock is emulated", "events": [ { "type": "BuiltinCommonInstructions::Standard", @@ -4203,6 +4233,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -4509,6 +4564,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -4530,7 +4597,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { diff --git a/examples/platformer-3d-wipeout/platformer-3d-wipeout.json b/examples/platformer-3d-wipeout/platformer-3d-wipeout.json index 14be783a7..0c0234bee 100644 --- a/examples/platformer-3d-wipeout/platformer-3d-wipeout.json +++ b/examples/platformer-3d-wipeout/platformer-3d-wipeout.json @@ -11475,8 +11475,8 @@ "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMTksMTkgMjQsMTkgMjQsMjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSI2LDIzIDExLDIzIDExLDE4ICIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTI0LDE5LjRjLTAuNywwLjgtMS40LDEuNi0yLjIsMi40Yy03LDctMTUuMywxMC4yLTE4LjUsN3MtMC4xLTExLjUsNy0xOC41czE1LjMtMTAuMiwxOC41LTcNCgljMS40LDEuNCwxLjYsMy42LDAuOCw2LjMiLz4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xMSwyMi41Yy0wLjMtMC4yLTAuNS0wLjUtMC44LTAuOGMtNy03LTEwLjItMTUuMy03LTE4LjVzMTEuNS0wLjEsMTguNSw3czEwLjIsMTUuMyw3LDE4LjUNCgljLTEuNywxLjctNC44LDEuNi04LjQsMC4xIi8+DQo8L3N2Zz4NCg==", "name": "MousePointerLock", "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Virtual Reality/Virtual Reality_360_rotate_vr_movement.svg", - "shortDescription": "This behavior removes the limit on the distance the mouse can move and hides the cursor.", - "version": "0.3.0", + "shortDescription": "Lock and hide mouse pointer for unlimited movement (e.g., FPS mouse look).", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -11524,7 +11524,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -11772,16 +11772,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -11928,7 +11958,7 @@ "functionType": "Condition", "name": "isEmulatingPointerLock", "private": true, - "sentence": "The mouse pointer is lock is emulated", + "sentence": "The mouse pointer lock is emulated", "events": [ { "type": "BuiltinCommonInstructions::Standard", @@ -11964,6 +11994,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -12270,6 +12325,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -12291,7 +12358,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { @@ -12934,32 +13001,7 @@ "name": "ShouldLock" } ], - "propertiesFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "propertyName": "Object3D" - }, - { - "propertyName": "HorizontalSpeed" - }, - { - "propertyName": "VerticalSpeed" - }, - { - "propertyName": "VerticalAngleMin" - }, - { - "propertyName": "VerticalAngleMax" - }, - { - "propertyName": "OffsetZ" - }, - { - "propertyName": "ShouldLock" - } - ] - } + "sharedPropertyDescriptors": [] } ], "eventsBasedObjects": [] diff --git a/examples/starting-3D-platformer/starting-3D-platformer.json b/examples/starting-3D-platformer/starting-3D-platformer.json index 3b068b660..2a2afb982 100644 --- a/examples/starting-3D-platformer/starting-3D-platformer.json +++ b/examples/starting-3D-platformer/starting-3D-platformer.json @@ -1517,6 +1517,51 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Start with touch controls on touchscreens." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "SystemInfo::HasTouchScreen" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::SetChecked" + }, + "parameters": [ + "ControlsToggle", + "yes", + "" + ] + } + ] + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -2063,18 +2108,11 @@ "conditions": [ { "type": { - "value": "MouseButtonFromTextPressed" + "value": "HasAnyTouchOrMouseStarted" }, "parameters": [ - "", - "\"Left\"" + "" ] - }, - { - "type": { - "value": "BuiltinCommonInstructions::Once" - }, - "parameters": [] } ], "actions": [], @@ -2093,17 +2131,6 @@ "\"Left\"", "" ] - }, - { - "type": { - "inverted": true, - "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" - }, - "parameters": [ - "JumpButton", - "MultitouchButton", - "" - ] } ], "actions": [ @@ -15268,7 +15295,6 @@ { "author": "PANDAKO-GitHub", "category": "Input", - "dimension": "", "extensionNamespace": "", "fullName": "Mouse Pointer Lock", "gdevelopVersion": ">=5.5.222", @@ -15276,8 +15302,8 @@ "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMTksMTkgMjQsMTkgMjQsMjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSI2LDIzIDExLDIzIDExLDE4ICIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTI0LDE5LjRjLTAuNywwLjgtMS40LDEuNi0yLjIsMi40Yy03LDctMTUuMywxMC4yLTE4LjUsN3MtMC4xLTExLjUsNy0xOC41czE1LjMtMTAuMiwxOC41LTcNCgljMS40LDEuNCwxLjYsMy42LDAuOCw2LjMiLz4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xMSwyMi41Yy0wLjMtMC4yLTAuNS0wLjUtMC44LTAuOGMtNy03LTEwLjItMTUuMy03LTE4LjVzMTEuNS0wLjEsMTguNSw3czEwLjIsMTUuMyw3LDE4LjUNCgljLTEuNywxLjctNC44LDEuNi04LjQsMC4xIi8+DQo8L3N2Zz4NCg==", "name": "MousePointerLock", "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Virtual Reality/Virtual Reality_360_rotate_vr_movement.svg", - "shortDescription": "This behavior removes the limit on the distance the mouse can move and hides the cursor.", - "version": "0.3.0", + "shortDescription": "Lock and hide mouse pointer for unlimited movement (e.g., FPS mouse look).", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -15325,7 +15351,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -15573,16 +15599,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -15729,7 +15785,7 @@ "functionType": "Condition", "name": "isEmulatingPointerLock", "private": true, - "sentence": "The mouse pointer is lock is emulated", + "sentence": "The mouse pointer lock is emulated", "events": [ { "type": "BuiltinCommonInstructions::Standard", @@ -15765,6 +15821,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -16046,68 +16127,12 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "onFirstSceneLoaded" - }, - { - "functionName": "onScenePostEvents" - }, - { - "functionName": "RequestPointerLock" - }, - { - "functionName": "ExitPointerLock" - }, - { - "functionName": "isPointerLocked" - }, - { - "functionName": "isPointerActuallyLocked" - }, - { - "functionName": "isEmulatingPointerLock" - }, - { - "functionName": "IsMoving" - }, - { - "functionName": "MovementX" - }, - { - "functionName": "SetMovementX" - }, - { - "functionName": "MovementY" - }, - { - "functionName": "SetMovementY" - }, - { - "functionName": "TouchX" - }, - { - "functionName": "TouchY" - }, - { - "functionName": "TouchSpeedFactor" - }, - { - "functionName": "SetTouchSpeedFactor" - } - ] - }, "eventsBasedBehaviors": [ { "description": "Control camera rotations with a mouse.", "fullName": "First person camera mouse mapper", - "helpPath": "", - "iconUrl": "", "name": "FirstPersonPointerMapper", "objectType": "", - "previewIconUrl": "", "eventsFunctions": [ { "fullName": "", @@ -16127,6 +16152,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -16148,7 +16185,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { @@ -16382,7 +16419,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "HorizontalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetHorizontalSpeed", "sentence": "", "events": [ @@ -16463,7 +16499,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalSpeed", "sentence": "", "events": [ @@ -16544,7 +16579,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMin", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMin", "sentence": "", "events": [ @@ -16625,7 +16659,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMax", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMax", "sentence": "", "events": [ @@ -16706,7 +16739,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "OffsetZ", - "group": "First person camera mouse mapper configuration", "name": "SetOffsetZ", "sentence": "", "events": [ @@ -16743,57 +16775,6 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "doStepPreEvents" - }, - { - "folderName": "Layers and cameras", - "children": [ - { - "functionName": "LookFromObjectEyes" - } - ] - }, - { - "folderName": "First person camera mouse mapper configuration", - "children": [ - { - "functionName": "HorizontalSpeed" - }, - { - "functionName": "SetHorizontalSpeed" - }, - { - "functionName": "VerticalSpeed" - }, - { - "functionName": "SetVerticalSpeed" - }, - { - "functionName": "VerticalAngleMin" - }, - { - "functionName": "SetVerticalAngleMin" - }, - { - "functionName": "VerticalAngleMax" - }, - { - "functionName": "SetVerticalAngleMax" - }, - { - "functionName": "OffsetZ" - }, - { - "functionName": "SetOffsetZ" - } - ] - } - ] - }, "propertyDescriptors": [ { "value": "", @@ -16847,32 +16828,7 @@ "name": "ShouldLock" } ], - "propertiesFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "propertyName": "Object3D" - }, - { - "propertyName": "HorizontalSpeed" - }, - { - "propertyName": "VerticalSpeed" - }, - { - "propertyName": "VerticalAngleMin" - }, - { - "propertyName": "VerticalAngleMax" - }, - { - "propertyName": "OffsetZ" - }, - { - "propertyName": "ShouldLock" - } - ] - } + "sharedPropertyDescriptors": [] } ], "eventsBasedObjects": [] @@ -27659,7 +27615,7 @@ "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", + "version": "0.2.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.", "", @@ -28759,8 +28715,17 @@ "vTerrainLocalXY = position.xy;", "vTerrainLocalZ = position.z;", "vTerrainWorldXY = (modelMatrix * vec4(transformed, 1.0)).xy * terrainWorldScale;", - "vTerrainUp = normalize(transpose(inverse(mat3(modelMatrix))) * objectNormal).z;", + "// The world normal, from the camera space normal (computed by Three.js with a", + "// matrix inverted on the CPU), instead of inverting a matrix for each vertex.", + "vTerrainUp = inverseTransformDirection(transformedNormal, viewMatrix).z;", "`;", + "/** @param {(layer: number) => string} getLayerCode */", + "const forEachLayerCode = (getLayerCode) => [0, 1, 2, 3].map(getLayerCode).join('\\n');", + "", + "// Noise is read from a texture of random values, repeated every", + "// NOISE_MAP_SIZE units of noise: 1 texture read instead of 16 hashes.", + "const NOISE_MAP_SIZE = 256;", + "", "const fragmentShaderDeclarations = `", "uniform sampler2D terrainSplatMap;", "uniform vec2 terrainSplatUvScaleAndOffset;", @@ -28769,11 +28734,11 @@ "uniform sampler2D terrainLayerMap1;", "uniform sampler2D terrainLayerMap2;", "uniform sampler2D terrainLayerMap3;", + "uniform vec3 terrainLayerAverageColors[4];", "uniform sampler2D terrainLayerNormalMap0;", "uniform sampler2D terrainLayerNormalMap1;", "uniform sampler2D terrainLayerNormalMap2;", "uniform sampler2D terrainLayerNormalMap3;", - "uniform float terrainHasNormalMaps;", "uniform float terrainNormalMapStrength;", "uniform float terrainTextureSize;", "uniform float terrainHeightBlending;", @@ -28782,15 +28747,13 @@ "uniform vec2 terrainCliffCosines;", "uniform vec4 terrainBrush;", "uniform sampler2D terrainColorMap;", + "uniform sampler2D terrainNoiseMap;", "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. */", @@ -28802,15 +28765,16 @@ " copyBump.xy = transpose(terrainCopyRotation) * copyBump.xy;", " return mix(bump, copyBump, patches);", "}", - "float terrainNoise(vec2 p) {", + "/**", + " * 4 independent value noises (one by channel), from 0 to 1, smoothly varying", + " * between random values at integer positions.", + " */", + "vec4 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", - " );", + " vec2 f = p - i;", + " // The read position is moved so that the linear interpolation of the", + " // texture between 2 random values becomes a smooth one.", + " return texture2D(terrainNoiseMap, (i + f * f * (3.0 - 2.0 * f) + 0.5) / ${NOISE_MAP_SIZE}.0);", "}", "`;", "// Holes are samples without any painted layer: the interpolated sum of the", @@ -28828,15 +28792,11 @@ "varying vec2 vTerrainLocalXY;", "`;", "", + "// Layers without texture, normal map, height blending or texture variation", + "// are compiled out (see TerrainRenderer.updateMaterial).", "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)", - ");", + "vec4 terrainJitter = terrainNoise(vTerrainWorldXY / 24.0);", "// 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;", @@ -28847,48 +28807,61 @@ "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;", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "vec3 terrainTexture${layer} = texture2D(terrainLayerMap${layer}, terrainUv).rgb;", + "#else", + "vec3 terrainTexture${layer} = vec3(1.0);", + "#endif`", + ")}", "", "// 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);", - "}", + "#ifdef TERRAIN_TEXTURE_VARIATION", + "// 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).x);", + "terrainUv2 = terrainCopyRotation * terrainUv + vec2(0.31, 0.73);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "terrainTexture${layer} = mix(terrainTexture${layer}, texture2D(terrainLayerMap${layer}, terrainUv2).rgb, terrainPatches);", + "#endif`", + ")}", + "terrainColorVariation += terrainTextureVariation * 0.3 *", + " (terrainNoise(terrainUv * 0.11).y + 0.5 * terrainNoise(terrainUv * 0.37 + 3.1).z - 0.75);", + "#endif", "", "// 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);", - "}", + "#ifdef TERRAIN_HEIGHT_BLENDING", + "// Averages are computed once on the CPU, or read from the smallest mipmap", + "// when the image can't be read by the CPU.", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MIPMAP_AVERAGE_${layer}", + "vec3 terrainAverage${layer} = textureLod(terrainLayerMap${layer}, vec2(0.5), 16.0).rgb;", + "#else", + "vec3 terrainAverage${layer} = terrainLayerAverageColors[${layer}];", + "#endif`", + ")}", + "const vec3 terrainLuminance = vec3(0.299, 0.587, 0.114);", + "vec4 terrainHeights = vec4(", + " dot(terrainTexture0 - terrainAverage0, terrainLuminance),", + " dot(terrainTexture1 - terrainAverage1, terrainLuminance),", + " dot(terrainTexture2 - terrainAverage2, terrainLuminance),", + " dot(terrainTexture3 - terrainAverage3, 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);", + "#endif", "", "vec3 terrainColor = terrainColorVariation * (", " terrainWeights.x * terrainLayerColors[0] * terrainTexture0 +", @@ -28920,26 +28893,30 @@ "`;", "", "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;", + "#ifdef TERRAIN_NORMAL_MAPS", + "// Layers without normal map are flat.", + "vec3 terrainMapNormal = vec3(0.0);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_NORMAL_MAP_${layer}", + "terrainMapNormal += terrainWeights[${layer}] * terrainSampleNormal(terrainLayerNormalMap${layer}, terrainUv, terrainUv2, terrainPatches);", + "#else", + "terrainMapNormal.z += terrainWeights[${layer}];", + "#endif`", + ")}", + "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);", - " // 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);", "}", + "terrainTangent = normalize(terrainTangent);", + "vec3 terrainBitangent = cross(normal, terrainTangent);", + "normal = normalize(mat3(terrainTangent, terrainBitangent, normal) * terrainMapNormal);", + "#endif", "`;", "", "let whiteTexture = null;", @@ -28960,7 +28937,30 @@ " return flatNormalTexture;", "};", "", + "let noiseTexture = null;", + "/** Random values, in 4 independent channels, read by `terrainNoise` in the shader. */", + "const getNoiseTexture = () => {", + " if (!noiseTexture) {", + " const random = createRandom(1);", + " const bytes = new Uint8Array(NOISE_MAP_SIZE * NOISE_MAP_SIZE * 4);", + " for (let index = 0; index < bytes.length; index++) {", + " bytes[index] = Math.floor(random() * 256);", + " }", + " noiseTexture = new THREE.DataTexture(bytes, NOISE_MAP_SIZE, NOISE_MAP_SIZE);", + " noiseTexture.wrapS = THREE.RepeatWrapping;", + " noiseTexture.wrapT = THREE.RepeatWrapping;", + " // No mipmaps: the shader moves the read position between samples.", + " noiseTexture.magFilter = THREE.LinearFilter;", + " noiseTexture.minFilter = THREE.LinearFilter;", + " noiseTexture.needsUpdate = true;", + " }", + " return noiseTexture;", + "};", + "", + "const DEFAULT_ANISOTROPY = 4;", + "/** @type {WeakMap>} */", "const layerTextures = new WeakMap();", + "/** @type {WeakMap>} */", "const layerNormalMaps = new WeakMap();", "/**", " * Textures of the image manager have no mipmaps: they would shimmer when", @@ -28968,12 +28968,18 @@ " * @param {gdjs.RuntimeGame} game", " * @param {string} resourceName", " * @param {boolean} isNormalMap Normal maps are directions, not colors.", + " * @param {number} anisotropy", " */", - "const getLayerTexture = (game, resourceName, isNormalMap) => {", + "const getLayerTexture = (game, resourceName, isNormalMap, anisotropy) => {", " if (!resourceName) return isNormalMap ? getFlatNormalTexture() : getWhiteTexture();", " const imageTexture = game.getImageManager().getThreeTexture(resourceName);", - " const textures = isNormalMap ? layerNormalMaps : layerTextures;", - " let texture = textures.get(imageTexture);", + " const texturesByImage = isNormalMap ? layerNormalMaps : layerTextures;", + " let texturesByAnisotropy = texturesByImage.get(imageTexture);", + " if (!texturesByAnisotropy) {", + " texturesByAnisotropy = new Map();", + " texturesByImage.set(imageTexture, texturesByAnisotropy);", + " }", + " let texture = texturesByAnisotropy.get(anisotropy);", " if (!texture) {", " texture = imageTexture.clone();", " if (isNormalMap) texture.colorSpace = THREE.NoColorSpace;", @@ -28981,13 +28987,71 @@ " texture.wrapT = THREE.RepeatWrapping;", " texture.minFilter = THREE.LinearMipmapLinearFilter;", " texture.generateMipmaps = true;", - " texture.anisotropy = 4;", + " texture.anisotropy = anisotropy;", " texture.needsUpdate = true;", - " textures.set(imageTexture, texture);", + " texturesByAnisotropy.set(anisotropy, texture);", " }", " return texture;", "};", "", + "/** @type {Float32Array | null} */", + "let srgbToLinearValues = null;", + "/** @type {WeakMap} */", + "const linearAverageColors = new WeakMap();", + "/**", + " * @param {THREE.Texture} texture", + " * @returns {THREE.Vector3 | null} The average color of the texture, in the", + " * linear color space (like colors read by shaders), or null if the image", + " * can't be read: not loaded yet, or from another domain without CORS.", + " */", + "const getLinearAverageColor = (texture) => {", + " const image = texture.image;", + " if (!image) return null;", + " const cachedColor = linearAverageColors.get(image);", + " if (cachedColor) return cachedColor;", + " 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 null;", + " const size = 64;", + " let pixels;", + " try {", + " const canvas = document.createElement('canvas');", + " canvas.width = size;", + " canvas.height = size;", + " const context = canvas.getContext('2d');", + " // Downscaled with filtering, so that all pixels are counted.", + " context.imageSmoothingEnabled = true;", + " context.imageSmoothingQuality = 'high';", + " context.drawImage(image, 0, 0, size, size);", + " pixels = context.getImageData(0, 0, size, size).data;", + " } catch (error) {", + " return null;", + " }", + " if (!srgbToLinearValues) {", + " srgbToLinearValues = new Float32Array(256);", + " for (let value = 0; value < 256; value++) {", + " const srgb = value / 255;", + " srgbToLinearValues[value] = srgb <= 0.04045 ? srgb / 12.92 : Math.pow((srgb + 0.055) / 1.055, 2.4);", + " }", + " }", + " const isSrgb = texture.colorSpace === THREE.SRGBColorSpace;", + " let red = 0;", + " let green = 0;", + " let blue = 0;", + " for (let index = 0; index < pixels.length; index += 4) {", + " red += isSrgb ? srgbToLinearValues[pixels[index]] : pixels[index] / 255;", + " green += isSrgb ? srgbToLinearValues[pixels[index + 1]] : pixels[index + 1] / 255;", + " blue += isSrgb ? srgbToLinearValues[pixels[index + 2]] : pixels[index + 2] / 255;", + " }", + " const count = size * size;", + " const color = new THREE.Vector3(red / count, green / count, blue / count);", + " linearAverageColors.set(image, color);", + " return color;", + "};", + "", "/**", " * A texture of 4 bytes per sample of a grid of `size * size` samples (layer", " * weights, tint and wetness...), interpolated between samples.", @@ -29046,11 +29110,14 @@ " terrainLayerMap1: { value: getWhiteTexture() },", " terrainLayerMap2: { value: getWhiteTexture() },", " terrainLayerMap3: { value: getWhiteTexture() },", + " // Used by the height blending, in the linear color space.", + " terrainLayerAverageColors: {", + " value: [new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1)],", + " },", " terrainLayerNormalMap0: { value: getFlatNormalTexture() },", " terrainLayerNormalMap1: { value: getFlatNormalTexture() },", " terrainLayerNormalMap2: { value: getFlatNormalTexture() },", " terrainLayerNormalMap3: { value: getFlatNormalTexture() },", - " terrainHasNormalMaps: { value: 0 },", " terrainNormalMapStrength: { value: 1 },", " terrainTextureSize: { value: 256 },", " terrainHeightBlending: { value: 0 },", @@ -29059,6 +29126,7 @@ " terrainCliffCosines: { value: new THREE.Vector2() },", " terrainBrush: { value: new THREE.Vector4() },", " terrainColorMap: { value: getWhiteTexture() },", + " terrainNoiseMap: { value: getNoiseTexture() },", " // 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.", @@ -29078,8 +29146,12 @@ " ['#include ', '#include \\n' + fragmentShaderNormalCode],", " ], 'Terrain3D');", " };", - " this.material.customProgramCacheKey = () => (this.hasHoles ? 'Terrain3DWithHoles' : 'Terrain3D');", " this.hasHoles = false;", + " /** The defines of the features used by the terrain (see `updateMaterial`), without holes. */", + " this.featureDefines = [];", + " /** All the defines of the shader, separated by semicolons. */", + " this.shaderKey = '';", + " this.material.customProgramCacheKey = () => 'Terrain3D;' + this.shaderKey;", " // Without them, holes would cast shadows.", " this.depthMaterial = this._createHoleDepthMaterial(new THREE.MeshDepthMaterial());", " this.distanceMaterial = this._createHoleDepthMaterial(new THREE.MeshDistanceMaterial());", @@ -29116,11 +29188,28 @@ " setHasHoles(hasHoles) {", " if (hasHoles === this.hasHoles) return;", " this.hasHoles = hasHoles;", + " this._updateShaderDefines();", + " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", + " }", + "", + " /**", + " * Compile the shader only with the features used by the terrain. It is", + " * compiled again only when they change: properties only change in the", + " * editor, holes can be made by actions.", + " */", + " _updateShaderDefines() {", + " const defines = this.hasHoles ? [...this.featureDefines, 'TERRAIN_HOLES'] : this.featureDefines;", + " const shaderKey = defines.join(';');", + " if (shaderKey === this.shaderKey) return;", " // The material has its own defines (STANDARD).", - " if (hasHoles) this.material.defines.TERRAIN_HOLES = '';", - " else delete this.material.defines.TERRAIN_HOLES;", + " for (const name of this.shaderKey ? this.shaderKey.split(';') : []) {", + " delete this.material.defines[name];", + " }", + " for (const name of defines) {", + " this.material.defines[name] = '';", + " }", + " this.shaderKey = shaderKey;", " this.material.needsUpdate = true;", - " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", " }", "", " /** @param {THREE.Mesh} mesh */", @@ -29352,16 +29441,38 @@ " object._getLayer3NormalMap(),", " object._getLayer4NormalMap(),", " ];", + " const anisotropy = Math.max(parseInt(object._getAnisotropy(), 10) || DEFAULT_ANISOTROPY, 1);", + " const heightBlending = clamp01(object._getHeightBlending());", + " const textureVariation = clamp01(object._getTextureVariation());", + " const featureDefines = [];", " 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);", + " const layerTexture = getLayerTexture(game, textures[layer], false, anisotropy);", + " this.uniforms['terrainLayerMap' + layer].value = layerTexture;", + " this.uniforms['terrainLayerNormalMap' + layer].value = getLayerTexture(game, normalMaps[layer], true, anisotropy);", + " const averageColor = this.uniforms.terrainLayerAverageColors.value[layer];", + " if (textures[layer]) {", + " featureDefines.push('TERRAIN_LAYER_MAP_' + layer);", + " const linearAverageColor = heightBlending > 0 ? getLinearAverageColor(layerTexture) : null;", + " if (linearAverageColor) {", + " averageColor.copy(linearAverageColor);", + " } else if (heightBlending > 0) {", + " featureDefines.push('TERRAIN_LAYER_MIPMAP_AVERAGE_' + layer);", + " }", + " } else {", + " averageColor.set(1, 1, 1);", + " }", + " if (normalMaps[layer]) featureDefines.push('TERRAIN_LAYER_NORMAL_MAP_' + layer);", " }", - " this.uniforms.terrainHasNormalMaps.value = normalMaps.some((normalMap) => !!normalMap) ? 1 : 0;", + " if (normalMaps.some((normalMap) => !!normalMap)) featureDefines.push('TERRAIN_NORMAL_MAPS');", + " if (heightBlending > 0) featureDefines.push('TERRAIN_HEIGHT_BLENDING');", + " if (textureVariation > 0) featureDefines.push('TERRAIN_TEXTURE_VARIATION');", + " this.featureDefines = featureDefines;", + " this._updateShaderDefines();", " 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());", + " this.uniforms.terrainHeightBlending.value = heightBlending;", + " this.uniforms.terrainTextureVariation.value = textureVariation;", "", " const cliffLayer = parseInt(object._getCliffLayer(), 10);", " const cliffAngle = gdjs.toRad(object._getCliffAngle());", @@ -30279,40 +30390,21 @@ " return typeof scene.getRenderer3DWorldScale === 'function' ? scene.getRenderer3DWorldScale() : 1;", "};", "", + "/** @type {WeakMap} */", "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).", + " * @returns {THREE.Color} The average color of the texture (white when it can't be read).", " */", "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 linearAverageColor = getLinearAverageColor(texture);", + " if (!linearAverageColor) return white || (white = new THREE.Color(1, 1, 1));", + " let color = averageColors.get(linearAverageColor);", + " if (!color) {", + " color = new THREE.Color(linearAverageColor.x, linearAverageColor.y, linearAverageColor.z);", + " averageColors.set(linearAverageColor, color);", " }", - " 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;", "};", "", @@ -33926,6 +34018,37 @@ "group": "Rendering", "name": "ReceiveShadow" }, + { + "value": "4", + "type": "Choice", + "label": "Anisotropic filtering", + "description": "Keeps layer textures sharp where the ground is seen at a low angle (like from a car). Lower is faster on slow graphics cards, but blurs the ground in the distance.", + "group": "Rendering", + "choices": [ + { + "label": "None (fastest)", + "value": "1" + }, + { + "label": "2x", + "value": "2" + }, + { + "label": "4x", + "value": "4" + }, + { + "label": "8x", + "value": "8" + }, + { + "label": "16x (sharpest)", + "value": "16" + } + ], + "advanced": true, + "name": "Anisotropy" + }, { "value": "", "type": "String", @@ -34039,6 +34162,9 @@ }, { "propertyName": "ReceiveShadow" + }, + { + "propertyName": "Anisotropy" } ] }, diff --git a/examples/starting-3D-platformer/theme-slots.json b/examples/starting-3D-platformer/theme-slots.json index 1b6aeb8d6..a0ae2cbf5 100644 --- a/examples/starting-3D-platformer/theme-slots.json +++ b/examples/starting-3D-platformer/theme-slots.json @@ -1,12 +1,11 @@ { "version": 1, "objects": { - "scene:Game Scene/Ground": { - "front": "env.ground", - "left": "env.wall", - "right": "env.wall", - "top": "env.wall", - "bottom": "env.wall" + "scene:Game Scene/Terrain": { + "Layer1Texture": { + "slot": "terrain.ground", + "normalMap": "Layer1NormalMap" + } }, "scene:Game Scene/Player": "character.player", "scene:Game Scene/Obstacle": { @@ -27,5 +26,10 @@ }, "effects": { "effect:Game Scene//SkyBox": "sky.day" - } + }, + "ignoredObjects": [ + "object:Terrain3D::Terrain3D/AreaPlaceholder", + "object:Terrain3D::TerrainGrass/AreaPlaceholder", + "object:Terrain3D::ScatteredModels/AreaPlaceholder" + ] } diff --git a/examples/starting-3d-car-racing/starting-3d-car-racing.json b/examples/starting-3d-car-racing/starting-3d-car-racing.json index 57e15dc05..066540e30 100644 --- a/examples/starting-3d-car-racing/starting-3d-car-racing.json +++ b/examples/starting-3d-car-racing/starting-3d-car-racing.json @@ -13158,7 +13158,7 @@ "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", + "version": "0.2.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.", "", @@ -14258,8 +14258,17 @@ "vTerrainLocalXY = position.xy;", "vTerrainLocalZ = position.z;", "vTerrainWorldXY = (modelMatrix * vec4(transformed, 1.0)).xy * terrainWorldScale;", - "vTerrainUp = normalize(transpose(inverse(mat3(modelMatrix))) * objectNormal).z;", + "// The world normal, from the camera space normal (computed by Three.js with a", + "// matrix inverted on the CPU), instead of inverting a matrix for each vertex.", + "vTerrainUp = inverseTransformDirection(transformedNormal, viewMatrix).z;", "`;", + "/** @param {(layer: number) => string} getLayerCode */", + "const forEachLayerCode = (getLayerCode) => [0, 1, 2, 3].map(getLayerCode).join('\\n');", + "", + "// Noise is read from a texture of random values, repeated every", + "// NOISE_MAP_SIZE units of noise: 1 texture read instead of 16 hashes.", + "const NOISE_MAP_SIZE = 256;", + "", "const fragmentShaderDeclarations = `", "uniform sampler2D terrainSplatMap;", "uniform vec2 terrainSplatUvScaleAndOffset;", @@ -14268,11 +14277,11 @@ "uniform sampler2D terrainLayerMap1;", "uniform sampler2D terrainLayerMap2;", "uniform sampler2D terrainLayerMap3;", + "uniform vec3 terrainLayerAverageColors[4];", "uniform sampler2D terrainLayerNormalMap0;", "uniform sampler2D terrainLayerNormalMap1;", "uniform sampler2D terrainLayerNormalMap2;", "uniform sampler2D terrainLayerNormalMap3;", - "uniform float terrainHasNormalMaps;", "uniform float terrainNormalMapStrength;", "uniform float terrainTextureSize;", "uniform float terrainHeightBlending;", @@ -14281,15 +14290,13 @@ "uniform vec2 terrainCliffCosines;", "uniform vec4 terrainBrush;", "uniform sampler2D terrainColorMap;", + "uniform sampler2D terrainNoiseMap;", "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. */", @@ -14301,15 +14308,16 @@ " copyBump.xy = transpose(terrainCopyRotation) * copyBump.xy;", " return mix(bump, copyBump, patches);", "}", - "float terrainNoise(vec2 p) {", + "/**", + " * 4 independent value noises (one by channel), from 0 to 1, smoothly varying", + " * between random values at integer positions.", + " */", + "vec4 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", - " );", + " vec2 f = p - i;", + " // The read position is moved so that the linear interpolation of the", + " // texture between 2 random values becomes a smooth one.", + " return texture2D(terrainNoiseMap, (i + f * f * (3.0 - 2.0 * f) + 0.5) / ${NOISE_MAP_SIZE}.0);", "}", "`;", "// Holes are samples without any painted layer: the interpolated sum of the", @@ -14327,15 +14335,11 @@ "varying vec2 vTerrainLocalXY;", "`;", "", + "// Layers without texture, normal map, height blending or texture variation", + "// are compiled out (see TerrainRenderer.updateMaterial).", "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)", - ");", + "vec4 terrainJitter = terrainNoise(vTerrainWorldXY / 24.0);", "// 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;", @@ -14346,48 +14350,61 @@ "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;", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "vec3 terrainTexture${layer} = texture2D(terrainLayerMap${layer}, terrainUv).rgb;", + "#else", + "vec3 terrainTexture${layer} = vec3(1.0);", + "#endif`", + ")}", "", "// 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);", - "}", + "#ifdef TERRAIN_TEXTURE_VARIATION", + "// 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).x);", + "terrainUv2 = terrainCopyRotation * terrainUv + vec2(0.31, 0.73);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "terrainTexture${layer} = mix(terrainTexture${layer}, texture2D(terrainLayerMap${layer}, terrainUv2).rgb, terrainPatches);", + "#endif`", + ")}", + "terrainColorVariation += terrainTextureVariation * 0.3 *", + " (terrainNoise(terrainUv * 0.11).y + 0.5 * terrainNoise(terrainUv * 0.37 + 3.1).z - 0.75);", + "#endif", "", "// 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);", - "}", + "#ifdef TERRAIN_HEIGHT_BLENDING", + "// Averages are computed once on the CPU, or read from the smallest mipmap", + "// when the image can't be read by the CPU.", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MIPMAP_AVERAGE_${layer}", + "vec3 terrainAverage${layer} = textureLod(terrainLayerMap${layer}, vec2(0.5), 16.0).rgb;", + "#else", + "vec3 terrainAverage${layer} = terrainLayerAverageColors[${layer}];", + "#endif`", + ")}", + "const vec3 terrainLuminance = vec3(0.299, 0.587, 0.114);", + "vec4 terrainHeights = vec4(", + " dot(terrainTexture0 - terrainAverage0, terrainLuminance),", + " dot(terrainTexture1 - terrainAverage1, terrainLuminance),", + " dot(terrainTexture2 - terrainAverage2, terrainLuminance),", + " dot(terrainTexture3 - terrainAverage3, 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);", + "#endif", "", "vec3 terrainColor = terrainColorVariation * (", " terrainWeights.x * terrainLayerColors[0] * terrainTexture0 +", @@ -14419,26 +14436,30 @@ "`;", "", "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;", + "#ifdef TERRAIN_NORMAL_MAPS", + "// Layers without normal map are flat.", + "vec3 terrainMapNormal = vec3(0.0);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_NORMAL_MAP_${layer}", + "terrainMapNormal += terrainWeights[${layer}] * terrainSampleNormal(terrainLayerNormalMap${layer}, terrainUv, terrainUv2, terrainPatches);", + "#else", + "terrainMapNormal.z += terrainWeights[${layer}];", + "#endif`", + ")}", + "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);", - " // 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);", "}", + "terrainTangent = normalize(terrainTangent);", + "vec3 terrainBitangent = cross(normal, terrainTangent);", + "normal = normalize(mat3(terrainTangent, terrainBitangent, normal) * terrainMapNormal);", + "#endif", "`;", "", "let whiteTexture = null;", @@ -14459,7 +14480,30 @@ " return flatNormalTexture;", "};", "", + "let noiseTexture = null;", + "/** Random values, in 4 independent channels, read by `terrainNoise` in the shader. */", + "const getNoiseTexture = () => {", + " if (!noiseTexture) {", + " const random = createRandom(1);", + " const bytes = new Uint8Array(NOISE_MAP_SIZE * NOISE_MAP_SIZE * 4);", + " for (let index = 0; index < bytes.length; index++) {", + " bytes[index] = Math.floor(random() * 256);", + " }", + " noiseTexture = new THREE.DataTexture(bytes, NOISE_MAP_SIZE, NOISE_MAP_SIZE);", + " noiseTexture.wrapS = THREE.RepeatWrapping;", + " noiseTexture.wrapT = THREE.RepeatWrapping;", + " // No mipmaps: the shader moves the read position between samples.", + " noiseTexture.magFilter = THREE.LinearFilter;", + " noiseTexture.minFilter = THREE.LinearFilter;", + " noiseTexture.needsUpdate = true;", + " }", + " return noiseTexture;", + "};", + "", + "const DEFAULT_ANISOTROPY = 4;", + "/** @type {WeakMap>} */", "const layerTextures = new WeakMap();", + "/** @type {WeakMap>} */", "const layerNormalMaps = new WeakMap();", "/**", " * Textures of the image manager have no mipmaps: they would shimmer when", @@ -14467,12 +14511,18 @@ " * @param {gdjs.RuntimeGame} game", " * @param {string} resourceName", " * @param {boolean} isNormalMap Normal maps are directions, not colors.", + " * @param {number} anisotropy", " */", - "const getLayerTexture = (game, resourceName, isNormalMap) => {", + "const getLayerTexture = (game, resourceName, isNormalMap, anisotropy) => {", " if (!resourceName) return isNormalMap ? getFlatNormalTexture() : getWhiteTexture();", " const imageTexture = game.getImageManager().getThreeTexture(resourceName);", - " const textures = isNormalMap ? layerNormalMaps : layerTextures;", - " let texture = textures.get(imageTexture);", + " const texturesByImage = isNormalMap ? layerNormalMaps : layerTextures;", + " let texturesByAnisotropy = texturesByImage.get(imageTexture);", + " if (!texturesByAnisotropy) {", + " texturesByAnisotropy = new Map();", + " texturesByImage.set(imageTexture, texturesByAnisotropy);", + " }", + " let texture = texturesByAnisotropy.get(anisotropy);", " if (!texture) {", " texture = imageTexture.clone();", " if (isNormalMap) texture.colorSpace = THREE.NoColorSpace;", @@ -14480,13 +14530,71 @@ " texture.wrapT = THREE.RepeatWrapping;", " texture.minFilter = THREE.LinearMipmapLinearFilter;", " texture.generateMipmaps = true;", - " texture.anisotropy = 4;", + " texture.anisotropy = anisotropy;", " texture.needsUpdate = true;", - " textures.set(imageTexture, texture);", + " texturesByAnisotropy.set(anisotropy, texture);", " }", " return texture;", "};", "", + "/** @type {Float32Array | null} */", + "let srgbToLinearValues = null;", + "/** @type {WeakMap} */", + "const linearAverageColors = new WeakMap();", + "/**", + " * @param {THREE.Texture} texture", + " * @returns {THREE.Vector3 | null} The average color of the texture, in the", + " * linear color space (like colors read by shaders), or null if the image", + " * can't be read: not loaded yet, or from another domain without CORS.", + " */", + "const getLinearAverageColor = (texture) => {", + " const image = texture.image;", + " if (!image) return null;", + " const cachedColor = linearAverageColors.get(image);", + " if (cachedColor) return cachedColor;", + " 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 null;", + " const size = 64;", + " let pixels;", + " try {", + " const canvas = document.createElement('canvas');", + " canvas.width = size;", + " canvas.height = size;", + " const context = canvas.getContext('2d');", + " // Downscaled with filtering, so that all pixels are counted.", + " context.imageSmoothingEnabled = true;", + " context.imageSmoothingQuality = 'high';", + " context.drawImage(image, 0, 0, size, size);", + " pixels = context.getImageData(0, 0, size, size).data;", + " } catch (error) {", + " return null;", + " }", + " if (!srgbToLinearValues) {", + " srgbToLinearValues = new Float32Array(256);", + " for (let value = 0; value < 256; value++) {", + " const srgb = value / 255;", + " srgbToLinearValues[value] = srgb <= 0.04045 ? srgb / 12.92 : Math.pow((srgb + 0.055) / 1.055, 2.4);", + " }", + " }", + " const isSrgb = texture.colorSpace === THREE.SRGBColorSpace;", + " let red = 0;", + " let green = 0;", + " let blue = 0;", + " for (let index = 0; index < pixels.length; index += 4) {", + " red += isSrgb ? srgbToLinearValues[pixels[index]] : pixels[index] / 255;", + " green += isSrgb ? srgbToLinearValues[pixels[index + 1]] : pixels[index + 1] / 255;", + " blue += isSrgb ? srgbToLinearValues[pixels[index + 2]] : pixels[index + 2] / 255;", + " }", + " const count = size * size;", + " const color = new THREE.Vector3(red / count, green / count, blue / count);", + " linearAverageColors.set(image, color);", + " return color;", + "};", + "", "/**", " * A texture of 4 bytes per sample of a grid of `size * size` samples (layer", " * weights, tint and wetness...), interpolated between samples.", @@ -14545,11 +14653,14 @@ " terrainLayerMap1: { value: getWhiteTexture() },", " terrainLayerMap2: { value: getWhiteTexture() },", " terrainLayerMap3: { value: getWhiteTexture() },", + " // Used by the height blending, in the linear color space.", + " terrainLayerAverageColors: {", + " value: [new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1)],", + " },", " terrainLayerNormalMap0: { value: getFlatNormalTexture() },", " terrainLayerNormalMap1: { value: getFlatNormalTexture() },", " terrainLayerNormalMap2: { value: getFlatNormalTexture() },", " terrainLayerNormalMap3: { value: getFlatNormalTexture() },", - " terrainHasNormalMaps: { value: 0 },", " terrainNormalMapStrength: { value: 1 },", " terrainTextureSize: { value: 256 },", " terrainHeightBlending: { value: 0 },", @@ -14558,6 +14669,7 @@ " terrainCliffCosines: { value: new THREE.Vector2() },", " terrainBrush: { value: new THREE.Vector4() },", " terrainColorMap: { value: getWhiteTexture() },", + " terrainNoiseMap: { value: getNoiseTexture() },", " // 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.", @@ -14577,8 +14689,12 @@ " ['#include ', '#include \\n' + fragmentShaderNormalCode],", " ], 'Terrain3D');", " };", - " this.material.customProgramCacheKey = () => (this.hasHoles ? 'Terrain3DWithHoles' : 'Terrain3D');", " this.hasHoles = false;", + " /** The defines of the features used by the terrain (see `updateMaterial`), without holes. */", + " this.featureDefines = [];", + " /** All the defines of the shader, separated by semicolons. */", + " this.shaderKey = '';", + " this.material.customProgramCacheKey = () => 'Terrain3D;' + this.shaderKey;", " // Without them, holes would cast shadows.", " this.depthMaterial = this._createHoleDepthMaterial(new THREE.MeshDepthMaterial());", " this.distanceMaterial = this._createHoleDepthMaterial(new THREE.MeshDistanceMaterial());", @@ -14615,11 +14731,28 @@ " setHasHoles(hasHoles) {", " if (hasHoles === this.hasHoles) return;", " this.hasHoles = hasHoles;", + " this._updateShaderDefines();", + " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", + " }", + "", + " /**", + " * Compile the shader only with the features used by the terrain. It is", + " * compiled again only when they change: properties only change in the", + " * editor, holes can be made by actions.", + " */", + " _updateShaderDefines() {", + " const defines = this.hasHoles ? [...this.featureDefines, 'TERRAIN_HOLES'] : this.featureDefines;", + " const shaderKey = defines.join(';');", + " if (shaderKey === this.shaderKey) return;", " // The material has its own defines (STANDARD).", - " if (hasHoles) this.material.defines.TERRAIN_HOLES = '';", - " else delete this.material.defines.TERRAIN_HOLES;", + " for (const name of this.shaderKey ? this.shaderKey.split(';') : []) {", + " delete this.material.defines[name];", + " }", + " for (const name of defines) {", + " this.material.defines[name] = '';", + " }", + " this.shaderKey = shaderKey;", " this.material.needsUpdate = true;", - " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", " }", "", " /** @param {THREE.Mesh} mesh */", @@ -14851,16 +14984,38 @@ " object._getLayer3NormalMap(),", " object._getLayer4NormalMap(),", " ];", + " const anisotropy = Math.max(parseInt(object._getAnisotropy(), 10) || DEFAULT_ANISOTROPY, 1);", + " const heightBlending = clamp01(object._getHeightBlending());", + " const textureVariation = clamp01(object._getTextureVariation());", + " const featureDefines = [];", " 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);", + " const layerTexture = getLayerTexture(game, textures[layer], false, anisotropy);", + " this.uniforms['terrainLayerMap' + layer].value = layerTexture;", + " this.uniforms['terrainLayerNormalMap' + layer].value = getLayerTexture(game, normalMaps[layer], true, anisotropy);", + " const averageColor = this.uniforms.terrainLayerAverageColors.value[layer];", + " if (textures[layer]) {", + " featureDefines.push('TERRAIN_LAYER_MAP_' + layer);", + " const linearAverageColor = heightBlending > 0 ? getLinearAverageColor(layerTexture) : null;", + " if (linearAverageColor) {", + " averageColor.copy(linearAverageColor);", + " } else if (heightBlending > 0) {", + " featureDefines.push('TERRAIN_LAYER_MIPMAP_AVERAGE_' + layer);", + " }", + " } else {", + " averageColor.set(1, 1, 1);", + " }", + " if (normalMaps[layer]) featureDefines.push('TERRAIN_LAYER_NORMAL_MAP_' + layer);", " }", - " this.uniforms.terrainHasNormalMaps.value = normalMaps.some((normalMap) => !!normalMap) ? 1 : 0;", + " if (normalMaps.some((normalMap) => !!normalMap)) featureDefines.push('TERRAIN_NORMAL_MAPS');", + " if (heightBlending > 0) featureDefines.push('TERRAIN_HEIGHT_BLENDING');", + " if (textureVariation > 0) featureDefines.push('TERRAIN_TEXTURE_VARIATION');", + " this.featureDefines = featureDefines;", + " this._updateShaderDefines();", " 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());", + " this.uniforms.terrainHeightBlending.value = heightBlending;", + " this.uniforms.terrainTextureVariation.value = textureVariation;", "", " const cliffLayer = parseInt(object._getCliffLayer(), 10);", " const cliffAngle = gdjs.toRad(object._getCliffAngle());", @@ -15778,40 +15933,21 @@ " return typeof scene.getRenderer3DWorldScale === 'function' ? scene.getRenderer3DWorldScale() : 1;", "};", "", + "/** @type {WeakMap} */", "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).", + " * @returns {THREE.Color} The average color of the texture (white when it can't be read).", " */", "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 linearAverageColor = getLinearAverageColor(texture);", + " if (!linearAverageColor) return white || (white = new THREE.Color(1, 1, 1));", + " let color = averageColors.get(linearAverageColor);", + " if (!color) {", + " color = new THREE.Color(linearAverageColor.x, linearAverageColor.y, linearAverageColor.z);", + " averageColors.set(linearAverageColor, color);", " }", - " 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;", "};", "", @@ -19425,6 +19561,37 @@ "group": "Rendering", "name": "ReceiveShadow" }, + { + "value": "4", + "type": "Choice", + "label": "Anisotropic filtering", + "description": "Keeps layer textures sharp where the ground is seen at a low angle (like from a car). Lower is faster on slow graphics cards, but blurs the ground in the distance.", + "group": "Rendering", + "choices": [ + { + "label": "None (fastest)", + "value": "1" + }, + { + "label": "2x", + "value": "2" + }, + { + "label": "4x", + "value": "4" + }, + { + "label": "8x", + "value": "8" + }, + { + "label": "16x (sharpest)", + "value": "16" + } + ], + "advanced": true, + "name": "Anisotropy" + }, { "value": "", "type": "String", @@ -19538,6 +19705,9 @@ }, { "propertyName": "ReceiveShadow" + }, + { + "propertyName": "Anisotropy" } ] }, diff --git a/examples/starting-3d-car-racing/theme-slots.json b/examples/starting-3d-car-racing/theme-slots.json index 83259ff28..962498f93 100644 --- a/examples/starting-3d-car-racing/theme-slots.json +++ b/examples/starting-3d-car-racing/theme-slots.json @@ -2,7 +2,12 @@ "version": 1, "objects": { "scene:Game Scene/Road_3D": "env.road", - "scene:Game Scene/Grass_3D": "env.ground", + "scene:Game Scene/Terrain": { + "Layer1Texture": { + "slot": "terrain.ground", + "normalMap": "Layer1NormalMap" + } + }, "scene:Game Scene/PlayerCar": "vehicle.car", "scene:Game Scene/TrafficCone": "prop.obstacle", "scene:Game Scene/FinishLine_3D": { @@ -16,5 +21,10 @@ }, "effects": { "effect:Game Scene//SkyBox": "sky.day" - } + }, + "ignoredObjects": [ + "object:Terrain3D::Terrain3D/AreaPlaceholder", + "object:Terrain3D::TerrainGrass/AreaPlaceholder", + "object:Terrain3D::ScatteredModels/AreaPlaceholder" + ] } diff --git a/examples/starting-3d-driving/starting-3d-driving.json b/examples/starting-3d-driving/starting-3d-driving.json index 8d2b0d827..ae2301a7b 100644 --- a/examples/starting-3d-driving/starting-3d-driving.json +++ b/examples/starting-3d-driving/starting-3d-driving.json @@ -12651,7 +12651,7 @@ "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", + "version": "0.2.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.", "", @@ -13751,8 +13751,17 @@ "vTerrainLocalXY = position.xy;", "vTerrainLocalZ = position.z;", "vTerrainWorldXY = (modelMatrix * vec4(transformed, 1.0)).xy * terrainWorldScale;", - "vTerrainUp = normalize(transpose(inverse(mat3(modelMatrix))) * objectNormal).z;", + "// The world normal, from the camera space normal (computed by Three.js with a", + "// matrix inverted on the CPU), instead of inverting a matrix for each vertex.", + "vTerrainUp = inverseTransformDirection(transformedNormal, viewMatrix).z;", "`;", + "/** @param {(layer: number) => string} getLayerCode */", + "const forEachLayerCode = (getLayerCode) => [0, 1, 2, 3].map(getLayerCode).join('\\n');", + "", + "// Noise is read from a texture of random values, repeated every", + "// NOISE_MAP_SIZE units of noise: 1 texture read instead of 16 hashes.", + "const NOISE_MAP_SIZE = 256;", + "", "const fragmentShaderDeclarations = `", "uniform sampler2D terrainSplatMap;", "uniform vec2 terrainSplatUvScaleAndOffset;", @@ -13761,11 +13770,11 @@ "uniform sampler2D terrainLayerMap1;", "uniform sampler2D terrainLayerMap2;", "uniform sampler2D terrainLayerMap3;", + "uniform vec3 terrainLayerAverageColors[4];", "uniform sampler2D terrainLayerNormalMap0;", "uniform sampler2D terrainLayerNormalMap1;", "uniform sampler2D terrainLayerNormalMap2;", "uniform sampler2D terrainLayerNormalMap3;", - "uniform float terrainHasNormalMaps;", "uniform float terrainNormalMapStrength;", "uniform float terrainTextureSize;", "uniform float terrainHeightBlending;", @@ -13774,15 +13783,13 @@ "uniform vec2 terrainCliffCosines;", "uniform vec4 terrainBrush;", "uniform sampler2D terrainColorMap;", + "uniform sampler2D terrainNoiseMap;", "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. */", @@ -13794,15 +13801,16 @@ " copyBump.xy = transpose(terrainCopyRotation) * copyBump.xy;", " return mix(bump, copyBump, patches);", "}", - "float terrainNoise(vec2 p) {", + "/**", + " * 4 independent value noises (one by channel), from 0 to 1, smoothly varying", + " * between random values at integer positions.", + " */", + "vec4 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", - " );", + " vec2 f = p - i;", + " // The read position is moved so that the linear interpolation of the", + " // texture between 2 random values becomes a smooth one.", + " return texture2D(terrainNoiseMap, (i + f * f * (3.0 - 2.0 * f) + 0.5) / ${NOISE_MAP_SIZE}.0);", "}", "`;", "// Holes are samples without any painted layer: the interpolated sum of the", @@ -13820,15 +13828,11 @@ "varying vec2 vTerrainLocalXY;", "`;", "", + "// Layers without texture, normal map, height blending or texture variation", + "// are compiled out (see TerrainRenderer.updateMaterial).", "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)", - ");", + "vec4 terrainJitter = terrainNoise(vTerrainWorldXY / 24.0);", "// 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;", @@ -13839,48 +13843,61 @@ "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;", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "vec3 terrainTexture${layer} = texture2D(terrainLayerMap${layer}, terrainUv).rgb;", + "#else", + "vec3 terrainTexture${layer} = vec3(1.0);", + "#endif`", + ")}", "", "// 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);", - "}", + "#ifdef TERRAIN_TEXTURE_VARIATION", + "// 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).x);", + "terrainUv2 = terrainCopyRotation * terrainUv + vec2(0.31, 0.73);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "terrainTexture${layer} = mix(terrainTexture${layer}, texture2D(terrainLayerMap${layer}, terrainUv2).rgb, terrainPatches);", + "#endif`", + ")}", + "terrainColorVariation += terrainTextureVariation * 0.3 *", + " (terrainNoise(terrainUv * 0.11).y + 0.5 * terrainNoise(terrainUv * 0.37 + 3.1).z - 0.75);", + "#endif", "", "// 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);", - "}", + "#ifdef TERRAIN_HEIGHT_BLENDING", + "// Averages are computed once on the CPU, or read from the smallest mipmap", + "// when the image can't be read by the CPU.", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MIPMAP_AVERAGE_${layer}", + "vec3 terrainAverage${layer} = textureLod(terrainLayerMap${layer}, vec2(0.5), 16.0).rgb;", + "#else", + "vec3 terrainAverage${layer} = terrainLayerAverageColors[${layer}];", + "#endif`", + ")}", + "const vec3 terrainLuminance = vec3(0.299, 0.587, 0.114);", + "vec4 terrainHeights = vec4(", + " dot(terrainTexture0 - terrainAverage0, terrainLuminance),", + " dot(terrainTexture1 - terrainAverage1, terrainLuminance),", + " dot(terrainTexture2 - terrainAverage2, terrainLuminance),", + " dot(terrainTexture3 - terrainAverage3, 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);", + "#endif", "", "vec3 terrainColor = terrainColorVariation * (", " terrainWeights.x * terrainLayerColors[0] * terrainTexture0 +", @@ -13912,26 +13929,30 @@ "`;", "", "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;", + "#ifdef TERRAIN_NORMAL_MAPS", + "// Layers without normal map are flat.", + "vec3 terrainMapNormal = vec3(0.0);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_NORMAL_MAP_${layer}", + "terrainMapNormal += terrainWeights[${layer}] * terrainSampleNormal(terrainLayerNormalMap${layer}, terrainUv, terrainUv2, terrainPatches);", + "#else", + "terrainMapNormal.z += terrainWeights[${layer}];", + "#endif`", + ")}", + "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);", - " // 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);", "}", + "terrainTangent = normalize(terrainTangent);", + "vec3 terrainBitangent = cross(normal, terrainTangent);", + "normal = normalize(mat3(terrainTangent, terrainBitangent, normal) * terrainMapNormal);", + "#endif", "`;", "", "let whiteTexture = null;", @@ -13952,7 +13973,30 @@ " return flatNormalTexture;", "};", "", + "let noiseTexture = null;", + "/** Random values, in 4 independent channels, read by `terrainNoise` in the shader. */", + "const getNoiseTexture = () => {", + " if (!noiseTexture) {", + " const random = createRandom(1);", + " const bytes = new Uint8Array(NOISE_MAP_SIZE * NOISE_MAP_SIZE * 4);", + " for (let index = 0; index < bytes.length; index++) {", + " bytes[index] = Math.floor(random() * 256);", + " }", + " noiseTexture = new THREE.DataTexture(bytes, NOISE_MAP_SIZE, NOISE_MAP_SIZE);", + " noiseTexture.wrapS = THREE.RepeatWrapping;", + " noiseTexture.wrapT = THREE.RepeatWrapping;", + " // No mipmaps: the shader moves the read position between samples.", + " noiseTexture.magFilter = THREE.LinearFilter;", + " noiseTexture.minFilter = THREE.LinearFilter;", + " noiseTexture.needsUpdate = true;", + " }", + " return noiseTexture;", + "};", + "", + "const DEFAULT_ANISOTROPY = 4;", + "/** @type {WeakMap>} */", "const layerTextures = new WeakMap();", + "/** @type {WeakMap>} */", "const layerNormalMaps = new WeakMap();", "/**", " * Textures of the image manager have no mipmaps: they would shimmer when", @@ -13960,12 +14004,18 @@ " * @param {gdjs.RuntimeGame} game", " * @param {string} resourceName", " * @param {boolean} isNormalMap Normal maps are directions, not colors.", + " * @param {number} anisotropy", " */", - "const getLayerTexture = (game, resourceName, isNormalMap) => {", + "const getLayerTexture = (game, resourceName, isNormalMap, anisotropy) => {", " if (!resourceName) return isNormalMap ? getFlatNormalTexture() : getWhiteTexture();", " const imageTexture = game.getImageManager().getThreeTexture(resourceName);", - " const textures = isNormalMap ? layerNormalMaps : layerTextures;", - " let texture = textures.get(imageTexture);", + " const texturesByImage = isNormalMap ? layerNormalMaps : layerTextures;", + " let texturesByAnisotropy = texturesByImage.get(imageTexture);", + " if (!texturesByAnisotropy) {", + " texturesByAnisotropy = new Map();", + " texturesByImage.set(imageTexture, texturesByAnisotropy);", + " }", + " let texture = texturesByAnisotropy.get(anisotropy);", " if (!texture) {", " texture = imageTexture.clone();", " if (isNormalMap) texture.colorSpace = THREE.NoColorSpace;", @@ -13973,13 +14023,71 @@ " texture.wrapT = THREE.RepeatWrapping;", " texture.minFilter = THREE.LinearMipmapLinearFilter;", " texture.generateMipmaps = true;", - " texture.anisotropy = 4;", + " texture.anisotropy = anisotropy;", " texture.needsUpdate = true;", - " textures.set(imageTexture, texture);", + " texturesByAnisotropy.set(anisotropy, texture);", " }", " return texture;", "};", "", + "/** @type {Float32Array | null} */", + "let srgbToLinearValues = null;", + "/** @type {WeakMap} */", + "const linearAverageColors = new WeakMap();", + "/**", + " * @param {THREE.Texture} texture", + " * @returns {THREE.Vector3 | null} The average color of the texture, in the", + " * linear color space (like colors read by shaders), or null if the image", + " * can't be read: not loaded yet, or from another domain without CORS.", + " */", + "const getLinearAverageColor = (texture) => {", + " const image = texture.image;", + " if (!image) return null;", + " const cachedColor = linearAverageColors.get(image);", + " if (cachedColor) return cachedColor;", + " 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 null;", + " const size = 64;", + " let pixels;", + " try {", + " const canvas = document.createElement('canvas');", + " canvas.width = size;", + " canvas.height = size;", + " const context = canvas.getContext('2d');", + " // Downscaled with filtering, so that all pixels are counted.", + " context.imageSmoothingEnabled = true;", + " context.imageSmoothingQuality = 'high';", + " context.drawImage(image, 0, 0, size, size);", + " pixels = context.getImageData(0, 0, size, size).data;", + " } catch (error) {", + " return null;", + " }", + " if (!srgbToLinearValues) {", + " srgbToLinearValues = new Float32Array(256);", + " for (let value = 0; value < 256; value++) {", + " const srgb = value / 255;", + " srgbToLinearValues[value] = srgb <= 0.04045 ? srgb / 12.92 : Math.pow((srgb + 0.055) / 1.055, 2.4);", + " }", + " }", + " const isSrgb = texture.colorSpace === THREE.SRGBColorSpace;", + " let red = 0;", + " let green = 0;", + " let blue = 0;", + " for (let index = 0; index < pixels.length; index += 4) {", + " red += isSrgb ? srgbToLinearValues[pixels[index]] : pixels[index] / 255;", + " green += isSrgb ? srgbToLinearValues[pixels[index + 1]] : pixels[index + 1] / 255;", + " blue += isSrgb ? srgbToLinearValues[pixels[index + 2]] : pixels[index + 2] / 255;", + " }", + " const count = size * size;", + " const color = new THREE.Vector3(red / count, green / count, blue / count);", + " linearAverageColors.set(image, color);", + " return color;", + "};", + "", "/**", " * A texture of 4 bytes per sample of a grid of `size * size` samples (layer", " * weights, tint and wetness...), interpolated between samples.", @@ -14038,11 +14146,14 @@ " terrainLayerMap1: { value: getWhiteTexture() },", " terrainLayerMap2: { value: getWhiteTexture() },", " terrainLayerMap3: { value: getWhiteTexture() },", + " // Used by the height blending, in the linear color space.", + " terrainLayerAverageColors: {", + " value: [new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1)],", + " },", " terrainLayerNormalMap0: { value: getFlatNormalTexture() },", " terrainLayerNormalMap1: { value: getFlatNormalTexture() },", " terrainLayerNormalMap2: { value: getFlatNormalTexture() },", " terrainLayerNormalMap3: { value: getFlatNormalTexture() },", - " terrainHasNormalMaps: { value: 0 },", " terrainNormalMapStrength: { value: 1 },", " terrainTextureSize: { value: 256 },", " terrainHeightBlending: { value: 0 },", @@ -14051,6 +14162,7 @@ " terrainCliffCosines: { value: new THREE.Vector2() },", " terrainBrush: { value: new THREE.Vector4() },", " terrainColorMap: { value: getWhiteTexture() },", + " terrainNoiseMap: { value: getNoiseTexture() },", " // 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.", @@ -14070,8 +14182,12 @@ " ['#include ', '#include \\n' + fragmentShaderNormalCode],", " ], 'Terrain3D');", " };", - " this.material.customProgramCacheKey = () => (this.hasHoles ? 'Terrain3DWithHoles' : 'Terrain3D');", " this.hasHoles = false;", + " /** The defines of the features used by the terrain (see `updateMaterial`), without holes. */", + " this.featureDefines = [];", + " /** All the defines of the shader, separated by semicolons. */", + " this.shaderKey = '';", + " this.material.customProgramCacheKey = () => 'Terrain3D;' + this.shaderKey;", " // Without them, holes would cast shadows.", " this.depthMaterial = this._createHoleDepthMaterial(new THREE.MeshDepthMaterial());", " this.distanceMaterial = this._createHoleDepthMaterial(new THREE.MeshDistanceMaterial());", @@ -14108,11 +14224,28 @@ " setHasHoles(hasHoles) {", " if (hasHoles === this.hasHoles) return;", " this.hasHoles = hasHoles;", + " this._updateShaderDefines();", + " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", + " }", + "", + " /**", + " * Compile the shader only with the features used by the terrain. It is", + " * compiled again only when they change: properties only change in the", + " * editor, holes can be made by actions.", + " */", + " _updateShaderDefines() {", + " const defines = this.hasHoles ? [...this.featureDefines, 'TERRAIN_HOLES'] : this.featureDefines;", + " const shaderKey = defines.join(';');", + " if (shaderKey === this.shaderKey) return;", " // The material has its own defines (STANDARD).", - " if (hasHoles) this.material.defines.TERRAIN_HOLES = '';", - " else delete this.material.defines.TERRAIN_HOLES;", + " for (const name of this.shaderKey ? this.shaderKey.split(';') : []) {", + " delete this.material.defines[name];", + " }", + " for (const name of defines) {", + " this.material.defines[name] = '';", + " }", + " this.shaderKey = shaderKey;", " this.material.needsUpdate = true;", - " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", " }", "", " /** @param {THREE.Mesh} mesh */", @@ -14344,16 +14477,38 @@ " object._getLayer3NormalMap(),", " object._getLayer4NormalMap(),", " ];", + " const anisotropy = Math.max(parseInt(object._getAnisotropy(), 10) || DEFAULT_ANISOTROPY, 1);", + " const heightBlending = clamp01(object._getHeightBlending());", + " const textureVariation = clamp01(object._getTextureVariation());", + " const featureDefines = [];", " 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);", + " const layerTexture = getLayerTexture(game, textures[layer], false, anisotropy);", + " this.uniforms['terrainLayerMap' + layer].value = layerTexture;", + " this.uniforms['terrainLayerNormalMap' + layer].value = getLayerTexture(game, normalMaps[layer], true, anisotropy);", + " const averageColor = this.uniforms.terrainLayerAverageColors.value[layer];", + " if (textures[layer]) {", + " featureDefines.push('TERRAIN_LAYER_MAP_' + layer);", + " const linearAverageColor = heightBlending > 0 ? getLinearAverageColor(layerTexture) : null;", + " if (linearAverageColor) {", + " averageColor.copy(linearAverageColor);", + " } else if (heightBlending > 0) {", + " featureDefines.push('TERRAIN_LAYER_MIPMAP_AVERAGE_' + layer);", + " }", + " } else {", + " averageColor.set(1, 1, 1);", + " }", + " if (normalMaps[layer]) featureDefines.push('TERRAIN_LAYER_NORMAL_MAP_' + layer);", " }", - " this.uniforms.terrainHasNormalMaps.value = normalMaps.some((normalMap) => !!normalMap) ? 1 : 0;", + " if (normalMaps.some((normalMap) => !!normalMap)) featureDefines.push('TERRAIN_NORMAL_MAPS');", + " if (heightBlending > 0) featureDefines.push('TERRAIN_HEIGHT_BLENDING');", + " if (textureVariation > 0) featureDefines.push('TERRAIN_TEXTURE_VARIATION');", + " this.featureDefines = featureDefines;", + " this._updateShaderDefines();", " 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());", + " this.uniforms.terrainHeightBlending.value = heightBlending;", + " this.uniforms.terrainTextureVariation.value = textureVariation;", "", " const cliffLayer = parseInt(object._getCliffLayer(), 10);", " const cliffAngle = gdjs.toRad(object._getCliffAngle());", @@ -15271,40 +15426,21 @@ " return typeof scene.getRenderer3DWorldScale === 'function' ? scene.getRenderer3DWorldScale() : 1;", "};", "", + "/** @type {WeakMap} */", "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).", + " * @returns {THREE.Color} The average color of the texture (white when it can't be read).", " */", "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 linearAverageColor = getLinearAverageColor(texture);", + " if (!linearAverageColor) return white || (white = new THREE.Color(1, 1, 1));", + " let color = averageColors.get(linearAverageColor);", + " if (!color) {", + " color = new THREE.Color(linearAverageColor.x, linearAverageColor.y, linearAverageColor.z);", + " averageColors.set(linearAverageColor, color);", " }", - " 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;", "};", "", @@ -18918,6 +19054,37 @@ "group": "Rendering", "name": "ReceiveShadow" }, + { + "value": "4", + "type": "Choice", + "label": "Anisotropic filtering", + "description": "Keeps layer textures sharp where the ground is seen at a low angle (like from a car). Lower is faster on slow graphics cards, but blurs the ground in the distance.", + "group": "Rendering", + "choices": [ + { + "label": "None (fastest)", + "value": "1" + }, + { + "label": "2x", + "value": "2" + }, + { + "label": "4x", + "value": "4" + }, + { + "label": "8x", + "value": "8" + }, + { + "label": "16x (sharpest)", + "value": "16" + } + ], + "advanced": true, + "name": "Anisotropy" + }, { "value": "", "type": "String", @@ -19031,6 +19198,9 @@ }, { "propertyName": "ReceiveShadow" + }, + { + "propertyName": "Anisotropy" } ] }, diff --git a/examples/starting-3d-driving/theme-slots.json b/examples/starting-3d-driving/theme-slots.json index ed37222a2..d07dc324d 100644 --- a/examples/starting-3d-driving/theme-slots.json +++ b/examples/starting-3d-driving/theme-slots.json @@ -2,12 +2,26 @@ "version": 1, "objects": { "scene:Game Scene/Road_3D": "env.road", - "scene:Game Scene/Grass_3D": "env.ground", + "scene:Game Scene/Terrain": { + "Layer1Texture": { + "slot": "terrain.ground", + "normalMap": "Layer1NormalMap" + }, + "Layer2Texture": { + "slot": "terrain.road", + "normalMap": "Layer2NormalMap" + } + }, "scene:Game Scene/PlayerCar": "vehicle.car", "scene:Game Scene/TrafficCone": "prop.obstacle", "scene:Game Scene/Walls_3D": "env.wall" }, "effects": { "effect:Game Scene//SkyBox": "sky.day" - } + }, + "ignoredObjects": [ + "object:Terrain3D::Terrain3D/AreaPlaceholder", + "object:Terrain3D::TerrainGrass/AreaPlaceholder", + "object:Terrain3D::ScatteredModels/AreaPlaceholder" + ] } diff --git a/examples/starting-3d-grand-theft-auto/starting-3d-grand-theft-auto.json b/examples/starting-3d-grand-theft-auto/starting-3d-grand-theft-auto.json index 136cb142d..58449cd91 100644 --- a/examples/starting-3d-grand-theft-auto/starting-3d-grand-theft-auto.json +++ b/examples/starting-3d-grand-theft-auto/starting-3d-grand-theft-auto.json @@ -1956,6 +1956,51 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Start with touch controls on touchscreens." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "SystemInfo::HasTouchScreen" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::SetChecked" + }, + "parameters": [ + "ControlsToggle", + "yes", + "" + ] + } + ] + }, { "folded": true, "type": "BuiltinCommonInstructions::Standard", @@ -3187,18 +3232,11 @@ "conditions": [ { "type": { - "value": "MouseButtonFromTextPressed" + "value": "HasAnyTouchOrMouseStarted" }, "parameters": [ - "", - "\"Left\"" + "" ] - }, - { - "type": { - "value": "BuiltinCommonInstructions::Once" - }, - "parameters": [] } ], "actions": [], @@ -3219,17 +3257,6 @@ "" ] }, - { - "type": { - "inverted": true, - "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" - }, - "parameters": [ - "JumpButton", - "MultitouchButton", - "" - ] - }, { "type": { "inverted": true, @@ -18760,7 +18787,6 @@ { "author": "PANDAKO-GitHub", "category": "Input", - "dimension": "", "extensionNamespace": "", "fullName": "Mouse Pointer Lock", "gdevelopVersion": ">=5.5.222", @@ -18769,7 +18795,7 @@ "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", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -18817,7 +18843,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -19065,16 +19091,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -19257,6 +19313,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -19538,68 +19619,12 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "onFirstSceneLoaded" - }, - { - "functionName": "onScenePostEvents" - }, - { - "functionName": "RequestPointerLock" - }, - { - "functionName": "ExitPointerLock" - }, - { - "functionName": "isPointerLocked" - }, - { - "functionName": "isPointerActuallyLocked" - }, - { - "functionName": "isEmulatingPointerLock" - }, - { - "functionName": "IsMoving" - }, - { - "functionName": "MovementX" - }, - { - "functionName": "SetMovementX" - }, - { - "functionName": "MovementY" - }, - { - "functionName": "SetMovementY" - }, - { - "functionName": "TouchX" - }, - { - "functionName": "TouchY" - }, - { - "functionName": "TouchSpeedFactor" - }, - { - "functionName": "SetTouchSpeedFactor" - } - ] - }, "eventsBasedBehaviors": [ { "description": "Control camera rotations with a mouse.", "fullName": "First person camera mouse mapper", - "helpPath": "", - "iconUrl": "", "name": "FirstPersonPointerMapper", "objectType": "", - "previewIconUrl": "", "eventsFunctions": [ { "fullName": "", @@ -19619,6 +19644,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -19640,7 +19677,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { @@ -19874,7 +19911,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "HorizontalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetHorizontalSpeed", "sentence": "", "events": [ @@ -19955,7 +19991,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalSpeed", "sentence": "", "events": [ @@ -20036,7 +20071,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMin", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMin", "sentence": "", "events": [ @@ -20117,7 +20151,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMax", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMax", "sentence": "", "events": [ @@ -20198,7 +20231,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "OffsetZ", - "group": "First person camera mouse mapper configuration", "name": "SetOffsetZ", "sentence": "", "events": [ @@ -20235,57 +20267,6 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "doStepPreEvents" - }, - { - "folderName": "Layers and cameras", - "children": [ - { - "functionName": "LookFromObjectEyes" - } - ] - }, - { - "folderName": "First person camera mouse mapper configuration", - "children": [ - { - "functionName": "HorizontalSpeed" - }, - { - "functionName": "SetHorizontalSpeed" - }, - { - "functionName": "VerticalSpeed" - }, - { - "functionName": "SetVerticalSpeed" - }, - { - "functionName": "VerticalAngleMin" - }, - { - "functionName": "SetVerticalAngleMin" - }, - { - "functionName": "VerticalAngleMax" - }, - { - "functionName": "SetVerticalAngleMax" - }, - { - "functionName": "OffsetZ" - }, - { - "functionName": "SetOffsetZ" - } - ] - } - ] - }, "propertyDescriptors": [ { "value": "", @@ -20339,32 +20320,7 @@ "name": "ShouldLock" } ], - "propertiesFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "propertyName": "Object3D" - }, - { - "propertyName": "HorizontalSpeed" - }, - { - "propertyName": "VerticalSpeed" - }, - { - "propertyName": "VerticalAngleMin" - }, - { - "propertyName": "VerticalAngleMax" - }, - { - "propertyName": "OffsetZ" - }, - { - "propertyName": "ShouldLock" - } - ] - } + "sharedPropertyDescriptors": [] } ], "eventsBasedObjects": [] @@ -28648,7 +28604,7 @@ "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", + "version": "0.2.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.", "", @@ -29748,8 +29704,17 @@ "vTerrainLocalXY = position.xy;", "vTerrainLocalZ = position.z;", "vTerrainWorldXY = (modelMatrix * vec4(transformed, 1.0)).xy * terrainWorldScale;", - "vTerrainUp = normalize(transpose(inverse(mat3(modelMatrix))) * objectNormal).z;", + "// The world normal, from the camera space normal (computed by Three.js with a", + "// matrix inverted on the CPU), instead of inverting a matrix for each vertex.", + "vTerrainUp = inverseTransformDirection(transformedNormal, viewMatrix).z;", "`;", + "/** @param {(layer: number) => string} getLayerCode */", + "const forEachLayerCode = (getLayerCode) => [0, 1, 2, 3].map(getLayerCode).join('\\n');", + "", + "// Noise is read from a texture of random values, repeated every", + "// NOISE_MAP_SIZE units of noise: 1 texture read instead of 16 hashes.", + "const NOISE_MAP_SIZE = 256;", + "", "const fragmentShaderDeclarations = `", "uniform sampler2D terrainSplatMap;", "uniform vec2 terrainSplatUvScaleAndOffset;", @@ -29758,11 +29723,11 @@ "uniform sampler2D terrainLayerMap1;", "uniform sampler2D terrainLayerMap2;", "uniform sampler2D terrainLayerMap3;", + "uniform vec3 terrainLayerAverageColors[4];", "uniform sampler2D terrainLayerNormalMap0;", "uniform sampler2D terrainLayerNormalMap1;", "uniform sampler2D terrainLayerNormalMap2;", "uniform sampler2D terrainLayerNormalMap3;", - "uniform float terrainHasNormalMaps;", "uniform float terrainNormalMapStrength;", "uniform float terrainTextureSize;", "uniform float terrainHeightBlending;", @@ -29771,15 +29736,13 @@ "uniform vec2 terrainCliffCosines;", "uniform vec4 terrainBrush;", "uniform sampler2D terrainColorMap;", + "uniform sampler2D terrainNoiseMap;", "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. */", @@ -29791,15 +29754,16 @@ " copyBump.xy = transpose(terrainCopyRotation) * copyBump.xy;", " return mix(bump, copyBump, patches);", "}", - "float terrainNoise(vec2 p) {", + "/**", + " * 4 independent value noises (one by channel), from 0 to 1, smoothly varying", + " * between random values at integer positions.", + " */", + "vec4 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", - " );", + " vec2 f = p - i;", + " // The read position is moved so that the linear interpolation of the", + " // texture between 2 random values becomes a smooth one.", + " return texture2D(terrainNoiseMap, (i + f * f * (3.0 - 2.0 * f) + 0.5) / ${NOISE_MAP_SIZE}.0);", "}", "`;", "// Holes are samples without any painted layer: the interpolated sum of the", @@ -29817,15 +29781,11 @@ "varying vec2 vTerrainLocalXY;", "`;", "", + "// Layers without texture, normal map, height blending or texture variation", + "// are compiled out (see TerrainRenderer.updateMaterial).", "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)", - ");", + "vec4 terrainJitter = terrainNoise(vTerrainWorldXY / 24.0);", "// 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;", @@ -29836,48 +29796,61 @@ "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;", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "vec3 terrainTexture${layer} = texture2D(terrainLayerMap${layer}, terrainUv).rgb;", + "#else", + "vec3 terrainTexture${layer} = vec3(1.0);", + "#endif`", + ")}", "", "// 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);", - "}", + "#ifdef TERRAIN_TEXTURE_VARIATION", + "// 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).x);", + "terrainUv2 = terrainCopyRotation * terrainUv + vec2(0.31, 0.73);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "terrainTexture${layer} = mix(terrainTexture${layer}, texture2D(terrainLayerMap${layer}, terrainUv2).rgb, terrainPatches);", + "#endif`", + ")}", + "terrainColorVariation += terrainTextureVariation * 0.3 *", + " (terrainNoise(terrainUv * 0.11).y + 0.5 * terrainNoise(terrainUv * 0.37 + 3.1).z - 0.75);", + "#endif", "", "// 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);", - "}", + "#ifdef TERRAIN_HEIGHT_BLENDING", + "// Averages are computed once on the CPU, or read from the smallest mipmap", + "// when the image can't be read by the CPU.", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MIPMAP_AVERAGE_${layer}", + "vec3 terrainAverage${layer} = textureLod(terrainLayerMap${layer}, vec2(0.5), 16.0).rgb;", + "#else", + "vec3 terrainAverage${layer} = terrainLayerAverageColors[${layer}];", + "#endif`", + ")}", + "const vec3 terrainLuminance = vec3(0.299, 0.587, 0.114);", + "vec4 terrainHeights = vec4(", + " dot(terrainTexture0 - terrainAverage0, terrainLuminance),", + " dot(terrainTexture1 - terrainAverage1, terrainLuminance),", + " dot(terrainTexture2 - terrainAverage2, terrainLuminance),", + " dot(terrainTexture3 - terrainAverage3, 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);", + "#endif", "", "vec3 terrainColor = terrainColorVariation * (", " terrainWeights.x * terrainLayerColors[0] * terrainTexture0 +", @@ -29909,26 +29882,30 @@ "`;", "", "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;", + "#ifdef TERRAIN_NORMAL_MAPS", + "// Layers without normal map are flat.", + "vec3 terrainMapNormal = vec3(0.0);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_NORMAL_MAP_${layer}", + "terrainMapNormal += terrainWeights[${layer}] * terrainSampleNormal(terrainLayerNormalMap${layer}, terrainUv, terrainUv2, terrainPatches);", + "#else", + "terrainMapNormal.z += terrainWeights[${layer}];", + "#endif`", + ")}", + "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);", - " // 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);", "}", + "terrainTangent = normalize(terrainTangent);", + "vec3 terrainBitangent = cross(normal, terrainTangent);", + "normal = normalize(mat3(terrainTangent, terrainBitangent, normal) * terrainMapNormal);", + "#endif", "`;", "", "let whiteTexture = null;", @@ -29949,7 +29926,30 @@ " return flatNormalTexture;", "};", "", + "let noiseTexture = null;", + "/** Random values, in 4 independent channels, read by `terrainNoise` in the shader. */", + "const getNoiseTexture = () => {", + " if (!noiseTexture) {", + " const random = createRandom(1);", + " const bytes = new Uint8Array(NOISE_MAP_SIZE * NOISE_MAP_SIZE * 4);", + " for (let index = 0; index < bytes.length; index++) {", + " bytes[index] = Math.floor(random() * 256);", + " }", + " noiseTexture = new THREE.DataTexture(bytes, NOISE_MAP_SIZE, NOISE_MAP_SIZE);", + " noiseTexture.wrapS = THREE.RepeatWrapping;", + " noiseTexture.wrapT = THREE.RepeatWrapping;", + " // No mipmaps: the shader moves the read position between samples.", + " noiseTexture.magFilter = THREE.LinearFilter;", + " noiseTexture.minFilter = THREE.LinearFilter;", + " noiseTexture.needsUpdate = true;", + " }", + " return noiseTexture;", + "};", + "", + "const DEFAULT_ANISOTROPY = 4;", + "/** @type {WeakMap>} */", "const layerTextures = new WeakMap();", + "/** @type {WeakMap>} */", "const layerNormalMaps = new WeakMap();", "/**", " * Textures of the image manager have no mipmaps: they would shimmer when", @@ -29957,12 +29957,18 @@ " * @param {gdjs.RuntimeGame} game", " * @param {string} resourceName", " * @param {boolean} isNormalMap Normal maps are directions, not colors.", + " * @param {number} anisotropy", " */", - "const getLayerTexture = (game, resourceName, isNormalMap) => {", + "const getLayerTexture = (game, resourceName, isNormalMap, anisotropy) => {", " if (!resourceName) return isNormalMap ? getFlatNormalTexture() : getWhiteTexture();", " const imageTexture = game.getImageManager().getThreeTexture(resourceName);", - " const textures = isNormalMap ? layerNormalMaps : layerTextures;", - " let texture = textures.get(imageTexture);", + " const texturesByImage = isNormalMap ? layerNormalMaps : layerTextures;", + " let texturesByAnisotropy = texturesByImage.get(imageTexture);", + " if (!texturesByAnisotropy) {", + " texturesByAnisotropy = new Map();", + " texturesByImage.set(imageTexture, texturesByAnisotropy);", + " }", + " let texture = texturesByAnisotropy.get(anisotropy);", " if (!texture) {", " texture = imageTexture.clone();", " if (isNormalMap) texture.colorSpace = THREE.NoColorSpace;", @@ -29970,13 +29976,71 @@ " texture.wrapT = THREE.RepeatWrapping;", " texture.minFilter = THREE.LinearMipmapLinearFilter;", " texture.generateMipmaps = true;", - " texture.anisotropy = 4;", + " texture.anisotropy = anisotropy;", " texture.needsUpdate = true;", - " textures.set(imageTexture, texture);", + " texturesByAnisotropy.set(anisotropy, texture);", " }", " return texture;", "};", "", + "/** @type {Float32Array | null} */", + "let srgbToLinearValues = null;", + "/** @type {WeakMap} */", + "const linearAverageColors = new WeakMap();", + "/**", + " * @param {THREE.Texture} texture", + " * @returns {THREE.Vector3 | null} The average color of the texture, in the", + " * linear color space (like colors read by shaders), or null if the image", + " * can't be read: not loaded yet, or from another domain without CORS.", + " */", + "const getLinearAverageColor = (texture) => {", + " const image = texture.image;", + " if (!image) return null;", + " const cachedColor = linearAverageColors.get(image);", + " if (cachedColor) return cachedColor;", + " 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 null;", + " const size = 64;", + " let pixels;", + " try {", + " const canvas = document.createElement('canvas');", + " canvas.width = size;", + " canvas.height = size;", + " const context = canvas.getContext('2d');", + " // Downscaled with filtering, so that all pixels are counted.", + " context.imageSmoothingEnabled = true;", + " context.imageSmoothingQuality = 'high';", + " context.drawImage(image, 0, 0, size, size);", + " pixels = context.getImageData(0, 0, size, size).data;", + " } catch (error) {", + " return null;", + " }", + " if (!srgbToLinearValues) {", + " srgbToLinearValues = new Float32Array(256);", + " for (let value = 0; value < 256; value++) {", + " const srgb = value / 255;", + " srgbToLinearValues[value] = srgb <= 0.04045 ? srgb / 12.92 : Math.pow((srgb + 0.055) / 1.055, 2.4);", + " }", + " }", + " const isSrgb = texture.colorSpace === THREE.SRGBColorSpace;", + " let red = 0;", + " let green = 0;", + " let blue = 0;", + " for (let index = 0; index < pixels.length; index += 4) {", + " red += isSrgb ? srgbToLinearValues[pixels[index]] : pixels[index] / 255;", + " green += isSrgb ? srgbToLinearValues[pixels[index + 1]] : pixels[index + 1] / 255;", + " blue += isSrgb ? srgbToLinearValues[pixels[index + 2]] : pixels[index + 2] / 255;", + " }", + " const count = size * size;", + " const color = new THREE.Vector3(red / count, green / count, blue / count);", + " linearAverageColors.set(image, color);", + " return color;", + "};", + "", "/**", " * A texture of 4 bytes per sample of a grid of `size * size` samples (layer", " * weights, tint and wetness...), interpolated between samples.", @@ -30035,11 +30099,14 @@ " terrainLayerMap1: { value: getWhiteTexture() },", " terrainLayerMap2: { value: getWhiteTexture() },", " terrainLayerMap3: { value: getWhiteTexture() },", + " // Used by the height blending, in the linear color space.", + " terrainLayerAverageColors: {", + " value: [new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1)],", + " },", " terrainLayerNormalMap0: { value: getFlatNormalTexture() },", " terrainLayerNormalMap1: { value: getFlatNormalTexture() },", " terrainLayerNormalMap2: { value: getFlatNormalTexture() },", " terrainLayerNormalMap3: { value: getFlatNormalTexture() },", - " terrainHasNormalMaps: { value: 0 },", " terrainNormalMapStrength: { value: 1 },", " terrainTextureSize: { value: 256 },", " terrainHeightBlending: { value: 0 },", @@ -30048,6 +30115,7 @@ " terrainCliffCosines: { value: new THREE.Vector2() },", " terrainBrush: { value: new THREE.Vector4() },", " terrainColorMap: { value: getWhiteTexture() },", + " terrainNoiseMap: { value: getNoiseTexture() },", " // 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.", @@ -30067,8 +30135,12 @@ " ['#include ', '#include \\n' + fragmentShaderNormalCode],", " ], 'Terrain3D');", " };", - " this.material.customProgramCacheKey = () => (this.hasHoles ? 'Terrain3DWithHoles' : 'Terrain3D');", " this.hasHoles = false;", + " /** The defines of the features used by the terrain (see `updateMaterial`), without holes. */", + " this.featureDefines = [];", + " /** All the defines of the shader, separated by semicolons. */", + " this.shaderKey = '';", + " this.material.customProgramCacheKey = () => 'Terrain3D;' + this.shaderKey;", " // Without them, holes would cast shadows.", " this.depthMaterial = this._createHoleDepthMaterial(new THREE.MeshDepthMaterial());", " this.distanceMaterial = this._createHoleDepthMaterial(new THREE.MeshDistanceMaterial());", @@ -30105,11 +30177,28 @@ " setHasHoles(hasHoles) {", " if (hasHoles === this.hasHoles) return;", " this.hasHoles = hasHoles;", + " this._updateShaderDefines();", + " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", + " }", + "", + " /**", + " * Compile the shader only with the features used by the terrain. It is", + " * compiled again only when they change: properties only change in the", + " * editor, holes can be made by actions.", + " */", + " _updateShaderDefines() {", + " const defines = this.hasHoles ? [...this.featureDefines, 'TERRAIN_HOLES'] : this.featureDefines;", + " const shaderKey = defines.join(';');", + " if (shaderKey === this.shaderKey) return;", " // The material has its own defines (STANDARD).", - " if (hasHoles) this.material.defines.TERRAIN_HOLES = '';", - " else delete this.material.defines.TERRAIN_HOLES;", + " for (const name of this.shaderKey ? this.shaderKey.split(';') : []) {", + " delete this.material.defines[name];", + " }", + " for (const name of defines) {", + " this.material.defines[name] = '';", + " }", + " this.shaderKey = shaderKey;", " this.material.needsUpdate = true;", - " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", " }", "", " /** @param {THREE.Mesh} mesh */", @@ -30341,16 +30430,38 @@ " object._getLayer3NormalMap(),", " object._getLayer4NormalMap(),", " ];", + " const anisotropy = Math.max(parseInt(object._getAnisotropy(), 10) || DEFAULT_ANISOTROPY, 1);", + " const heightBlending = clamp01(object._getHeightBlending());", + " const textureVariation = clamp01(object._getTextureVariation());", + " const featureDefines = [];", " 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);", + " const layerTexture = getLayerTexture(game, textures[layer], false, anisotropy);", + " this.uniforms['terrainLayerMap' + layer].value = layerTexture;", + " this.uniforms['terrainLayerNormalMap' + layer].value = getLayerTexture(game, normalMaps[layer], true, anisotropy);", + " const averageColor = this.uniforms.terrainLayerAverageColors.value[layer];", + " if (textures[layer]) {", + " featureDefines.push('TERRAIN_LAYER_MAP_' + layer);", + " const linearAverageColor = heightBlending > 0 ? getLinearAverageColor(layerTexture) : null;", + " if (linearAverageColor) {", + " averageColor.copy(linearAverageColor);", + " } else if (heightBlending > 0) {", + " featureDefines.push('TERRAIN_LAYER_MIPMAP_AVERAGE_' + layer);", + " }", + " } else {", + " averageColor.set(1, 1, 1);", + " }", + " if (normalMaps[layer]) featureDefines.push('TERRAIN_LAYER_NORMAL_MAP_' + layer);", " }", - " this.uniforms.terrainHasNormalMaps.value = normalMaps.some((normalMap) => !!normalMap) ? 1 : 0;", + " if (normalMaps.some((normalMap) => !!normalMap)) featureDefines.push('TERRAIN_NORMAL_MAPS');", + " if (heightBlending > 0) featureDefines.push('TERRAIN_HEIGHT_BLENDING');", + " if (textureVariation > 0) featureDefines.push('TERRAIN_TEXTURE_VARIATION');", + " this.featureDefines = featureDefines;", + " this._updateShaderDefines();", " 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());", + " this.uniforms.terrainHeightBlending.value = heightBlending;", + " this.uniforms.terrainTextureVariation.value = textureVariation;", "", " const cliffLayer = parseInt(object._getCliffLayer(), 10);", " const cliffAngle = gdjs.toRad(object._getCliffAngle());", @@ -31268,40 +31379,21 @@ " return typeof scene.getRenderer3DWorldScale === 'function' ? scene.getRenderer3DWorldScale() : 1;", "};", "", + "/** @type {WeakMap} */", "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).", + " * @returns {THREE.Color} The average color of the texture (white when it can't be read).", " */", "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 linearAverageColor = getLinearAverageColor(texture);", + " if (!linearAverageColor) return white || (white = new THREE.Color(1, 1, 1));", + " let color = averageColors.get(linearAverageColor);", + " if (!color) {", + " color = new THREE.Color(linearAverageColor.x, linearAverageColor.y, linearAverageColor.z);", + " averageColors.set(linearAverageColor, color);", " }", - " 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;", "};", "", @@ -34915,6 +35007,37 @@ "group": "Rendering", "name": "ReceiveShadow" }, + { + "value": "4", + "type": "Choice", + "label": "Anisotropic filtering", + "description": "Keeps layer textures sharp where the ground is seen at a low angle (like from a car). Lower is faster on slow graphics cards, but blurs the ground in the distance.", + "group": "Rendering", + "choices": [ + { + "label": "None (fastest)", + "value": "1" + }, + { + "label": "2x", + "value": "2" + }, + { + "label": "4x", + "value": "4" + }, + { + "label": "8x", + "value": "8" + }, + { + "label": "16x (sharpest)", + "value": "16" + } + ], + "advanced": true, + "name": "Anisotropy" + }, { "value": "", "type": "String", @@ -35028,6 +35151,9 @@ }, { "propertyName": "ReceiveShadow" + }, + { + "propertyName": "Anisotropy" } ] }, diff --git a/examples/starting-3d-grand-theft-auto/theme-slots.json b/examples/starting-3d-grand-theft-auto/theme-slots.json index 33706862e..728049021 100644 --- a/examples/starting-3d-grand-theft-auto/theme-slots.json +++ b/examples/starting-3d-grand-theft-auto/theme-slots.json @@ -6,15 +6,19 @@ "scene:Game Scene/Driven_Car": "vehicle.car", "scene:Game Scene/PushableBox": "env.crate", "scene:Game Scene/Obstacle": "env.wall", - "scene:Game Scene/Ground": { - "front": "env.ground", - "left": "env.wall", - "right": "env.wall", - "top": "env.wall", - "bottom": "env.wall" + "scene:Game Scene/Terrain": { + "Layer1Texture": { + "slot": "terrain.ground", + "normalMap": "Layer1NormalMap" + } } }, "effects": { "effect:Game Scene//SkyBox": "sky.day" - } + }, + "ignoredObjects": [ + "object:Terrain3D::Terrain3D/AreaPlaceholder", + "object:Terrain3D::TerrainGrass/AreaPlaceholder", + "object:Terrain3D::ScatteredModels/AreaPlaceholder" + ] } diff --git a/examples/starting-3d-tank/starting-3d-tank.json b/examples/starting-3d-tank/starting-3d-tank.json index 819928141..951c62993 100644 --- a/examples/starting-3d-tank/starting-3d-tank.json +++ b/examples/starting-3d-tank/starting-3d-tank.json @@ -36268,7 +36268,7 @@ "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", + "version": "0.2.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.", "", @@ -37368,8 +37368,17 @@ "vTerrainLocalXY = position.xy;", "vTerrainLocalZ = position.z;", "vTerrainWorldXY = (modelMatrix * vec4(transformed, 1.0)).xy * terrainWorldScale;", - "vTerrainUp = normalize(transpose(inverse(mat3(modelMatrix))) * objectNormal).z;", + "// The world normal, from the camera space normal (computed by Three.js with a", + "// matrix inverted on the CPU), instead of inverting a matrix for each vertex.", + "vTerrainUp = inverseTransformDirection(transformedNormal, viewMatrix).z;", "`;", + "/** @param {(layer: number) => string} getLayerCode */", + "const forEachLayerCode = (getLayerCode) => [0, 1, 2, 3].map(getLayerCode).join('\\n');", + "", + "// Noise is read from a texture of random values, repeated every", + "// NOISE_MAP_SIZE units of noise: 1 texture read instead of 16 hashes.", + "const NOISE_MAP_SIZE = 256;", + "", "const fragmentShaderDeclarations = `", "uniform sampler2D terrainSplatMap;", "uniform vec2 terrainSplatUvScaleAndOffset;", @@ -37378,11 +37387,11 @@ "uniform sampler2D terrainLayerMap1;", "uniform sampler2D terrainLayerMap2;", "uniform sampler2D terrainLayerMap3;", + "uniform vec3 terrainLayerAverageColors[4];", "uniform sampler2D terrainLayerNormalMap0;", "uniform sampler2D terrainLayerNormalMap1;", "uniform sampler2D terrainLayerNormalMap2;", "uniform sampler2D terrainLayerNormalMap3;", - "uniform float terrainHasNormalMaps;", "uniform float terrainNormalMapStrength;", "uniform float terrainTextureSize;", "uniform float terrainHeightBlending;", @@ -37391,15 +37400,13 @@ "uniform vec2 terrainCliffCosines;", "uniform vec4 terrainBrush;", "uniform sampler2D terrainColorMap;", + "uniform sampler2D terrainNoiseMap;", "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. */", @@ -37411,15 +37418,16 @@ " copyBump.xy = transpose(terrainCopyRotation) * copyBump.xy;", " return mix(bump, copyBump, patches);", "}", - "float terrainNoise(vec2 p) {", + "/**", + " * 4 independent value noises (one by channel), from 0 to 1, smoothly varying", + " * between random values at integer positions.", + " */", + "vec4 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", - " );", + " vec2 f = p - i;", + " // The read position is moved so that the linear interpolation of the", + " // texture between 2 random values becomes a smooth one.", + " return texture2D(terrainNoiseMap, (i + f * f * (3.0 - 2.0 * f) + 0.5) / ${NOISE_MAP_SIZE}.0);", "}", "`;", "// Holes are samples without any painted layer: the interpolated sum of the", @@ -37437,15 +37445,11 @@ "varying vec2 vTerrainLocalXY;", "`;", "", + "// Layers without texture, normal map, height blending or texture variation", + "// are compiled out (see TerrainRenderer.updateMaterial).", "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)", - ");", + "vec4 terrainJitter = terrainNoise(vTerrainWorldXY / 24.0);", "// 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;", @@ -37456,48 +37460,61 @@ "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;", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "vec3 terrainTexture${layer} = texture2D(terrainLayerMap${layer}, terrainUv).rgb;", + "#else", + "vec3 terrainTexture${layer} = vec3(1.0);", + "#endif`", + ")}", "", "// 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);", - "}", + "#ifdef TERRAIN_TEXTURE_VARIATION", + "// 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).x);", + "terrainUv2 = terrainCopyRotation * terrainUv + vec2(0.31, 0.73);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "terrainTexture${layer} = mix(terrainTexture${layer}, texture2D(terrainLayerMap${layer}, terrainUv2).rgb, terrainPatches);", + "#endif`", + ")}", + "terrainColorVariation += terrainTextureVariation * 0.3 *", + " (terrainNoise(terrainUv * 0.11).y + 0.5 * terrainNoise(terrainUv * 0.37 + 3.1).z - 0.75);", + "#endif", "", "// 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);", - "}", + "#ifdef TERRAIN_HEIGHT_BLENDING", + "// Averages are computed once on the CPU, or read from the smallest mipmap", + "// when the image can't be read by the CPU.", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MIPMAP_AVERAGE_${layer}", + "vec3 terrainAverage${layer} = textureLod(terrainLayerMap${layer}, vec2(0.5), 16.0).rgb;", + "#else", + "vec3 terrainAverage${layer} = terrainLayerAverageColors[${layer}];", + "#endif`", + ")}", + "const vec3 terrainLuminance = vec3(0.299, 0.587, 0.114);", + "vec4 terrainHeights = vec4(", + " dot(terrainTexture0 - terrainAverage0, terrainLuminance),", + " dot(terrainTexture1 - terrainAverage1, terrainLuminance),", + " dot(terrainTexture2 - terrainAverage2, terrainLuminance),", + " dot(terrainTexture3 - terrainAverage3, 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);", + "#endif", "", "vec3 terrainColor = terrainColorVariation * (", " terrainWeights.x * terrainLayerColors[0] * terrainTexture0 +", @@ -37529,26 +37546,30 @@ "`;", "", "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;", + "#ifdef TERRAIN_NORMAL_MAPS", + "// Layers without normal map are flat.", + "vec3 terrainMapNormal = vec3(0.0);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_NORMAL_MAP_${layer}", + "terrainMapNormal += terrainWeights[${layer}] * terrainSampleNormal(terrainLayerNormalMap${layer}, terrainUv, terrainUv2, terrainPatches);", + "#else", + "terrainMapNormal.z += terrainWeights[${layer}];", + "#endif`", + ")}", + "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);", - " // 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);", "}", + "terrainTangent = normalize(terrainTangent);", + "vec3 terrainBitangent = cross(normal, terrainTangent);", + "normal = normalize(mat3(terrainTangent, terrainBitangent, normal) * terrainMapNormal);", + "#endif", "`;", "", "let whiteTexture = null;", @@ -37569,7 +37590,30 @@ " return flatNormalTexture;", "};", "", + "let noiseTexture = null;", + "/** Random values, in 4 independent channels, read by `terrainNoise` in the shader. */", + "const getNoiseTexture = () => {", + " if (!noiseTexture) {", + " const random = createRandom(1);", + " const bytes = new Uint8Array(NOISE_MAP_SIZE * NOISE_MAP_SIZE * 4);", + " for (let index = 0; index < bytes.length; index++) {", + " bytes[index] = Math.floor(random() * 256);", + " }", + " noiseTexture = new THREE.DataTexture(bytes, NOISE_MAP_SIZE, NOISE_MAP_SIZE);", + " noiseTexture.wrapS = THREE.RepeatWrapping;", + " noiseTexture.wrapT = THREE.RepeatWrapping;", + " // No mipmaps: the shader moves the read position between samples.", + " noiseTexture.magFilter = THREE.LinearFilter;", + " noiseTexture.minFilter = THREE.LinearFilter;", + " noiseTexture.needsUpdate = true;", + " }", + " return noiseTexture;", + "};", + "", + "const DEFAULT_ANISOTROPY = 4;", + "/** @type {WeakMap>} */", "const layerTextures = new WeakMap();", + "/** @type {WeakMap>} */", "const layerNormalMaps = new WeakMap();", "/**", " * Textures of the image manager have no mipmaps: they would shimmer when", @@ -37577,12 +37621,18 @@ " * @param {gdjs.RuntimeGame} game", " * @param {string} resourceName", " * @param {boolean} isNormalMap Normal maps are directions, not colors.", + " * @param {number} anisotropy", " */", - "const getLayerTexture = (game, resourceName, isNormalMap) => {", + "const getLayerTexture = (game, resourceName, isNormalMap, anisotropy) => {", " if (!resourceName) return isNormalMap ? getFlatNormalTexture() : getWhiteTexture();", " const imageTexture = game.getImageManager().getThreeTexture(resourceName);", - " const textures = isNormalMap ? layerNormalMaps : layerTextures;", - " let texture = textures.get(imageTexture);", + " const texturesByImage = isNormalMap ? layerNormalMaps : layerTextures;", + " let texturesByAnisotropy = texturesByImage.get(imageTexture);", + " if (!texturesByAnisotropy) {", + " texturesByAnisotropy = new Map();", + " texturesByImage.set(imageTexture, texturesByAnisotropy);", + " }", + " let texture = texturesByAnisotropy.get(anisotropy);", " if (!texture) {", " texture = imageTexture.clone();", " if (isNormalMap) texture.colorSpace = THREE.NoColorSpace;", @@ -37590,13 +37640,71 @@ " texture.wrapT = THREE.RepeatWrapping;", " texture.minFilter = THREE.LinearMipmapLinearFilter;", " texture.generateMipmaps = true;", - " texture.anisotropy = 4;", + " texture.anisotropy = anisotropy;", " texture.needsUpdate = true;", - " textures.set(imageTexture, texture);", + " texturesByAnisotropy.set(anisotropy, texture);", " }", " return texture;", "};", "", + "/** @type {Float32Array | null} */", + "let srgbToLinearValues = null;", + "/** @type {WeakMap} */", + "const linearAverageColors = new WeakMap();", + "/**", + " * @param {THREE.Texture} texture", + " * @returns {THREE.Vector3 | null} The average color of the texture, in the", + " * linear color space (like colors read by shaders), or null if the image", + " * can't be read: not loaded yet, or from another domain without CORS.", + " */", + "const getLinearAverageColor = (texture) => {", + " const image = texture.image;", + " if (!image) return null;", + " const cachedColor = linearAverageColors.get(image);", + " if (cachedColor) return cachedColor;", + " 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 null;", + " const size = 64;", + " let pixels;", + " try {", + " const canvas = document.createElement('canvas');", + " canvas.width = size;", + " canvas.height = size;", + " const context = canvas.getContext('2d');", + " // Downscaled with filtering, so that all pixels are counted.", + " context.imageSmoothingEnabled = true;", + " context.imageSmoothingQuality = 'high';", + " context.drawImage(image, 0, 0, size, size);", + " pixels = context.getImageData(0, 0, size, size).data;", + " } catch (error) {", + " return null;", + " }", + " if (!srgbToLinearValues) {", + " srgbToLinearValues = new Float32Array(256);", + " for (let value = 0; value < 256; value++) {", + " const srgb = value / 255;", + " srgbToLinearValues[value] = srgb <= 0.04045 ? srgb / 12.92 : Math.pow((srgb + 0.055) / 1.055, 2.4);", + " }", + " }", + " const isSrgb = texture.colorSpace === THREE.SRGBColorSpace;", + " let red = 0;", + " let green = 0;", + " let blue = 0;", + " for (let index = 0; index < pixels.length; index += 4) {", + " red += isSrgb ? srgbToLinearValues[pixels[index]] : pixels[index] / 255;", + " green += isSrgb ? srgbToLinearValues[pixels[index + 1]] : pixels[index + 1] / 255;", + " blue += isSrgb ? srgbToLinearValues[pixels[index + 2]] : pixels[index + 2] / 255;", + " }", + " const count = size * size;", + " const color = new THREE.Vector3(red / count, green / count, blue / count);", + " linearAverageColors.set(image, color);", + " return color;", + "};", + "", "/**", " * A texture of 4 bytes per sample of a grid of `size * size` samples (layer", " * weights, tint and wetness...), interpolated between samples.", @@ -37655,11 +37763,14 @@ " terrainLayerMap1: { value: getWhiteTexture() },", " terrainLayerMap2: { value: getWhiteTexture() },", " terrainLayerMap3: { value: getWhiteTexture() },", + " // Used by the height blending, in the linear color space.", + " terrainLayerAverageColors: {", + " value: [new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1)],", + " },", " terrainLayerNormalMap0: { value: getFlatNormalTexture() },", " terrainLayerNormalMap1: { value: getFlatNormalTexture() },", " terrainLayerNormalMap2: { value: getFlatNormalTexture() },", " terrainLayerNormalMap3: { value: getFlatNormalTexture() },", - " terrainHasNormalMaps: { value: 0 },", " terrainNormalMapStrength: { value: 1 },", " terrainTextureSize: { value: 256 },", " terrainHeightBlending: { value: 0 },", @@ -37668,6 +37779,7 @@ " terrainCliffCosines: { value: new THREE.Vector2() },", " terrainBrush: { value: new THREE.Vector4() },", " terrainColorMap: { value: getWhiteTexture() },", + " terrainNoiseMap: { value: getNoiseTexture() },", " // 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.", @@ -37687,8 +37799,12 @@ " ['#include ', '#include \\n' + fragmentShaderNormalCode],", " ], 'Terrain3D');", " };", - " this.material.customProgramCacheKey = () => (this.hasHoles ? 'Terrain3DWithHoles' : 'Terrain3D');", " this.hasHoles = false;", + " /** The defines of the features used by the terrain (see `updateMaterial`), without holes. */", + " this.featureDefines = [];", + " /** All the defines of the shader, separated by semicolons. */", + " this.shaderKey = '';", + " this.material.customProgramCacheKey = () => 'Terrain3D;' + this.shaderKey;", " // Without them, holes would cast shadows.", " this.depthMaterial = this._createHoleDepthMaterial(new THREE.MeshDepthMaterial());", " this.distanceMaterial = this._createHoleDepthMaterial(new THREE.MeshDistanceMaterial());", @@ -37725,11 +37841,28 @@ " setHasHoles(hasHoles) {", " if (hasHoles === this.hasHoles) return;", " this.hasHoles = hasHoles;", + " this._updateShaderDefines();", + " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", + " }", + "", + " /**", + " * Compile the shader only with the features used by the terrain. It is", + " * compiled again only when they change: properties only change in the", + " * editor, holes can be made by actions.", + " */", + " _updateShaderDefines() {", + " const defines = this.hasHoles ? [...this.featureDefines, 'TERRAIN_HOLES'] : this.featureDefines;", + " const shaderKey = defines.join(';');", + " if (shaderKey === this.shaderKey) return;", " // The material has its own defines (STANDARD).", - " if (hasHoles) this.material.defines.TERRAIN_HOLES = '';", - " else delete this.material.defines.TERRAIN_HOLES;", + " for (const name of this.shaderKey ? this.shaderKey.split(';') : []) {", + " delete this.material.defines[name];", + " }", + " for (const name of defines) {", + " this.material.defines[name] = '';", + " }", + " this.shaderKey = shaderKey;", " this.material.needsUpdate = true;", - " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", " }", "", " /** @param {THREE.Mesh} mesh */", @@ -37961,16 +38094,38 @@ " object._getLayer3NormalMap(),", " object._getLayer4NormalMap(),", " ];", + " const anisotropy = Math.max(parseInt(object._getAnisotropy(), 10) || DEFAULT_ANISOTROPY, 1);", + " const heightBlending = clamp01(object._getHeightBlending());", + " const textureVariation = clamp01(object._getTextureVariation());", + " const featureDefines = [];", " 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);", + " const layerTexture = getLayerTexture(game, textures[layer], false, anisotropy);", + " this.uniforms['terrainLayerMap' + layer].value = layerTexture;", + " this.uniforms['terrainLayerNormalMap' + layer].value = getLayerTexture(game, normalMaps[layer], true, anisotropy);", + " const averageColor = this.uniforms.terrainLayerAverageColors.value[layer];", + " if (textures[layer]) {", + " featureDefines.push('TERRAIN_LAYER_MAP_' + layer);", + " const linearAverageColor = heightBlending > 0 ? getLinearAverageColor(layerTexture) : null;", + " if (linearAverageColor) {", + " averageColor.copy(linearAverageColor);", + " } else if (heightBlending > 0) {", + " featureDefines.push('TERRAIN_LAYER_MIPMAP_AVERAGE_' + layer);", + " }", + " } else {", + " averageColor.set(1, 1, 1);", + " }", + " if (normalMaps[layer]) featureDefines.push('TERRAIN_LAYER_NORMAL_MAP_' + layer);", " }", - " this.uniforms.terrainHasNormalMaps.value = normalMaps.some((normalMap) => !!normalMap) ? 1 : 0;", + " if (normalMaps.some((normalMap) => !!normalMap)) featureDefines.push('TERRAIN_NORMAL_MAPS');", + " if (heightBlending > 0) featureDefines.push('TERRAIN_HEIGHT_BLENDING');", + " if (textureVariation > 0) featureDefines.push('TERRAIN_TEXTURE_VARIATION');", + " this.featureDefines = featureDefines;", + " this._updateShaderDefines();", " 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());", + " this.uniforms.terrainHeightBlending.value = heightBlending;", + " this.uniforms.terrainTextureVariation.value = textureVariation;", "", " const cliffLayer = parseInt(object._getCliffLayer(), 10);", " const cliffAngle = gdjs.toRad(object._getCliffAngle());", @@ -38888,40 +39043,21 @@ " return typeof scene.getRenderer3DWorldScale === 'function' ? scene.getRenderer3DWorldScale() : 1;", "};", "", + "/** @type {WeakMap} */", "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).", + " * @returns {THREE.Color} The average color of the texture (white when it can't be read).", " */", "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 linearAverageColor = getLinearAverageColor(texture);", + " if (!linearAverageColor) return white || (white = new THREE.Color(1, 1, 1));", + " let color = averageColors.get(linearAverageColor);", + " if (!color) {", + " color = new THREE.Color(linearAverageColor.x, linearAverageColor.y, linearAverageColor.z);", + " averageColors.set(linearAverageColor, color);", " }", - " 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;", "};", "", @@ -42535,6 +42671,37 @@ "group": "Rendering", "name": "ReceiveShadow" }, + { + "value": "4", + "type": "Choice", + "label": "Anisotropic filtering", + "description": "Keeps layer textures sharp where the ground is seen at a low angle (like from a car). Lower is faster on slow graphics cards, but blurs the ground in the distance.", + "group": "Rendering", + "choices": [ + { + "label": "None (fastest)", + "value": "1" + }, + { + "label": "2x", + "value": "2" + }, + { + "label": "4x", + "value": "4" + }, + { + "label": "8x", + "value": "8" + }, + { + "label": "16x (sharpest)", + "value": "16" + } + ], + "advanced": true, + "name": "Anisotropy" + }, { "value": "", "type": "String", @@ -42648,6 +42815,9 @@ }, { "propertyName": "ReceiveShadow" + }, + { + "propertyName": "Anisotropy" } ] }, diff --git a/examples/starting-3d-tank/theme-slots.json b/examples/starting-3d-tank/theme-slots.json index 136d5f06b..741e699fa 100644 --- a/examples/starting-3d-tank/theme-slots.json +++ b/examples/starting-3d-tank/theme-slots.json @@ -2,7 +2,16 @@ "version": 1, "objects": { "scene:Game Scene/Road_3D": "env.road", - "scene:Game Scene/Grass_3D": "env.ground", + "scene:Game Scene/Terrain": { + "Layer1Texture": { + "slot": "terrain.ground", + "normalMap": "Layer1NormalMap" + }, + "Layer2Texture": { + "slot": "terrain.road", + "normalMap": "Layer2NormalMap" + } + }, "scene:Game Scene/Target": "character.enemy", "scene:Game Scene/Bullet": "projectile.player", "scene:Game Scene/Walls_3D": "env.wall", @@ -12,5 +21,10 @@ }, "effects": { "effect:Game Scene//SkyBox": "sky.day" - } + }, + "ignoredObjects": [ + "object:Terrain3D::Terrain3D/AreaPlaceholder", + "object:Terrain3D::TerrainGrass/AreaPlaceholder", + "object:Terrain3D::ScatteredModels/AreaPlaceholder" + ] } diff --git a/examples/starting-expanded-3D-platformer/starting-expanded-3D-platformer.json b/examples/starting-expanded-3D-platformer/starting-expanded-3D-platformer.json index 9a72a2bb8..23f2ced35 100644 --- a/examples/starting-expanded-3D-platformer/starting-expanded-3D-platformer.json +++ b/examples/starting-expanded-3D-platformer/starting-expanded-3D-platformer.json @@ -2957,6 +2957,51 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Start with touch controls on touchscreens." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "SystemInfo::HasTouchScreen" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::SetChecked" + }, + "parameters": [ + "ControlsToggle", + "yes", + "" + ] + } + ] + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -3503,18 +3548,11 @@ "conditions": [ { "type": { - "value": "MouseButtonFromTextPressed" + "value": "HasAnyTouchOrMouseStarted" }, "parameters": [ - "", - "\"Left\"" + "" ] - }, - { - "type": { - "value": "BuiltinCommonInstructions::Once" - }, - "parameters": [] } ], "actions": [], @@ -3533,17 +3571,6 @@ "\"Left\"", "" ] - }, - { - "type": { - "inverted": true, - "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" - }, - "parameters": [ - "JumpButton", - "MultitouchButton", - "" - ] } ], "actions": [ @@ -17328,7 +17355,7 @@ "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", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -17376,7 +17403,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -17624,16 +17651,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -17816,6 +17873,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -18122,6 +18204,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -18143,7 +18237,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { diff --git a/examples/starting-expanded-3d-grand-theft-auto/starting-expanded-3d-grand-theft-auto.json b/examples/starting-expanded-3d-grand-theft-auto/starting-expanded-3d-grand-theft-auto.json index 6181db416..b1838de62 100644 --- a/examples/starting-expanded-3d-grand-theft-auto/starting-expanded-3d-grand-theft-auto.json +++ b/examples/starting-expanded-3d-grand-theft-auto/starting-expanded-3d-grand-theft-auto.json @@ -4719,6 +4719,51 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Start with touch controls on touchscreens." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "SystemInfo::HasTouchScreen" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::SetChecked" + }, + "parameters": [ + "ControlsToggle", + "yes", + "" + ] + } + ] + }, { "folded": false, "type": "BuiltinCommonInstructions::Standard", @@ -5950,18 +5995,11 @@ "conditions": [ { "type": { - "value": "MouseButtonFromTextPressed" + "value": "HasAnyTouchOrMouseStarted" }, "parameters": [ - "", - "\"Left\"" + "" ] - }, - { - "type": { - "value": "BuiltinCommonInstructions::Once" - }, - "parameters": [] } ], "actions": [], @@ -5982,17 +6020,6 @@ "" ] }, - { - "type": { - "inverted": true, - "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" - }, - "parameters": [ - "JumpButton", - "MultitouchButton", - "" - ] - }, { "type": { "inverted": true, @@ -22040,7 +22067,6 @@ { "author": "PANDAKO-GitHub", "category": "Input", - "dimension": "", "extensionNamespace": "", "fullName": "Mouse Pointer Lock", "gdevelopVersion": ">=5.5.222", @@ -22049,7 +22075,7 @@ "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", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -22097,7 +22123,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -22345,16 +22371,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -22537,6 +22593,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -22818,68 +22899,12 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "onFirstSceneLoaded" - }, - { - "functionName": "onScenePostEvents" - }, - { - "functionName": "RequestPointerLock" - }, - { - "functionName": "ExitPointerLock" - }, - { - "functionName": "isPointerLocked" - }, - { - "functionName": "isPointerActuallyLocked" - }, - { - "functionName": "isEmulatingPointerLock" - }, - { - "functionName": "IsMoving" - }, - { - "functionName": "MovementX" - }, - { - "functionName": "SetMovementX" - }, - { - "functionName": "MovementY" - }, - { - "functionName": "SetMovementY" - }, - { - "functionName": "TouchX" - }, - { - "functionName": "TouchY" - }, - { - "functionName": "TouchSpeedFactor" - }, - { - "functionName": "SetTouchSpeedFactor" - } - ] - }, "eventsBasedBehaviors": [ { "description": "Control camera rotations with a mouse.", "fullName": "First person camera mouse mapper", - "helpPath": "", - "iconUrl": "", "name": "FirstPersonPointerMapper", "objectType": "", - "previewIconUrl": "", "eventsFunctions": [ { "fullName": "", @@ -22899,6 +22924,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -22920,7 +22957,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { @@ -23154,7 +23191,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "HorizontalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetHorizontalSpeed", "sentence": "", "events": [ @@ -23235,7 +23271,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalSpeed", "sentence": "", "events": [ @@ -23316,7 +23351,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMin", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMin", "sentence": "", "events": [ @@ -23397,7 +23431,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMax", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMax", "sentence": "", "events": [ @@ -23478,7 +23511,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "OffsetZ", - "group": "First person camera mouse mapper configuration", "name": "SetOffsetZ", "sentence": "", "events": [ @@ -23515,57 +23547,6 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "doStepPreEvents" - }, - { - "folderName": "Layers and cameras", - "children": [ - { - "functionName": "LookFromObjectEyes" - } - ] - }, - { - "folderName": "First person camera mouse mapper configuration", - "children": [ - { - "functionName": "HorizontalSpeed" - }, - { - "functionName": "SetHorizontalSpeed" - }, - { - "functionName": "VerticalSpeed" - }, - { - "functionName": "SetVerticalSpeed" - }, - { - "functionName": "VerticalAngleMin" - }, - { - "functionName": "SetVerticalAngleMin" - }, - { - "functionName": "VerticalAngleMax" - }, - { - "functionName": "SetVerticalAngleMax" - }, - { - "functionName": "OffsetZ" - }, - { - "functionName": "SetOffsetZ" - } - ] - } - ] - }, "propertyDescriptors": [ { "value": "", @@ -23619,32 +23600,7 @@ "name": "ShouldLock" } ], - "propertiesFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "propertyName": "Object3D" - }, - { - "propertyName": "HorizontalSpeed" - }, - { - "propertyName": "VerticalSpeed" - }, - { - "propertyName": "VerticalAngleMin" - }, - { - "propertyName": "VerticalAngleMax" - }, - { - "propertyName": "OffsetZ" - }, - { - "propertyName": "ShouldLock" - } - ] - } + "sharedPropertyDescriptors": [] } ], "eventsBasedObjects": [] diff --git a/examples/starting-expanded-3d-house-horror/README.md b/examples/starting-expanded-3d-house-horror/README.md new file mode 100644 index 000000000..ceb8c3324 --- /dev/null +++ b/examples/starting-expanded-3d-house-horror/README.md @@ -0,0 +1,3 @@ +# 3D Horror House Escape Expanded + +A 3D physics-based first-person horror escape game in a dark, furnished two-story house made of 3D boxes: a living room, a kitchen, a study and a storage room with pushable crates downstairs, bedrooms and a dressing room upstairs, linked by a staircase, and a fenced yard outside. The rooms have old semi-realistic furniture (beds, sofa, cupboards, a grandfather clock, oil lamps). The player only sees what the flashlight lights, and must find the key hidden in the house to unlock the front door, then escape through the gate of the yard. A monster roams both floors using navmesh pathfinding, climbing the stairs and finding its way through the rooms; it uses raycasting to check if the player is in its line of sight, then chases them, and the game resets if it catches the player. The player cannot fight back. It suits haunted house, escape and hide-and-seek horror games. The game supports both keyboard/mouse controls and mobile touch controls. diff --git a/examples/starting-expanded-3d-house-horror/assets/Classic Nightstand 01.glb b/examples/starting-expanded-3d-house-horror/assets/Classic Nightstand 01.glb new file mode 100644 index 000000000..9a0f4f1f9 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Classic Nightstand 01.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Common Tree Dead 1.glb b/examples/starting-expanded-3d-house-horror/assets/Common Tree Dead 1.glb new file mode 100644 index 000000000..9cb232033 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Common Tree Dead 1.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/DarkSkybox_Back.png b/examples/starting-expanded-3d-house-horror/assets/DarkSkybox_Back.png new file mode 100644 index 000000000..3f967d101 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/DarkSkybox_Back.png differ diff --git a/examples/starting-expanded-3d-house-horror/assets/DarkSkybox_Bottom.png b/examples/starting-expanded-3d-house-horror/assets/DarkSkybox_Bottom.png new file mode 100644 index 000000000..5c13b34da Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/DarkSkybox_Bottom.png differ diff --git a/examples/starting-expanded-3d-house-horror/assets/DarkSkybox_Front.png b/examples/starting-expanded-3d-house-horror/assets/DarkSkybox_Front.png new file mode 100644 index 000000000..afe6cedfd Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/DarkSkybox_Front.png differ diff --git a/examples/starting-expanded-3d-house-horror/assets/DarkSkybox_Left.png b/examples/starting-expanded-3d-house-horror/assets/DarkSkybox_Left.png new file mode 100644 index 000000000..4141f54d3 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/DarkSkybox_Left.png differ diff --git a/examples/starting-expanded-3d-house-horror/assets/DarkSkybox_Right.png b/examples/starting-expanded-3d-house-horror/assets/DarkSkybox_Right.png new file mode 100644 index 000000000..a5bd2e80c Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/DarkSkybox_Right.png differ diff --git a/examples/starting-expanded-3d-house-horror/assets/DarkSkybox_Top.png b/examples/starting-expanded-3d-house-horror/assets/DarkSkybox_Top.png new file mode 100644 index 000000000..81cc3397b Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/DarkSkybox_Top.png differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Electric Stove.glb b/examples/starting-expanded-3d-house-horror/assets/Electric Stove.glb new file mode 100644 index 000000000..61f398e36 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Electric Stove.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Fence.glb b/examples/starting-expanded-3d-house-horror/assets/Fence.glb new file mode 100644 index 000000000..bd0f6a0b3 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Fence.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Flat dark joystick border.png b/examples/starting-expanded-3d-house-horror/assets/Flat dark joystick border.png new file mode 100644 index 000000000..b4e83e112 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Flat dark joystick border.png differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Flat dark joystick thumb.png b/examples/starting-expanded-3d-house-horror/assets/Flat dark joystick thumb.png new file mode 100644 index 000000000..8acf179f8 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Flat dark joystick thumb.png differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Gothic Bed 01.glb b/examples/starting-expanded-3d-house-horror/assets/Gothic Bed 01.glb new file mode 100644 index 000000000..ea657d2f9 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Gothic Bed 01.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Gothic Commode 01.glb b/examples/starting-expanded-3d-house-horror/assets/Gothic Commode 01.glb new file mode 100644 index 000000000..f52f0603d Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Gothic Commode 01.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/HouseCeiling.png b/examples/starting-expanded-3d-house-horror/assets/HouseCeiling.png new file mode 100644 index 000000000..963ef46e3 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/HouseCeiling.png differ diff --git a/examples/starting-expanded-3d-house-horror/assets/HouseCrate.png b/examples/starting-expanded-3d-house-horror/assets/HouseCrate.png new file mode 100644 index 000000000..5d16b01f9 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/HouseCrate.png differ diff --git a/examples/starting-expanded-3d-house-horror/assets/HouseDoor.png b/examples/starting-expanded-3d-house-horror/assets/HouseDoor.png new file mode 100644 index 000000000..81edaf2be Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/HouseDoor.png differ diff --git a/examples/starting-expanded-3d-house-horror/assets/HouseFloor.png b/examples/starting-expanded-3d-house-horror/assets/HouseFloor.png new file mode 100644 index 000000000..5121824a2 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/HouseFloor.png differ diff --git a/examples/starting-expanded-3d-house-horror/assets/HouseRoof.png b/examples/starting-expanded-3d-house-horror/assets/HouseRoof.png new file mode 100644 index 000000000..f09879fb8 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/HouseRoof.png differ diff --git a/examples/starting-expanded-3d-house-horror/assets/HouseSiding.png b/examples/starting-expanded-3d-house-horror/assets/HouseSiding.png new file mode 100644 index 000000000..71284ffa7 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/HouseSiding.png differ diff --git a/examples/starting-expanded-3d-house-horror/assets/HouseWallpaper.png b/examples/starting-expanded-3d-house-horror/assets/HouseWallpaper.png new file mode 100644 index 000000000..af842141b Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/HouseWallpaper.png differ diff --git a/examples/starting-expanded-3d-house-horror/assets/HouseYard.png b/examples/starting-expanded-3d-house-horror/assets/HouseYard.png new file mode 100644 index 000000000..a190a1b4f Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/HouseYard.png differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Key.glb b/examples/starting-expanded-3d-house-horror/assets/Key.glb new file mode 100644 index 000000000..d31fae8c5 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Key.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/NotoSansArabicUI-Black.ttf b/examples/starting-expanded-3d-house-horror/assets/NotoSansArabicUI-Black.ttf new file mode 100644 index 000000000..9541c2cf3 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/NotoSansArabicUI-Black.ttf differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Ornate Mirror 01.glb b/examples/starting-expanded-3d-house-horror/assets/Ornate Mirror 01.glb new file mode 100644 index 000000000..108d3efdd Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Ornate Mirror 01.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Painted Wooden Cabinet 02.glb b/examples/starting-expanded-3d-house-horror/assets/Painted Wooden Cabinet 02.glb new file mode 100644 index 000000000..24e20cb7e Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Painted Wooden Cabinet 02.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Painted Wooden Cabinet.glb b/examples/starting-expanded-3d-house-horror/assets/Painted Wooden Cabinet.glb new file mode 100644 index 000000000..a83cb8ef8 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Painted Wooden Cabinet.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Painted Wooden Chair 02.glb b/examples/starting-expanded-3d-house-horror/assets/Painted Wooden Chair 02.glb new file mode 100644 index 000000000..a3db6c97f Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Painted Wooden Chair 02.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Rock Moss 1.glb b/examples/starting-expanded-3d-house-horror/assets/Rock Moss 1.glb new file mode 100644 index 000000000..b814ed483 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Rock Moss 1.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Rock Moss 4.glb b/examples/starting-expanded-3d-house-horror/assets/Rock Moss 4.glb new file mode 100644 index 000000000..1887874c6 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Rock Moss 4.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Rockingchair 01.glb b/examples/starting-expanded-3d-house-horror/assets/Rockingchair 01.glb new file mode 100644 index 000000000..3bebc2ccb Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Rockingchair 01.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Small Wooden Table 01.glb b/examples/starting-expanded-3d-house-horror/assets/Small Wooden Table 01.glb new file mode 100644 index 000000000..a76d638f5 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Small Wooden Table 01.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Sofa 03.glb b/examples/starting-expanded-3d-house-horror/assets/Sofa 03.glb new file mode 100644 index 000000000..2df537cac Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Sofa 03.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/StarterGlass.png b/examples/starting-expanded-3d-house-horror/assets/StarterGlass.png new file mode 100644 index 000000000..d82ea0586 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/StarterGlass.png differ diff --git a/examples/starting-expanded-3d-house-horror/assets/StarterToggle_Keyboard.png b/examples/starting-expanded-3d-house-horror/assets/StarterToggle_Keyboard.png new file mode 100644 index 000000000..aabe87250 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/StarterToggle_Keyboard.png differ diff --git a/examples/starting-expanded-3d-house-horror/assets/StarterToggle_Touch.png b/examples/starting-expanded-3d-house-horror/assets/StarterToggle_Touch.png new file mode 100644 index 000000000..204462798 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/StarterToggle_Touch.png differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Street Lamp 01.glb b/examples/starting-expanded-3d-house-horror/assets/Street Lamp 01.glb new file mode 100644 index 000000000..efcadae02 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Street Lamp 01.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Television 01.glb b/examples/starting-expanded-3d-house-horror/assets/Television 01.glb new file mode 100644 index 000000000..3df8ed24b Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Television 01.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Top arrow button.png b/examples/starting-expanded-3d-house-horror/assets/Top arrow button.png new file mode 100644 index 000000000..e69188046 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Top arrow button.png differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Vintage Day Bed.glb b/examples/starting-expanded-3d-house-horror/assets/Vintage Day Bed.glb new file mode 100644 index 000000000..49c8b5078 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Vintage Day Bed.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Vintage Grandfather Clock 01.glb b/examples/starting-expanded-3d-house-horror/assets/Vintage Grandfather Clock 01.glb new file mode 100644 index 000000000..14c171457 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Vintage Grandfather Clock 01.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Vintage Oil Lamp.glb b/examples/starting-expanded-3d-house-horror/assets/Vintage Oil Lamp.glb new file mode 100644 index 000000000..bbcb9491a Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Vintage Oil Lamp.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Willow Dead 1.glb b/examples/starting-expanded-3d-house-horror/assets/Willow Dead 1.glb new file mode 100644 index 000000000..cc0b4018e Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Willow Dead 1.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Wooden Bookshelf Worn.glb b/examples/starting-expanded-3d-house-horror/assets/Wooden Bookshelf Worn.glb new file mode 100644 index 000000000..4329a6b17 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Wooden Bookshelf Worn.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Wooden Table 01.glb b/examples/starting-expanded-3d-house-horror/assets/Wooden Table 01.glb new file mode 100644 index 000000000..63e766c89 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Wooden Table 01.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/Worn Metal Rack.glb b/examples/starting-expanded-3d-house-horror/assets/Worn Metal Rack.glb new file mode 100644 index 000000000..b6e64a569 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/Worn Metal Rack.glb differ diff --git a/examples/starting-expanded-3d-house-horror/assets/starting_unit_character_orange.glb b/examples/starting-expanded-3d-house-horror/assets/starting_unit_character_orange.glb new file mode 100644 index 000000000..8960c5c53 Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/assets/starting_unit_character_orange.glb differ diff --git a/examples/starting-expanded-3d-house-horror/preview.png b/examples/starting-expanded-3d-house-horror/preview.png new file mode 100644 index 000000000..cb171630e Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/preview.png differ diff --git a/examples/starting-expanded-3d-house-horror/starting-expanded-3d-house-horror.json b/examples/starting-expanded-3d-house-horror/starting-expanded-3d-house-horror.json new file mode 100644 index 000000000..fa33d5eb2 --- /dev/null +++ b/examples/starting-expanded-3d-house-horror/starting-expanded-3d-house-horror.json @@ -0,0 +1,37710 @@ +{ + "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": "ec1ea75b-c7ff-51fe-a492-e8aeeaf18e02", + "scaleMode": "linear", + "sizeOnStartupMode": "", + "templateSlug": "starting-first-person", + "version": "1.0.0", + "name": "3D Horror House Escape Expanded", + "description": "A first-person horror game in a dark two-story house: find the key, unlock the front door and escape while a monster hunts you.", + "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/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/DarkSkybox_Front.png", + "kind": "image", + "metadata": "", + "name": "assets/DarkSkybox_Front.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/DarkSkybox_Back.png", + "kind": "image", + "metadata": "", + "name": "assets/DarkSkybox_Back.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/DarkSkybox_Top.png", + "kind": "image", + "metadata": "", + "name": "assets/DarkSkybox_Top.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/DarkSkybox_Bottom.png", + "kind": "image", + "metadata": "", + "name": "assets/DarkSkybox_Bottom.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/DarkSkybox_Left.png", + "kind": "image", + "metadata": "", + "name": "assets/DarkSkybox_Left.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/DarkSkybox_Right.png", + "kind": "image", + "metadata": "", + "name": "assets/DarkSkybox_Right.png", + "smoothed": true, + "userAdded": false + }, + { + "file": "assets/starting_unit_character_orange.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/starting_unit_character_orange.glb", + "userAdded": false + }, + { + "file": "assets/HouseWallpaper.png", + "kind": "image", + "metadata": "", + "name": "assets/HouseWallpaper.png", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/HouseFloor.png", + "kind": "image", + "metadata": "", + "name": "assets/HouseFloor.png", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/HouseCeiling.png", + "kind": "image", + "metadata": "", + "name": "assets/HouseCeiling.png", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/HouseSiding.png", + "kind": "image", + "metadata": "", + "name": "assets/HouseSiding.png", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/HouseRoof.png", + "kind": "image", + "metadata": "", + "name": "assets/HouseRoof.png", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/HouseDoor.png", + "kind": "image", + "metadata": "", + "name": "assets/HouseDoor.png", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/HouseYard.png", + "kind": "image", + "metadata": "", + "name": "assets/HouseYard.png", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/StarterGlass.png", + "kind": "image", + "metadata": "", + "name": "assets/StarterGlass.png", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/HouseCrate.png", + "kind": "image", + "metadata": "", + "name": "assets/HouseCrate.png", + "userAdded": true, + "smoothed": true + }, + { + "file": "assets/Fence.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Fence.glb", + "userAdded": true + }, + { + "file": "assets/Street Lamp 01.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Street Lamp 01.glb", + "userAdded": true + }, + { + "file": "assets/Common Tree Dead 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Common Tree Dead 1.glb", + "userAdded": true + }, + { + "file": "assets/Willow Dead 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Willow Dead 1.glb", + "userAdded": true + }, + { + "file": "assets/Rock Moss 1.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Rock Moss 1.glb", + "userAdded": true + }, + { + "file": "assets/Rock Moss 4.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Rock Moss 4.glb", + "userAdded": true + }, + { + "file": "assets/Sofa 03.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Sofa 03.glb", + "userAdded": true + }, + { + "file": "assets/Television 01.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Television 01.glb", + "userAdded": true + }, + { + "file": "assets/Gothic Commode 01.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Gothic Commode 01.glb", + "userAdded": true + }, + { + "file": "assets/Classic Nightstand 01.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Classic Nightstand 01.glb", + "userAdded": true + }, + { + "file": "assets/Vintage Oil Lamp.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Vintage Oil Lamp.glb", + "userAdded": true + }, + { + "file": "assets/Wooden Bookshelf Worn.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Wooden Bookshelf Worn.glb", + "userAdded": true + }, + { + "file": "assets/Rockingchair 01.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Rockingchair 01.glb", + "userAdded": true + }, + { + "file": "assets/Painted Wooden Cabinet.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Painted Wooden Cabinet.glb", + "userAdded": true + }, + { + "file": "assets/Electric Stove.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Electric Stove.glb", + "userAdded": true + }, + { + "file": "assets/Painted Wooden Cabinet 02.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Painted Wooden Cabinet 02.glb", + "userAdded": true + }, + { + "file": "assets/Wooden Table 01.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Wooden Table 01.glb", + "userAdded": true + }, + { + "file": "assets/Painted Wooden Chair 02.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Painted Wooden Chair 02.glb", + "userAdded": true + }, + { + "file": "assets/Vintage Grandfather Clock 01.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Vintage Grandfather Clock 01.glb", + "userAdded": true + }, + { + "file": "assets/Small Wooden Table 01.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Small Wooden Table 01.glb", + "userAdded": true + }, + { + "file": "assets/Worn Metal Rack.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Worn Metal Rack.glb", + "userAdded": true + }, + { + "file": "assets/Vintage Day Bed.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Vintage Day Bed.glb", + "userAdded": true + }, + { + "file": "assets/Gothic Bed 01.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Gothic Bed 01.glb", + "userAdded": true + }, + { + "file": "assets/Ornate Mirror 01.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Ornate Mirror 01.glb", + "userAdded": true + }, + { + "file": "assets/Key.glb", + "kind": "model3D", + "metadata": "", + "name": "assets/Key.glb", + "userAdded": true + }, + { + "file": "assets/NotoSansArabicUI-Black.ttf", + "kind": "font", + "metadata": "", + "name": "assets/NotoSansArabicUI-Black.ttf", + "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.4633111824514261, + "windowMask": false, + "selectedLayer": "", + "gameEditorMode": "embedded-game" + }, + "objectsGroups": [ + { + "name": "MonsterVisionObstacles", + "objects": [ + { + "name": "PushableBox" + }, + { + "name": "Player" + }, + { + "name": "Wall" + }, + { + "name": "OuterWall" + }, + { + "name": "Floor" + }, + { + "name": "Stairs" + }, + { + "name": "Door" + } + ] + } + ], + "variables": [ + { + "name": "ControlKind", + "persistentUuid": "928b4b8d-c842-4a49-83a4-097bd9da6cff", + "type": "string", + "value": "Keyboard" + }, + { + "name": "HasKey", + "type": "boolean", + "value": false + }, + { + "name": "Escaped", + "type": "boolean", + "value": false + } + ], + "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": 104, + "keepRatio": true, + "layer": "MobileControls", + "locked": true, + "name": "JumpButton", + "persistentUuid": "c641f19e-29c5-4424-be1c-656b5505f1ac", + "width": 104, + "x": 1110, + "y": 576, + "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": 270, + "customSize": false, + "height": 40, + "layer": "", + "name": "Flashlight", + "persistentUuid": "4c4fa2af-33a5-4cbf-80fe-de4ad1ef8161", + "width": 40, + "x": 150, + "y": 600, + "z": 48, + "zOrder": 0, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 200, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "d5336879-bc6d-5e29-8df5-c7a98a131af5", + "x": 0, + "y": 0, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "51191bd2-6990-5840-9653-72d2eafa7785", + "x": 200, + "y": 0, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 130 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "52eaf006-6cd6-561a-a925-c10225d042ea", + "x": 200, + "y": 0, + "z": 230, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 220 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "b25d287d-c860-53d7-a429-15c0666e9335", + "x": 200, + "y": 0, + "z": 550, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 70 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 690, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "df1f0704-aacb-54bb-9654-773541698607", + "x": 360, + "y": 0, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "56c5b998-cdbe-5170-a348-bcb4a042f713", + "x": 1050, + "y": 0, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 130 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "77f1873f-6ae8-5344-a582-826931cec1bf", + "x": 1050, + "y": 0, + "z": 230, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 220 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "5f727540-8089-5810-80c6-90d4ac6ca9a2", + "x": 1050, + "y": 0, + "z": 550, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 70 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 190, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "302db6d2-e70c-59d8-a532-8f51e575abfb", + "x": 1210, + "y": 0, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 190, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "ed251035-4f9c-581b-9d04-911ad0abdec7", + "x": 10, + "y": 10, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "6ceea474-aac4-5d04-912a-267246ab7096", + "x": 200, + "y": 10, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 130 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "3edc88cf-772a-5d4b-a5c8-a25ab81029ad", + "x": 200, + "y": 10, + "z": 230, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 220 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "1a93c034-ce76-5682-80c0-f61e8949384d", + "x": 200, + "y": 10, + "z": 550, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 70 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 690, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "4ea9d8e0-84b5-5bc5-a4db-2c3317f15626", + "x": 360, + "y": 10, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "34d61c2b-5ab5-5089-ae86-a5d56c6853a5", + "x": 1050, + "y": 10, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 130 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "249cdba7-9b65-550e-92e7-fd06325d479c", + "x": 1050, + "y": 10, + "z": 230, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 220 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "35bfd319-7af9-5cfa-b010-9c6ef15fc2ad", + "x": 1050, + "y": 10, + "z": 550, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 70 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 180, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "9e481ea5-47c4-5047-85ad-5882118a158d", + "x": 1210, + "y": 10, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 200, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "54baec08-497c-5dc6-9bd9-75da1ac78a46", + "x": 0, + "y": 990, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "73289677-cc57-5920-91e7-5d845e77ed3d", + "x": 200, + "y": 990, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 130 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "a16ac16c-9701-5e93-9484-10d1a9bc1174", + "x": 200, + "y": 990, + "z": 230, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 220 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "4b249373-5264-5434-b7fe-24d20f8c3b20", + "x": 200, + "y": 990, + "z": 550, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 70 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 200, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "54245683-fd29-5297-8164-1132452070dd", + "x": 360, + "y": 990, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 140, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "dd69f5ef-d24c-5a6d-816a-7816033ba4fe", + "x": 560, + "y": 990, + "z": 230, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 220 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 140, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "af4f5509-f926-5761-a49c-7d901553d30a", + "x": 560, + "y": 990, + "z": 550, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 70 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 20, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "a7f7354e-bb88-5f04-91de-f2874f992f62", + "x": 700, + "y": 990, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 450 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 20, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "ebb27bb0-24a1-58e8-8b53-578b604b1a47", + "x": 700, + "y": 990, + "z": 550, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 70 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 330, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "2ae1397b-fcd5-58fa-9c09-43a49bf6880e", + "x": 720, + "y": 990, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "2d0cfc21-88c8-5cb7-a458-4e7ba355456d", + "x": 1050, + "y": 990, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 130 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "1457d685-029c-55a1-a790-333171719672", + "x": 1050, + "y": 990, + "z": 230, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 220 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "dea66d77-360e-5f0d-9dcf-4f2254f8f8ac", + "x": 1050, + "y": 990, + "z": 550, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 70 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 190, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "51d212cf-73d5-5578-86c1-f864bdf6c6a4", + "x": 1210, + "y": 990, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 190, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "c0108f26-e2cd-5a80-bb20-9757f9f3f665", + "x": 10, + "y": 980, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "b80cb36b-816f-5518-adcf-5256b5151e09", + "x": 200, + "y": 980, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 130 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "cad6ea7b-ee2c-50c5-924e-011e95b01972", + "x": 200, + "y": 980, + "z": 230, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 220 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "98904f22-4e65-5522-9906-844aead77986", + "x": 200, + "y": 980, + "z": 550, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 70 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 200, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "fb7ee0d9-281c-5144-87ce-2bdcdc1339a6", + "x": 360, + "y": 980, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 140, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "be06e5f2-903b-5348-bc7d-d843aebba042", + "x": 560, + "y": 980, + "z": 230, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 220 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 140, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "84f16040-407a-59a0-83ed-0c1417eacbee", + "x": 560, + "y": 980, + "z": 550, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 70 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 20, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "e272f068-9986-5f17-affb-c5725a796337", + "x": 700, + "y": 980, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 450 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 20, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "a2120900-b300-5a05-a76b-ac1f31409986", + "x": 700, + "y": 980, + "z": 550, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 70 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 330, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "c8d7f830-ccb7-5832-a476-c61977c7a712", + "x": 720, + "y": 980, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "b7dff05b-9593-56e6-b2c8-4c64e8b87763", + "x": 1050, + "y": 980, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 130 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "c3d61135-f6e8-5102-a5f8-e2d83ddbb383", + "x": 1050, + "y": 980, + "z": 230, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 220 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 160, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "07953a65-6054-541f-8c51-ebe93e54f69b", + "x": 1050, + "y": 980, + "z": 550, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 70 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 180, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "e8226859-b104-55f0-8286-c9e870c986dc", + "x": 1210, + "y": 980, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 110, + "width": 10, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "256b175d-277b-5761-b9f6-d830639e2fa7", + "x": 0, + "y": 10, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "c2f40a11-be4c-5e41-b500-785f3bcb17e4", + "x": 0, + "y": 120, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 130 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "8ce5ac81-9eb4-553e-81d0-c8eaea400ce2", + "x": 0, + "y": 120, + "z": 230, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 220 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "e8461610-41e5-5815-9b60-a1999009d1a1", + "x": 0, + "y": 120, + "z": 550, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 70 + }, + { + "angle": 0, + "customSize": true, + "height": 370, + "width": 10, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "3a9ad51c-b977-54b5-99ed-ba1e363ae2de", + "x": 0, + "y": 280, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "0b1da844-d66d-5b87-b0af-b8bcad235d89", + "x": 0, + "y": 650, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 130 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "a2990fa8-9c3b-5782-94ba-c020928f38d1", + "x": 0, + "y": 650, + "z": 230, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 220 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "181bad6f-2ec9-57e1-a51e-4fb2e88dbd56", + "x": 0, + "y": 650, + "z": 550, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 70 + }, + { + "angle": 0, + "customSize": true, + "height": 180, + "width": 10, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "08588805-735f-5a20-98ef-afd5f1b8cce8", + "x": 0, + "y": 810, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 100, + "width": 10, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "2aa5bdcc-0e25-5834-8902-abd9a4937a17", + "x": 10, + "y": 20, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "3806fb8b-712c-5aab-a99c-4e84fc78a10b", + "x": 10, + "y": 120, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 130 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "9430fc56-0885-5c0e-be1f-4a02f38e8b46", + "x": 10, + "y": 120, + "z": 230, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 220 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "84fb447c-c59e-5630-947d-9723d12de60d", + "x": 10, + "y": 120, + "z": 550, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 70 + }, + { + "angle": 0, + "customSize": true, + "height": 370, + "width": 10, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "bc8d9067-8f23-5ed4-8c8f-bac85106bf65", + "x": 10, + "y": 280, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "9421f71f-92b9-58ff-9f52-1067d0c61f50", + "x": 10, + "y": 650, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 130 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "9b6566a3-0bcb-5c69-850a-4a859ef19224", + "x": 10, + "y": 650, + "z": 230, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 220 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "d64b3d0c-13f9-53cf-93a2-0351e76c8706", + "x": 10, + "y": 650, + "z": 550, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 70 + }, + { + "angle": 0, + "customSize": true, + "height": 170, + "width": 10, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "7378ddd5-27b9-5099-96f6-3bb1d4deff6f", + "x": 10, + "y": 810, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 110, + "width": 10, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "26f3cd12-d2e1-58fa-8308-b9f701189979", + "x": 1390, + "y": 10, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "8f03a476-ca42-5e11-bd92-e005b5eeb518", + "x": 1390, + "y": 120, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 450 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "7fc0da59-888d-5198-8555-84232278668d", + "x": 1390, + "y": 120, + "z": 550, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 70 + }, + { + "angle": 0, + "customSize": true, + "height": 370, + "width": 10, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "e756c535-e011-59af-a31f-fcba34c9654e", + "x": 1390, + "y": 280, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "ceacb24c-e57b-59ea-835e-2e5a611ad659", + "x": 1390, + "y": 650, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 130 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "37133fd2-77f1-516c-860b-11006982d45d", + "x": 1390, + "y": 650, + "z": 230, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 220 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "27b49f2d-d20a-5010-9b20-8493cb2d2680", + "x": 1390, + "y": 650, + "z": 550, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 70 + }, + { + "angle": 0, + "customSize": true, + "height": 180, + "width": 10, + "layer": "", + "locked": false, + "name": "OuterWall", + "persistentUuid": "4499620d-ee1d-5d33-a7b6-4ea44c880c0f", + "x": 1390, + "y": 810, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 100, + "width": 10, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "d0911579-3068-554b-ac19-d4c411253869", + "x": 1380, + "y": 20, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "295fb4aa-151a-55cc-9393-249ec43acc6e", + "x": 1380, + "y": 120, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 450 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "120aa216-f0a0-53f3-b2c5-ad2462329fe8", + "x": 1380, + "y": 120, + "z": 550, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 70 + }, + { + "angle": 0, + "customSize": true, + "height": 370, + "width": 10, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "2ae646ad-e725-51d4-878d-74b3bbfe93ab", + "x": 1380, + "y": 280, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "0e2c961a-b055-5a5d-bfb2-058a75d40cdf", + "x": 1380, + "y": 650, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 130 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "f8e0c98a-572f-5823-bf30-2100e27e1110", + "x": 1380, + "y": 650, + "z": 230, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 220 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 10, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "9904404f-4c2a-558d-bc18-b7ce4787a8d1", + "x": 1380, + "y": 650, + "z": 550, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 70 + }, + { + "angle": 0, + "customSize": true, + "height": 170, + "width": 10, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "95047f84-5278-5b7b-beeb-b8a557ab3a44", + "x": 1380, + "y": 810, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 620 + }, + { + "angle": 0, + "customSize": true, + "height": 4, + "width": 160, + "layer": "", + "locked": false, + "name": "Window", + "persistentUuid": "3235af4a-3166-53bc-802a-723516913528", + "x": 200, + "y": 8, + "z": 130, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 100 + }, + { + "angle": 0, + "customSize": true, + "height": 4, + "width": 160, + "layer": "", + "locked": false, + "name": "Window", + "persistentUuid": "48252751-2d37-506f-ac7b-d71707f8da34", + "x": 1050, + "y": 8, + "z": 130, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 100 + }, + { + "angle": 0, + "customSize": true, + "height": 4, + "width": 160, + "layer": "", + "locked": false, + "name": "Window", + "persistentUuid": "e092bace-091d-590e-9aa8-aba792247965", + "x": 200, + "y": 8, + "z": 450, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 100 + }, + { + "angle": 0, + "customSize": true, + "height": 4, + "width": 160, + "layer": "", + "locked": false, + "name": "Window", + "persistentUuid": "6943f24f-46e1-55c4-a098-b9547b80ef55", + "x": 1050, + "y": 8, + "z": 450, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 100 + }, + { + "angle": 0, + "customSize": true, + "height": 4, + "width": 160, + "layer": "", + "locked": false, + "name": "Window", + "persistentUuid": "86ae3594-46be-5a70-9af8-afffd8e32a08", + "x": 200, + "y": 988, + "z": 130, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 100 + }, + { + "angle": 0, + "customSize": true, + "height": 4, + "width": 160, + "layer": "", + "locked": false, + "name": "Window", + "persistentUuid": "a78211ef-7e39-5ee1-adb9-cd24aa2d19f3", + "x": 1050, + "y": 988, + "z": 130, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 100 + }, + { + "angle": 0, + "customSize": true, + "height": 4, + "width": 160, + "layer": "", + "locked": false, + "name": "Window", + "persistentUuid": "94ac4da4-58f1-54dc-afd2-e74f5a64fe63", + "x": 200, + "y": 988, + "z": 450, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 100 + }, + { + "angle": 0, + "customSize": true, + "height": 4, + "width": 160, + "layer": "", + "locked": false, + "name": "Window", + "persistentUuid": "c51cfb65-8846-5d57-8469-83ea56e4dd63", + "x": 560, + "y": 988, + "z": 450, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 100 + }, + { + "angle": 0, + "customSize": true, + "height": 4, + "width": 160, + "layer": "", + "locked": false, + "name": "Window", + "persistentUuid": "ba65ec8a-ac33-5b4a-a683-5faeb4948f11", + "x": 1050, + "y": 988, + "z": 450, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 100 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 4, + "layer": "", + "locked": false, + "name": "Window", + "persistentUuid": "b6b447a6-3ea2-5f39-9c4e-970a763122e2", + "x": 8, + "y": 120, + "z": 130, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 100 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 4, + "layer": "", + "locked": false, + "name": "Window", + "persistentUuid": "37e66342-23a8-5efb-957d-2d0baf55e31d", + "x": 8, + "y": 650, + "z": 130, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 100 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 4, + "layer": "", + "locked": false, + "name": "Window", + "persistentUuid": "99ca77df-74c1-510b-938c-77d672fe2e0c", + "x": 8, + "y": 120, + "z": 450, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 100 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 4, + "layer": "", + "locked": false, + "name": "Window", + "persistentUuid": "dbe9875b-4f07-5f4d-a3e1-e878725c2be5", + "x": 8, + "y": 650, + "z": 450, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 100 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 4, + "layer": "", + "locked": false, + "name": "Window", + "persistentUuid": "97b0366a-409a-5ba7-be26-0d2d9e404c92", + "x": 1388, + "y": 650, + "z": 130, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 100 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 4, + "layer": "", + "locked": false, + "name": "Window", + "persistentUuid": "ce699687-b02b-59f8-9499-6b6359ce3ae3", + "x": 1388, + "y": 120, + "z": 450, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 100 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 4, + "layer": "", + "locked": false, + "name": "Window", + "persistentUuid": "87882c8f-6dd2-586d-8eed-a811f8625716", + "x": 1388, + "y": 650, + "z": 450, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 100 + }, + { + "angle": 0, + "customSize": true, + "height": 1000, + "width": 1400, + "layer": "", + "locked": false, + "name": "Floor", + "persistentUuid": "d313548c-a7b0-565f-a8d3-44d4dbc45120", + "x": 0, + "y": 0, + "z": -20, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 20 + }, + { + "angle": 0, + "customSize": true, + "height": 100, + "width": 20, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "4ea8afe7-1adb-549a-8eca-0e9ffbe7a1a1", + "x": 490, + "y": 20, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 300 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 20, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "e6fe246b-a6aa-5df5-aa05-848bde548038", + "x": 490, + "y": 120, + "z": 220, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 80 + }, + { + "angle": 0, + "customSize": true, + "height": 370, + "width": 20, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "5d8b653f-152c-559d-bcec-6adced1bb31c", + "x": 490, + "y": 280, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 300 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 20, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "80368d75-8070-5375-a4e5-1712fcea83d0", + "x": 490, + "y": 650, + "z": 220, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 80 + }, + { + "angle": 0, + "customSize": true, + "height": 170, + "width": 20, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "fcf50328-09be-5d1b-a76b-d2fe9a4e81d9", + "x": 490, + "y": 810, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 300 + }, + { + "angle": 0, + "customSize": true, + "height": 60, + "width": 20, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "b1ea7094-f150-5d31-b911-547c47d5462a", + "x": 890, + "y": 20, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 300 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 20, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "ac8c36fb-a50d-5482-ae48-59156f3264d5", + "x": 890, + "y": 80, + "z": 220, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 80 + }, + { + "angle": 0, + "customSize": true, + "height": 740, + "width": 20, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "c34db666-1556-52ec-bfb8-5e9b91f975e9", + "x": 890, + "y": 240, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 300 + }, + { + "angle": 0, + "customSize": true, + "height": 20, + "width": 150, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "07429443-dc54-5c07-a6ea-7e2636a0e8d9", + "x": 20, + "y": 390, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 300 + }, + { + "angle": 0, + "customSize": true, + "height": 20, + "width": 160, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "17ec4434-e904-520a-9957-8ef96d42ba6e", + "x": 170, + "y": 390, + "z": 220, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 80 + }, + { + "angle": 0, + "customSize": true, + "height": 20, + "width": 160, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "09441bac-bd0d-5a94-9b87-48ac0dd1db56", + "x": 330, + "y": 390, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 300 + }, + { + "angle": 0, + "customSize": true, + "height": 20, + "width": 150, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "9e5dac8a-dd1c-5476-a1ba-e00f19f2038f", + "x": 910, + "y": 390, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 300 + }, + { + "angle": 0, + "customSize": true, + "height": 20, + "width": 160, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "35bd4fd2-d79f-5fbd-9926-d8b97bb1e67e", + "x": 1060, + "y": 390, + "z": 220, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 80 + }, + { + "angle": 0, + "customSize": true, + "height": 20, + "width": 160, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "40647d0b-e425-566a-97fa-3b6440612b99", + "x": 1220, + "y": 390, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 300 + }, + { + "angle": 0, + "customSize": true, + "height": 32, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "7adcb961-d375-5e7d-9f28-6107fb041810", + "x": 700, + "y": 868, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 16 + }, + { + "angle": 0, + "customSize": true, + "height": 32, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "b502ae6b-e75e-5766-8c21-727af9e2346d", + "x": 700, + "y": 836, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 32 + }, + { + "angle": 0, + "customSize": true, + "height": 32, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "cfe86e63-b93d-5cdd-9ac9-3f40007db489", + "x": 700, + "y": 804, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 48 + }, + { + "angle": 0, + "customSize": true, + "height": 32, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "2780b98e-d776-5d5e-91a3-fefbc570c5c1", + "x": 700, + "y": 772, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 64 + }, + { + "angle": 0, + "customSize": true, + "height": 32, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "8cc85f48-00db-56ba-9f21-d4ee4aa69ce7", + "x": 700, + "y": 740, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 80 + }, + { + "angle": 0, + "customSize": true, + "height": 32, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "64eead34-2b65-53eb-8e3c-15f32eeb0e8c", + "x": 700, + "y": 708, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 96 + }, + { + "angle": 0, + "customSize": true, + "height": 32, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "b62cd1b3-7599-5389-8847-72dab397260d", + "x": 700, + "y": 676, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 112 + }, + { + "angle": 0, + "customSize": true, + "height": 32, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "276beb74-9f1c-5a76-8941-8d3a99ea753f", + "x": 700, + "y": 644, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 128 + }, + { + "angle": 0, + "customSize": true, + "height": 32, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "30de2b60-325b-559a-9fab-af83338b1f72", + "x": 700, + "y": 612, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 144 + }, + { + "angle": 0, + "customSize": true, + "height": 32, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "2ddfa692-c822-54b9-82f9-0bd85f4f975f", + "x": 700, + "y": 580, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 160 + }, + { + "angle": 0, + "customSize": true, + "height": 32, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "3c42386c-61c8-5f9c-b7ff-879856745742", + "x": 700, + "y": 548, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 176 + }, + { + "angle": 0, + "customSize": true, + "height": 32, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "85e04bd6-8384-50b8-a184-ba57cf66214a", + "x": 700, + "y": 516, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 192 + }, + { + "angle": 0, + "customSize": true, + "height": 32, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "4fc77077-d68a-58c7-b6ab-9ee25b23bfa9", + "x": 700, + "y": 484, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 208 + }, + { + "angle": 0, + "customSize": true, + "height": 32, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "25de4e02-bfb2-56b4-951e-cab8a86522e1", + "x": 700, + "y": 452, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 224 + }, + { + "angle": 0, + "customSize": true, + "height": 32, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "11df2c23-c49d-5575-8d90-21b3e7b21289", + "x": 700, + "y": 420, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 240 + }, + { + "angle": 0, + "customSize": true, + "height": 32, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "f7614b4a-1339-5d55-9912-a87524335a1d", + "x": 700, + "y": 388, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 256 + }, + { + "angle": 0, + "customSize": true, + "height": 32, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "87db051c-8d27-513f-9e5d-236e5bbe270c", + "x": 700, + "y": 356, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 272 + }, + { + "angle": 0, + "customSize": true, + "height": 32, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "0d21e1cb-e9fa-5603-8073-b85ecd09f6be", + "x": 700, + "y": 324, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 288 + }, + { + "angle": 0, + "customSize": true, + "height": 32, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "c2203695-1b47-5f3b-b60d-6d083e625b69", + "x": 700, + "y": 292, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 304 + }, + { + "angle": 0, + "customSize": true, + "height": 32, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "bd7b9d07-1824-53ec-860d-d3a7a67e5a7b", + "x": 700, + "y": 260, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 320 + }, + { + "angle": 0, + "customSize": true, + "height": 960, + "width": 680, + "layer": "", + "locked": false, + "name": "Floor", + "persistentUuid": "f603c15b-94b8-588e-adb3-61b3853b224f", + "x": 20, + "y": 20, + "z": 300, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 20 + }, + { + "angle": 0, + "customSize": true, + "height": 960, + "width": 490, + "layer": "", + "locked": false, + "name": "Floor", + "persistentUuid": "a699d277-1c24-5ae8-963e-6d2fcc9cb4c4", + "x": 890, + "y": 20, + "z": 300, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 20 + }, + { + "angle": 0, + "customSize": true, + "height": 240, + "width": 190, + "layer": "", + "locked": false, + "name": "Floor", + "persistentUuid": "d75a70e6-2337-520c-b5c2-b4720932cadc", + "x": 700, + "y": 20, + "z": 300, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 20 + }, + { + "angle": 0, + "customSize": true, + "height": 280, + "width": 190, + "layer": "", + "locked": false, + "name": "Floor", + "persistentUuid": "7b704f7a-1c18-5021-a2e5-939d03f91998", + "x": 700, + "y": 700, + "z": 300, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 20 + }, + { + "angle": 0, + "customSize": true, + "height": 440, + "width": 12, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "68a339f0-be45-511b-921f-ad98acd25344", + "x": 688, + "y": 260, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 100 + }, + { + "angle": 0, + "customSize": true, + "height": 12, + "width": 190, + "layer": "", + "locked": false, + "name": "Stairs", + "persistentUuid": "b80100c4-2fae-56b7-af1e-fb0c63d73f83", + "x": 700, + "y": 700, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 100 + }, + { + "angle": 0, + "customSize": true, + "height": 80, + "width": 20, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "6cc54f9a-a3a8-5578-a44d-68e18101fbb1", + "x": 490, + "y": 20, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 280 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 20, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "77c44654-0ed7-5c1e-8f05-59602066be76", + "x": 490, + "y": 100, + "z": 540, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 60 + }, + { + "angle": 0, + "customSize": true, + "height": 500, + "width": 20, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "94efd55f-4d7b-5481-97cc-08c0eabdd637", + "x": 490, + "y": 260, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 280 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 20, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "54319adb-6da8-5990-bea7-bfea318cbd82", + "x": 490, + "y": 760, + "z": 540, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 60 + }, + { + "angle": 0, + "customSize": true, + "height": 60, + "width": 20, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "f9953752-55ad-5abd-bb78-49344662c23f", + "x": 490, + "y": 920, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 280 + }, + { + "angle": 0, + "customSize": true, + "height": 80, + "width": 20, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "ed253d29-c948-57a4-80b5-cc7bc5b8b055", + "x": 890, + "y": 20, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 280 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 20, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "bfb7335e-4204-576c-aa4d-e5120dfd4659", + "x": 890, + "y": 100, + "z": 540, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 60 + }, + { + "angle": 0, + "customSize": true, + "height": 500, + "width": 20, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "e43fd7d2-46e9-5170-b044-582fe1caf22c", + "x": 890, + "y": 260, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 280 + }, + { + "angle": 0, + "customSize": true, + "height": 160, + "width": 20, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "c8e20882-e195-5260-8ab6-64cdccec9a66", + "x": 890, + "y": 760, + "z": 540, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 60 + }, + { + "angle": 0, + "customSize": true, + "height": 60, + "width": 20, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "143f1de7-e919-5701-a8ca-ed78635f9855", + "x": 890, + "y": 920, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 280 + }, + { + "angle": 0, + "customSize": true, + "height": 20, + "width": 470, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "b2f19d43-acf7-50b0-b626-ff6d3191d4a8", + "x": 20, + "y": 490, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 280 + }, + { + "angle": 0, + "customSize": true, + "height": 20, + "width": 470, + "layer": "", + "locked": false, + "name": "Wall", + "persistentUuid": "16bd2cf8-1e25-5596-8ffd-aba8ff0fc8f6", + "x": 910, + "y": 390, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 280 + }, + { + "angle": 0, + "customSize": true, + "height": 960, + "width": 1360, + "layer": "", + "locked": false, + "name": "Floor", + "persistentUuid": "2a898768-fb7b-566c-8245-db215c183055", + "x": 20, + "y": 20, + "z": 600, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 20 + }, + { + "angle": 0, + "customSize": true, + "height": 1080, + "width": 1480, + "layer": "", + "locked": false, + "name": "Roof", + "persistentUuid": "94717079-421b-5839-bb42-9d4a4c1de9f4", + "x": -40, + "y": -40, + "z": 620, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 30 + }, + { + "angle": 0, + "customSize": true, + "height": 100, + "width": 100, + "layer": "", + "locked": false, + "name": "Roof", + "persistentUuid": "0105b549-5984-5c5d-8b1f-1fc6d7624a00", + "x": 1150, + "y": 150, + "z": 650, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 150 + }, + { + "angle": 0, + "customSize": true, + "height": 140, + "width": 300, + "layer": "", + "locked": false, + "name": "Floor", + "persistentUuid": "f56bb65e-f4e2-5885-98c2-1102a2c8595d", + "x": 480, + "y": 1000, + "z": -20, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 28 + }, + { + "angle": 0, + "customSize": true, + "height": 10, + "width": 140, + "layer": "", + "locked": false, + "name": "Door", + "persistentUuid": "5eb543f4-a5e1-5fc8-b156-952e122d7875", + "x": 560, + "y": 990, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 230 + }, + { + "angle": 0, + "customSize": true, + "height": 500, + "width": 2600, + "layer": "", + "locked": false, + "name": "Ground", + "persistentUuid": "012f4e33-9ed9-5c14-854d-bfef74b80a69", + "x": -600, + "y": -500, + "z": -30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 30 + }, + { + "angle": 0, + "customSize": true, + "height": 900, + "width": 2600, + "layer": "", + "locked": false, + "name": "Ground", + "persistentUuid": "8fe512d2-fc1f-5fca-9a2f-33a5fa1107c1", + "x": -600, + "y": 1000, + "z": -30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 30 + }, + { + "angle": 0, + "customSize": true, + "height": 1000, + "width": 600, + "layer": "", + "locked": false, + "name": "Ground", + "persistentUuid": "3bef1146-7c36-51f9-9f0d-6f5c42701ba4", + "x": -600, + "y": 0, + "z": -30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 30 + }, + { + "angle": 0, + "customSize": true, + "height": 1000, + "width": 600, + "layer": "", + "locked": false, + "name": "Ground", + "persistentUuid": "2e49d285-efd7-5297-a76c-7b36d040ed79", + "x": 1400, + "y": 0, + "z": -30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 30 + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "63fddf63-4495-5dd5-be7f-0338a18dae1c", + "x": -350, + "y": -400, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "835a2143-db23-5487-b002-fde3220d34b8", + "x": -50, + "y": -400, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "3e2e9b25-a79e-5d69-84e2-c8846776016a", + "x": 250, + "y": -400, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "543df4db-bc5a-53f0-a376-ff433c6e3e70", + "x": 550, + "y": -400, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "bef37868-1434-5deb-a5f3-2f18dbb2e6f0", + "x": 850, + "y": -400, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "ab7f43f2-2960-5d01-90c9-86b6b28b6e4f", + "x": 1150, + "y": -400, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "3886e7c9-1cb2-5bcc-b4a6-5a0280b87597", + "x": 1450, + "y": -400, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "ba95f666-bff3-5be6-9062-83402dd89656", + "x": 1750, + "y": -400, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "d6c7d087-0b0d-5db5-815e-7e1deedd1ced", + "x": -330, + "y": 1800, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "850d0a2d-2245-56ad-8756-2dc7215d3de3", + "x": 10, + "y": 1800, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "3521e77b-b720-54f8-bc7d-4ae814d26292", + "x": 350, + "y": 1800, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "3e07abe2-b7c1-5c18-98e0-273d2f7c05e8", + "x": 885, + "y": 1800, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "f6e96a10-cde2-5d8a-82dc-3ccb671e997b", + "x": 1175, + "y": 1800, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "33c25ec0-53f6-58b8-9f83-c456318f36ca", + "x": 1465, + "y": 1800, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "40d8c87c-dda6-54a3-9c7f-815f3cb8dfdc", + "x": 1755, + "y": 1800, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "65a3421e-3c24-553c-bc03-02ebdb911c18", + "x": -500, + "y": -242.9, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "3f88046f-03d8-54c2-97f9-0123147a94aa", + "x": -500, + "y": 71.4, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "04d6f513-d38c-5cdd-b121-67d6f3b6a752", + "x": -500, + "y": 385.7, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "3207b075-d54d-54d6-8627-842a13f1dbff", + "x": -500, + "y": 700, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "bb4de374-279c-5a02-8b19-101b5b8c7268", + "x": -500, + "y": 1014.3, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "6b47f462-bc42-5446-9401-dc73a3b2b4e5", + "x": -500, + "y": 1328.6, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "9cd253fa-87c5-519d-a5d4-b5a232e1fa84", + "x": -500, + "y": 1642.9, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "416e4eae-20d1-59bd-a695-88a2058014e9", + "x": 1900, + "y": -242.9, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "5d72914a-1f04-5ba4-84d2-a7e6756664e5", + "x": 1900, + "y": 71.4, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "68645427-c3b1-5015-8bfc-0cad92140661", + "x": 1900, + "y": 385.7, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "7d376e74-0378-5123-a974-e213e41abb86", + "x": 1900, + "y": 700, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "06676615-5b0a-5a3a-8e46-be67e1945b53", + "x": 1900, + "y": 1014.3, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "d525f725-af15-5b9a-8d23-a43e57c84e4d", + "x": 1900, + "y": 1328.6, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Fence", + "persistentUuid": "9135d3cb-3961-581d-8f71-2af78faa631c", + "x": 1900, + "y": 1642.9, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 90, + "width": 220, + "layer": "", + "locked": false, + "name": "ExitZone", + "persistentUuid": "5cc5f986-a2ff-5bd6-853e-bd936ed2c503", + "x": 520, + "y": 1810, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 200 + }, + { + "angle": 0, + "customSize": true, + "height": 40, + "width": 2600, + "layer": "", + "locked": false, + "name": "Boundary", + "persistentUuid": "8e429f5c-af1d-5691-b2f6-2bfdc47b71e7", + "x": -600, + "y": 1900, + "z": -30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 430 + }, + { + "angle": 0, + "customSize": true, + "height": 2400, + "width": 40, + "layer": "", + "locked": false, + "name": "Boundary", + "persistentUuid": "7535d900-c89c-5426-aa11-70b8c2580782", + "x": -640, + "y": -500, + "z": -30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 430 + }, + { + "angle": 0, + "customSize": true, + "height": 2400, + "width": 40, + "layer": "", + "locked": false, + "name": "Boundary", + "persistentUuid": "22ddcbdb-a568-5bbd-b88e-310d7452a91d", + "x": 2000, + "y": -500, + "z": -30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 430 + }, + { + "angle": 0, + "customSize": true, + "height": 40, + "width": 2600, + "layer": "", + "locked": false, + "name": "Boundary", + "persistentUuid": "a21f7ce6-37eb-5774-be8a-47bca8f0cc1c", + "x": -600, + "y": -540, + "z": -30, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 430 + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Lightpost", + "persistentUuid": "3d90bbe5-f9df-5f2d-a685-0f63010220d8", + "x": 490, + "y": 1760, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Lightpost", + "persistentUuid": "4486200d-34f2-52be-b456-f4cc2d9be191", + "x": 770, + "y": 1760, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 10, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "DeadTree", + "persistentUuid": "b453cb7b-7f25-55dd-bdff-7a19be659902", + "x": -300, + "y": 1300, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 80, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "DeadWillow", + "persistentUuid": "1b440069-51b7-53ad-a359-f18289d58e80", + "x": 1700, + "y": 1400, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 150, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "DeadWillow", + "persistentUuid": "a505629a-6717-57af-9e4f-796d8220d7c6", + "x": -350, + "y": 200, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 220, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "DeadTree", + "persistentUuid": "08303ef7-1b22-596a-b309-7350b4a60969", + "x": 1750, + "y": 300, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 300, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "DeadTree", + "persistentUuid": "753d36ce-fb4f-5e23-b0a0-b3ac1e18132b", + "x": 1600, + "y": 1650, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 40, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "DeadWillow", + "persistentUuid": "7a80d3b5-f91e-552b-8531-4c1941b8f105", + "x": 150, + "y": 1600, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 30, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Rock", + "persistentUuid": "d8bc52cd-197d-5e22-be45-c02b71859561", + "x": -150, + "y": 1500, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 200, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Rock", + "persistentUuid": "09440e77-dcbc-56d2-a0d7-a821af535f0e", + "x": 1500, + "y": 1200, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Rock", + "persistentUuid": "b691190b-9d45-55e7-ac9d-39764d1dbf23", + "x": 1650, + "y": -200, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 60, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "SmallRock", + "persistentUuid": "597bfb8a-cd8b-5791-91d4-c13d64956fef", + "x": 380, + "y": 1250, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 140, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "SmallRock", + "persistentUuid": "8e16629b-25a2-51f6-bf2a-cfe7f981f97c", + "x": 960, + "y": 1450, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 10, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "SmallRock", + "persistentUuid": "0779f9b2-fd07-515f-a3de-b4dff8c71c9e", + "x": -400, + "y": 700, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 250, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "SmallRock", + "persistentUuid": "3703d8f9-5329-539b-81fa-aefb3b6db7be", + "x": 1800, + "y": 900, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 270, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Commode", + "persistentUuid": "9f9be44f-8dc5-55bc-8584-bd97de1960c6", + "x": 48.4, + "y": 520, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 270, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "TV", + "persistentUuid": "ba17903b-42e0-5bf5-a54f-5fe0dfece4af", + "x": 48.4, + "y": 520, + "z": 110, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Sofa", + "persistentUuid": "57ec8049-d6a1-5a87-9855-04d0bac4bc37", + "x": 330, + "y": 520, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 135, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "RockingChair", + "persistentUuid": "9c2a6e88-0859-5b35-962f-544d2ad50f29", + "x": 200, + "y": 700, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Bookshelf", + "persistentUuid": "8f378a0b-ad56-54c2-b25e-bc614396913e", + "x": 430, + "y": 956.9, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Nightstand", + "persistentUuid": "8e213ad2-7775-5ca4-a9ee-4a3c9c55d920", + "x": 55, + "y": 935, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "OilLamp", + "persistentUuid": "8ed06690-e407-5a82-8033-0aeecfab5481", + "x": 55, + "y": 935, + "z": 50, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Cupboard", + "persistentUuid": "209fc463-686a-5d7d-92f9-e3d0a97ebedc", + "x": 420, + "y": 433.3, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "KitchenCabinet", + "persistentUuid": "afbbeda0-cb3c-5bd8-827c-095cf9ebbdc6", + "x": 61.5, + "y": 41.4, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Stove", + "persistentUuid": "15a72f32-af36-5a08-a79e-4f7c9d0bfe1f", + "x": 121.5, + "y": 48.4, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "KitchenCabinet", + "persistentUuid": "baa6fdec-725a-5591-b936-8f8e39898286", + "x": 181.5, + "y": 41.4, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "KitchenCabinet", + "persistentUuid": "d503f35e-04aa-54ac-9311-d259eda2a556", + "x": 258.5, + "y": 41.4, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Cupboard", + "persistentUuid": "025a9971-85f1-5c93-98d7-b9bc93630dfa", + "x": 458.9, + "y": 43.3, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Table", + "persistentUuid": "20376d54-b701-5be9-9bcb-b53c033ecbdd", + "x": 255, + "y": 245, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Chair", + "persistentUuid": "8e792451-ccf9-5c33-989e-4364bd55f2b5", + "x": 217.5, + "y": 208, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Chair", + "persistentUuid": "fbf4b307-46f6-528f-98cf-f6cff1d1182c", + "x": 217.5, + "y": 282, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Chair", + "persistentUuid": "e808f0ba-6504-52f2-93eb-6c9702f9196f", + "x": 292.5, + "y": 208, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Chair", + "persistentUuid": "ace3fa25-91a4-590c-a621-a9c05fbc15fc", + "x": 292.5, + "y": 282, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 270, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Clock", + "persistentUuid": "c05087ac-a221-567a-9550-9d78729ab7f8", + "x": 527.6, + "y": 880, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Commode", + "persistentUuid": "7a992004-3cb2-5d7e-9460-86a8d0709c05", + "x": 600, + "y": 48.4, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "OilLamp", + "persistentUuid": "adbfeb3d-681e-5dad-af93-7ca2e33f5f7b", + "x": 600, + "y": 48.4, + "z": 110, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Bookshelf", + "persistentUuid": "5ba30d7e-782c-50e6-9fc4-413bdb50b4cc", + "x": 1356.9, + "y": 500, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Bookshelf", + "persistentUuid": "3a705d13-b54f-5abb-847b-89b4a06a24b7", + "x": 1356.9, + "y": 900, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Desk", + "persistentUuid": "f255fd87-06fe-53cf-9ee6-87f87c45747c", + "x": 1130, + "y": 954, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Chair", + "persistentUuid": "30954c16-f1dd-5389-8b8b-918a01f8ed5d", + "x": 1130, + "y": 922.4, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 315, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "RockingChair", + "persistentUuid": "9ccd061c-cebf-5308-9a97-bba36e816db9", + "x": 990, + "y": 500, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 270, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Cupboard", + "persistentUuid": "1ebf103a-58d4-5e73-a0a3-2a76235da6dd", + "x": 933.3, + "y": 880, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Rack", + "persistentUuid": "f8114557-0b73-55f7-9563-b587795e774b", + "x": 1355.9, + "y": 90, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Rack", + "persistentUuid": "04e57876-907a-52ea-934d-154da3544920", + "x": 1290, + "y": 44.1, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 43, + "width": 43, + "layer": "", + "locked": false, + "name": "PushableBox", + "persistentUuid": "cbea3126-dfd6-5f4a-9206-4c469b169d6a", + "x": 963.5, + "y": 48.5, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 43 + }, + { + "angle": 0, + "customSize": true, + "height": 43, + "width": 43, + "layer": "", + "locked": false, + "name": "PushableBox", + "persistentUuid": "90872ff4-af6e-5a1a-aeca-9cc246125b24", + "x": 1008.5, + "y": 48.5, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 43 + }, + { + "angle": 0, + "customSize": true, + "height": 43, + "width": 43, + "layer": "", + "locked": false, + "name": "PushableBox", + "persistentUuid": "a70f609f-adfa-507e-b315-63c1be290665", + "x": 985.5, + "y": 48.5, + "z": 43, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 43 + }, + { + "angle": 0, + "customSize": true, + "height": 43, + "width": 43, + "layer": "", + "locked": false, + "name": "PushableBox", + "persistentUuid": "ed5603c0-d275-5be8-90c1-ef6de445329a", + "x": 1053.5, + "y": 48.5, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 43 + }, + { + "angle": 0, + "customSize": true, + "height": 43, + "width": 43, + "layer": "", + "locked": false, + "name": "PushableBox", + "persistentUuid": "328d614c-cb35-5e3b-a7ef-2c243b6f0b6d", + "x": 1228.5, + "y": 278.5, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 43 + }, + { + "angle": 0, + "customSize": true, + "height": 43, + "width": 43, + "layer": "", + "locked": false, + "name": "PushableBox", + "persistentUuid": "f607896b-bf20-5257-ac6a-8c2035fc1380", + "x": 1178.5, + "y": 298.5, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 43 + }, + { + "angle": 270, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Bed", + "persistentUuid": "92a59422-8b9b-51e0-8778-219e77743c53", + "x": 54.5, + "y": 200, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 270, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Nightstand", + "persistentUuid": "2c7d4e3f-462f-51f5-b8c7-6856fe6538a3", + "x": 37.2, + "y": 360, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "OilLamp", + "persistentUuid": "cd657883-4e52-5639-a761-93290acd2617", + "x": 37.2, + "y": 360, + "z": 370, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Cupboard", + "persistentUuid": "5e90851b-5888-5a44-ab31-797eb7f90303", + "x": 300, + "y": 466.7, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Mirror", + "persistentUuid": "6ce24e7c-ff4d-5a4f-ae64-4cff9bae9442", + "x": 420, + "y": 24.3, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "DoubleBed", + "persistentUuid": "0e7b09ee-f18f-57c0-a6b4-18da79ee37db", + "x": 120, + "y": 893, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Nightstand", + "persistentUuid": "ca11b634-7f6b-5b12-b225-94f369d05106", + "x": 235, + "y": 962.9, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 270, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Bookshelf", + "persistentUuid": "dc32a92e-acba-585c-8896-76931d3d8448", + "x": 43.2, + "y": 580, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Commode", + "persistentUuid": "351e723e-f010-5b90-915b-11ec9f6013a4", + "x": 300, + "y": 538.4, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Mirror", + "persistentUuid": "bd3d5382-973b-5e3b-9310-b1397e5292f0", + "x": 985, + "y": 24.3, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Commode", + "persistentUuid": "5f84610f-34bd-55f7-ad45-9f4dbfa51360", + "x": 1150, + "y": 361.7, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Cupboard", + "persistentUuid": "9d3072d8-a9b7-520d-91e1-80626f2d0f9f", + "x": 1356.7, + "y": 330, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 120, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "RockingChair", + "persistentUuid": "2de02fa1-0f79-58f1-a754-628df0af565d", + "x": 1250, + "y": 170, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "DoubleBed", + "persistentUuid": "f9893ab1-996e-5e1c-a358-af3f7bc9022b", + "x": 1293, + "y": 530, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Nightstand", + "persistentUuid": "39cfa858-a88a-569b-aa27-ef9a7a27df14", + "x": 1362.9, + "y": 625, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Desk", + "persistentUuid": "d1da462f-26de-505e-a02b-d0fea252924a", + "x": 1000, + "y": 954, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Chair", + "persistentUuid": "ee4585d2-99cd-5192-8b84-04c5d383c1f0", + "x": 1000, + "y": 922.4, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 180, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Commode", + "persistentUuid": "a4bfe232-ae46-536e-8287-6d116902fbf1", + "x": 1290, + "y": 951.7, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 270, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Mirror", + "persistentUuid": "6009c336-6942-59e1-bc8f-ef1cd53efcb3", + "x": 914.3, + "y": 500, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 270, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Cupboard", + "persistentUuid": "a9168138-74ff-5290-86de-56e76321e969", + "x": 933.3, + "y": 650, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 240, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "RockingChair", + "persistentUuid": "5b1d75ae-9572-5ddb-89b2-b7362210a2c5", + "x": 1130, + "y": 700, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 30, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Key", + "persistentUuid": "079ee210-7242-58d1-beab-2f2a9ecd22e9", + "x": 1000, + "y": 954, + "z": 380.2, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 20, + "width": 20, + "layer": "", + "locked": false, + "name": "PatrolPoint", + "persistentUuid": "b31b22be-9bc8-5bdc-b16b-d82acfa7d2dc", + "x": 100, + "y": 310, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 20 + }, + { + "angle": 0, + "customSize": true, + "height": 20, + "width": 20, + "layer": "", + "locked": false, + "name": "PatrolPoint", + "persistentUuid": "8cc3f6c1-9971-52a6-888e-88e937acd8a2", + "x": 245, + "y": 610, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 20 + }, + { + "angle": 0, + "customSize": true, + "height": 20, + "width": 20, + "layer": "", + "locked": false, + "name": "PatrolPoint", + "persistentUuid": "c1c5f8d6-c582-5723-b7e9-c44a50985881", + "x": 590, + "y": 110, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 20 + }, + { + "angle": 0, + "customSize": true, + "height": 20, + "width": 20, + "layer": "", + "locked": false, + "name": "PatrolPoint", + "persistentUuid": "727e97f7-8683-567c-b15b-380308262f43", + "x": 590, + "y": 870, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 20 + }, + { + "angle": 0, + "customSize": true, + "height": 20, + "width": 20, + "layer": "", + "locked": false, + "name": "PatrolPoint", + "persistentUuid": "57bc6b95-95ff-58d0-ae99-049b35152b5d", + "x": 1140, + "y": 630, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 20 + }, + { + "angle": 0, + "customSize": true, + "height": 20, + "width": 20, + "layer": "", + "locked": false, + "name": "PatrolPoint", + "persistentUuid": "6169e802-1652-5f8b-9f43-f4b1ae97941e", + "x": 1140, + "y": 220, + "z": 0, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 20 + }, + { + "angle": 0, + "customSize": true, + "height": 20, + "width": 20, + "layer": "", + "locked": false, + "name": "PatrolPoint", + "persistentUuid": "4bb5d5ec-23f9-5291-a71e-093e9dfe5dee", + "x": 590, + "y": 130, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 20 + }, + { + "angle": 0, + "customSize": true, + "height": 20, + "width": 20, + "layer": "", + "locked": false, + "name": "PatrolPoint", + "persistentUuid": "0341e202-1d27-5b98-a55a-cdb1e3e8b67a", + "x": 240, + "y": 250, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 20 + }, + { + "angle": 0, + "customSize": true, + "height": 20, + "width": 20, + "layer": "", + "locked": false, + "name": "PatrolPoint", + "persistentUuid": "5e7d5e20-da7c-5bb2-b364-8158e992ed92", + "x": 245, + "y": 710, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 20 + }, + { + "angle": 0, + "customSize": true, + "height": 20, + "width": 20, + "layer": "", + "locked": false, + "name": "PatrolPoint", + "persistentUuid": "18a486b0-149e-5c3e-b364-e1a9288b428b", + "x": 1140, + "y": 240, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 20 + }, + { + "angle": 0, + "customSize": true, + "height": 20, + "width": 20, + "layer": "", + "locked": false, + "name": "PatrolPoint", + "persistentUuid": "d467ebb4-8296-5cd0-a822-17e371823258", + "x": 1090, + "y": 640, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 20 + }, + { + "angle": 0, + "customSize": true, + "height": 20, + "width": 20, + "layer": "", + "locked": false, + "name": "PatrolPoint", + "persistentUuid": "5f1f4397-5031-55d8-8111-eff433152313", + "x": 790, + "y": 840, + "z": 320, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 20 + }, + { + "angle": 0, + "customSize": true, + "height": 50, + "width": 50, + "layer": "", + "locked": false, + "name": "Player", + "persistentUuid": "172ec3ef-00be-52cb-8ed8-d9b42301272a", + "x": 125, + "y": 575, + "z": 1, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [], + "depth": 107 + }, + { + "angle": 90, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Monster", + "persistentUuid": "e80bc693-d928-507e-84fe-39be0663094a", + "x": 340, + "y": 300, + "z": 321, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Lamp", + "persistentUuid": "da37a255-a60f-5a28-ab23-e289d4f62728", + "x": 55, + "y": 935, + "z": 100, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Lamp", + "persistentUuid": "2694b250-57d6-5fa1-9ced-b955917b2d3f", + "x": 60, + "y": 360, + "z": 400, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "FlickeringLamp", + "persistentUuid": "8891ef66-55a8-50c8-9344-195c4610267d", + "x": 600, + "y": 140, + "z": 260, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "FlickeringLamp", + "persistentUuid": "ac474268-be95-50a4-b1d1-92047a87f390", + "x": 1150, + "y": 650, + "z": 570, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Lamp", + "persistentUuid": "51a7c087-478f-5dd1-b1fd-ee192c248a18", + "x": 490, + "y": 1760, + "z": 250, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": false, + "height": 0, + "width": 0, + "layer": "", + "locked": false, + "name": "Lamp", + "persistentUuid": "a0d8ead6-6851-5b2d-bce0-be7b19a8adb9", + "x": 770, + "y": 1760, + "z": 250, + "zOrder": 1, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 80, + "width": 1000, + "layer": "UI", + "locked": false, + "name": "MessageText", + "persistentUuid": "f52c8071-f18a-5884-8f29-9d3f808908a2", + "x": 140, + "y": 520, + "z": 0, + "zOrder": 10, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + }, + { + "angle": 0, + "customSize": true, + "height": 40, + "width": 500, + "layer": "UI", + "locked": false, + "name": "ObjectiveText", + "persistentUuid": "c0ff9949-98c4-5974-9af8-bef5a48f09b5", + "x": 30, + "y": 30, + "z": 0, + "zOrder": 10, + "numberProperties": [], + "stringProperties": [], + "initialVariables": [] + } + ], + "objects": [ + { + "assetStoreId": "", + "name": "Ground", + "persistentUuid": "ca100636-aff6-40f7-893c-942f6dc28a40", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": false + } + ], + "content": { + "width": 100, + "height": 100, + "depth": 100, + "enableTextureTransparency": false, + "facesOrientation": "Z", + "frontFaceResourceName": "assets/HouseYard.png", + "backFaceResourceName": "", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "assets/HouseYard.png", + "rightFaceResourceName": "assets/HouseYard.png", + "topFaceResourceName": "assets/HouseYard.png", + "bottomFaceResourceName": "assets/HouseYard.png", + "frontFaceVisible": true, + "backFaceVisible": false, + "leftFaceVisible": true, + "rightFaceVisible": true, + "topFaceVisible": true, + "bottomFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceRepeat": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceRepeat": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceRepeat": true, + "materialType": "StandardWithoutMetalness", + "isCastingShadow": true, + "isReceivingShadow": true, + "tileScale": 0.4 + } + }, + { + "assetStoreId": "", + "name": "PushableBox", + "persistentUuid": "b546ebe2-0ded-40b3-b96b-a4c0b43b3b19", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Dynamic", + "bullet": false, + "fixedRotation": false, + "shape": "Box", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "density": 3, + "friction": 3, + "restitution": 0.1, + "linearDamping": 4, + "angularDamping": 5, + "gravityScale": 1, + "layers": 17, + "masks": 17, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "width": 43, + "height": 43, + "depth": 43, + "enableTextureTransparency": false, + "facesOrientation": "Y", + "frontFaceResourceName": "assets/HouseCrate.png", + "backFaceResourceName": "assets/HouseCrate.png", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "assets/HouseCrate.png", + "rightFaceResourceName": "assets/HouseCrate.png", + "topFaceResourceName": "assets/HouseCrate.png", + "bottomFaceResourceName": "assets/HouseCrate.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": "StandardWithoutMetalness", + "isCastingShadow": true, + "isReceivingShadow": true, + "tileScale": 0.2 + } + }, + { + "assetStoreId": "e71bd69f896d6c7531b48c65ceb5da25071d4fbdeb518aeceecba8d21f34ed8d", + "name": "MovementJoystick", + "persistentUuid": "4cc8677b-456f-4452-a420-3077e4f32707", + "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": "8b542da4-4a65-411b-9083-1612d2437a74", + "type": "Sprite", + "updateIfNotVisible": false, + "variables": [], + "effects": [], + "behaviors": [ + { + "name": "ButtonFSM", + "type": "ButtonStates::ButtonFSM", + "ShouldCheckHovering": true, + "State": "Idle", + "TouchId": 0, + "TouchIsInside": false, + "MouseIsInside": false, + "Index": 0 + }, + { + "name": "ButtonScaleTween", + "type": "ButtonStates::ButtonScaleTween", + "Scale": "Scale", + "ButtonFSM": "ButtonFSM", + "Tween": "Tween", + "IdleScale": 1.3, + "FocusedScale": 1.25, + "FadeInDuration": 0.02, + "FadeOutDuration": 0.05, + "PressedScale": 1.2, + "FadeInEasing": "easeOutQuad", + "FadeOutEasing": "easeInQuad", + "PreviousState": "Idle" + }, + { + "name": "MultitouchButton", + "type": "SpriteMultitouchJoystick::MultitouchButton", + "ControllerIdentifier": 1, + "ButtonIdentifier": "A", + "TouchId": 0, + "TouchIndex": 2.0247e-320, + "IsReleased": false + }, + { + "name": "Tween", + "type": "Tween::TweenBehavior" + } + ], + "animations": [ + { + "name": "", + "useMultipleDirections": false, + "directions": [ + { + "looping": true, + "timeBetweenFrames": 0.025, + "sprites": [ + { + "hasCustomCollisionMask": true, + "image": "Top arrow button.png", + "points": [], + "originPoint": { + "name": "origine", + "x": 40, + "y": 40 + }, + "centerPoint": { + "automatic": true, + "name": "centre", + "x": 0, + "y": 0 + }, + "customCollisionMask": [ + [ + { + "x": 0, + "y": 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", + "persistentUuid": "9ce2dba8-7d52-43df-ae32-76a18a84807e", + "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": true + }, + { + "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", + "persistentUuid": "de0c9d39-acf1-40fd-8c95-8aa18af25e6a", + "type": "SpriteToggleSwitch::SpriteToggleSwitch", + "variant": "Controls toggle", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "IsChecked": false + } + }, + { + "assetStoreId": "", + "name": "Monster", + "persistentUuid": "5092fc06-eb7b-44db-9fe1-094df33dc8be", + "type": "Scene3D::Model3DObject", + "variables": [ + { + "name": "State", + "type": "string", + "value": "Chasing" + } + ], + "effects": [], + "behaviors": [ + { + "name": "NavMeshCharacter", + "type": "NavMeshPathfinding::NavMeshCharacterBehavior", + "acceleration": 1500, + "maxSpeed": 80, + "angularMaxSpeed": 180, + "rotateObject": true, + "angleOffset": 0, + "radius": 0, + "avoidanceSightRange": 120 + }, + { + "isFolded": true, + "name": "Physics3D", + "type": "Physics3D::Physics3DBehavior", + "object3D": "Object3D", + "bodyType": "Kinematic", + "bullet": false, + "fixedRotation": true, + "shape": "Capsule", + "shapeOrientation": "Z", + "shapeDimensionA": 0, + "shapeDimensionB": 0, + "shapeDimensionC": 0, + "shapeOffsetX": 0, + "shapeOffsetY": 0, + "shapeOffsetZ": 0, + "massCenterOffsetX": 0, + "massCenterOffsetY": 0, + "massCenterOffsetZ": 0, + "massOverride": 0, + "density": 40, + "friction": 1, + "restitution": 0.1, + "linearDamping": 0.1, + "angularDamping": 0.1, + "gravityScale": 1, + "layers": 17, + "masks": 17 + } + ], + "content": { + "centerLocation": "ObjectCenter", + "crossfadeDuration": 0.10000000149011612, + "depth": 125, + "height": 125, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/starting_unit_character_orange.glb", + "originLocation": "BottomCenterZ", + "rotationX": 0, + "rotationY": 0, + "rotationZ": 0, + "width": 125, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "Flashlight", + "persistentUuid": "e3c7f6a8-9927-4b7d-a56a-2c6dd0136fa6", + "type": "Light3D::SpotLight3D", + "variant": "", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Intensity": 9, + "ConeLength": 4000, + "ConeAngle": 22, + "Decay": 1, + "Smoothing": 0.15, + "ShadowBias": 0.0001 + } + }, + { + "assetStoreId": "", + "name": "OuterWall", + "persistentUuid": "e404f98b-6bd9-523e-a222-e3a8b75bbc38", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "width": 100, + "height": 100, + "depth": 100, + "enableTextureTransparency": false, + "facesOrientation": "Z", + "frontFaceResourceName": "assets/HouseSiding.png", + "backFaceResourceName": "assets/HouseSiding.png", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "assets/HouseSiding.png", + "rightFaceResourceName": "assets/HouseSiding.png", + "topFaceResourceName": "assets/HouseSiding.png", + "bottomFaceResourceName": "assets/HouseSiding.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", + "isCastingShadow": true, + "isReceivingShadow": true, + "tileScale": 0.4 + } + }, + { + "assetStoreId": "", + "name": "Wall", + "persistentUuid": "0339efc4-5336-5f0b-8351-4fcfcb49ec49", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "width": 100, + "height": 100, + "depth": 100, + "enableTextureTransparency": false, + "facesOrientation": "Z", + "frontFaceResourceName": "assets/HouseWallpaper.png", + "backFaceResourceName": "assets/HouseWallpaper.png", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "assets/HouseWallpaper.png", + "rightFaceResourceName": "assets/HouseWallpaper.png", + "topFaceResourceName": "assets/HouseWallpaper.png", + "bottomFaceResourceName": "assets/HouseWallpaper.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", + "isCastingShadow": true, + "isReceivingShadow": true, + "tileScale": 0.4 + } + }, + { + "assetStoreId": "", + "name": "Floor", + "persistentUuid": "90a4dfcb-2be4-52c7-889d-8f7c4b0155c7", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": false + } + ], + "content": { + "width": 100, + "height": 100, + "depth": 100, + "enableTextureTransparency": false, + "facesOrientation": "Z", + "frontFaceResourceName": "assets/HouseFloor.png", + "backFaceResourceName": "assets/HouseCeiling.png", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "assets/HouseCeiling.png", + "rightFaceResourceName": "assets/HouseCeiling.png", + "topFaceResourceName": "assets/HouseCeiling.png", + "bottomFaceResourceName": "assets/HouseCeiling.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", + "isCastingShadow": true, + "isReceivingShadow": true, + "tileScale": 0.4 + } + }, + { + "assetStoreId": "", + "name": "Stairs", + "persistentUuid": "5b077964-68ab-53e2-b30a-05ffa25ebf9b", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": false + } + ], + "content": { + "width": 100, + "height": 100, + "depth": 100, + "enableTextureTransparency": false, + "facesOrientation": "Z", + "frontFaceResourceName": "assets/HouseFloor.png", + "backFaceResourceName": "", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "assets/HouseFloor.png", + "rightFaceResourceName": "assets/HouseFloor.png", + "topFaceResourceName": "assets/HouseFloor.png", + "bottomFaceResourceName": "assets/HouseFloor.png", + "frontFaceVisible": true, + "backFaceVisible": false, + "leftFaceVisible": true, + "rightFaceVisible": true, + "topFaceVisible": true, + "bottomFaceVisible": true, + "frontFaceResourceRepeat": true, + "backFaceResourceRepeat": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceRepeat": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceRepeat": true, + "materialType": "StandardWithoutMetalness", + "isCastingShadow": true, + "isReceivingShadow": true, + "tileScale": 0.4 + } + }, + { + "assetStoreId": "", + "name": "Roof", + "persistentUuid": "6189f5be-ad70-5833-b528-7ae815d177dc", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 100, + "height": 100, + "depth": 100, + "enableTextureTransparency": false, + "facesOrientation": "Z", + "frontFaceResourceName": "assets/HouseRoof.png", + "backFaceResourceName": "assets/HouseRoof.png", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "assets/HouseRoof.png", + "rightFaceResourceName": "assets/HouseRoof.png", + "topFaceResourceName": "assets/HouseRoof.png", + "bottomFaceResourceName": "assets/HouseRoof.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", + "isCastingShadow": true, + "isReceivingShadow": true, + "tileScale": 0.4 + } + }, + { + "assetStoreId": "", + "name": "Window", + "persistentUuid": "06099282-fa93-5305-b89e-222e2a755df3", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "width": 100, + "height": 100, + "depth": 100, + "enableTextureTransparency": true, + "facesOrientation": "Z", + "frontFaceResourceName": "assets/StarterGlass.png", + "backFaceResourceName": "assets/StarterGlass.png", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "assets/StarterGlass.png", + "rightFaceResourceName": "assets/StarterGlass.png", + "topFaceResourceName": "assets/StarterGlass.png", + "bottomFaceResourceName": "assets/StarterGlass.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", + "isCastingShadow": false, + "isReceivingShadow": true, + "tileScale": 0.4 + } + }, + { + "assetStoreId": "", + "name": "Door", + "persistentUuid": "3a6aafac-b970-57fa-aaff-4cc7852d5df8", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "width": 100, + "height": 100, + "depth": 100, + "enableTextureTransparency": false, + "facesOrientation": "Z", + "frontFaceResourceName": "assets/HouseFloor.png", + "backFaceResourceName": "assets/HouseFloor.png", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "assets/HouseFloor.png", + "rightFaceResourceName": "assets/HouseFloor.png", + "topFaceResourceName": "assets/HouseDoor.png", + "bottomFaceResourceName": "assets/HouseDoor.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": "StandardWithoutMetalness", + "isCastingShadow": true, + "isReceivingShadow": true, + "tileScale": 0.4 + } + }, + { + "assetStoreId": "", + "name": "PatrolPoint", + "persistentUuid": "57c5522c-2088-5916-b018-49cfa6e8c2aa", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 100, + "height": 100, + "depth": 100, + "enableTextureTransparency": false, + "facesOrientation": "Z", + "frontFaceResourceName": "assets/StarterGlass.png", + "backFaceResourceName": "assets/StarterGlass.png", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "assets/StarterGlass.png", + "rightFaceResourceName": "assets/StarterGlass.png", + "topFaceResourceName": "assets/StarterGlass.png", + "bottomFaceResourceName": "assets/StarterGlass.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", + "isCastingShadow": false, + "isReceivingShadow": true, + "tileScale": 0.4 + } + }, + { + "assetStoreId": "", + "name": "Boundary", + "persistentUuid": "fd536cd3-e980-5061-b731-e26face0c5e4", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "width": 100, + "height": 100, + "depth": 100, + "enableTextureTransparency": false, + "facesOrientation": "Z", + "frontFaceResourceName": "", + "backFaceResourceName": "", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "", + "rightFaceResourceName": "", + "topFaceResourceName": "", + "bottomFaceResourceName": "", + "frontFaceVisible": false, + "backFaceVisible": false, + "leftFaceVisible": false, + "rightFaceVisible": false, + "topFaceVisible": false, + "bottomFaceVisible": false, + "frontFaceResourceRepeat": true, + "backFaceResourceRepeat": true, + "leftFaceResourceRepeat": true, + "rightFaceResourceRepeat": true, + "topFaceResourceRepeat": true, + "bottomFaceResourceRepeat": true, + "materialType": "StandardWithoutMetalness", + "isCastingShadow": true, + "isReceivingShadow": true, + "tileScale": 0.4 + } + }, + { + "assetStoreId": "", + "name": "ExitZone", + "persistentUuid": "45618e86-5630-5845-b020-b7e41b7dc19b", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 100, + "height": 100, + "depth": 100, + "enableTextureTransparency": false, + "facesOrientation": "Z", + "frontFaceResourceName": "assets/StarterGlass.png", + "backFaceResourceName": "assets/StarterGlass.png", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "assets/StarterGlass.png", + "rightFaceResourceName": "assets/StarterGlass.png", + "topFaceResourceName": "assets/StarterGlass.png", + "bottomFaceResourceName": "assets/StarterGlass.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", + "isCastingShadow": false, + "isReceivingShadow": true, + "tileScale": 0.4 + } + }, + { + "assetStoreId": "a1dc2bb09a39f6f7344c662d917edfa597264f69e6a8722153deeb6aa9fdba73", + "name": "Fence", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 55.9, + "height": 8.4, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Fence.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 300, + "animations": [] + } + }, + { + "assetStoreId": "566fdca12149a269bc82374d43392b8f826fd1ff45708794aa7db73bc8916f62", + "name": "Lightpost", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 260, + "height": 26, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Street Lamp 01.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 47.3, + "animations": [] + } + }, + { + "assetStoreId": "0a9e3dc369a0ad31cc7eb24c7fded0ab3dc10ec968d97b251ab7e250aac35c37", + "name": "DeadTree", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 520, + "height": 244.6, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Common Tree Dead 1.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": -90, + "width": 496.4, + "animations": [] + } + }, + { + "assetStoreId": "1f56a36a894ec4d54b1182950618abd57834f57b165cf0398242b6c7c9e6e427", + "name": "DeadWillow", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 480, + "height": 89, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Willow Dead 1.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": -90, + "width": 323.6, + "animations": [] + } + }, + { + "assetStoreId": "556088e204432fcb25228a656fd25c81bd04d3b5c58c19035a8292bd62ac9c09", + "name": "Rock", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 90, + "height": 46, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Rock Moss 1.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": -90, + "width": 44.2, + "animations": [] + } + }, + { + "assetStoreId": "aa047601f0710138bacf8288cb7f0325bc736d4d68fada535327bc46eda31ffb", + "name": "SmallRock", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 31.3, + "height": 29.7, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Rock Moss 4.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": -90, + "width": 50, + "animations": [] + } + }, + { + "assetStoreId": "23d5330ae865486b82f004832c11f535d74617ad0a37eb56c921f3fdb67006dd", + "name": "Sofa", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 65.5, + "height": 54.2, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Sofa 03.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 160, + "animations": [] + } + }, + { + "assetStoreId": "e457d877028b6e22b92edd3430fdffc98b24bbf7eb9ee054071695e943d2d0fa", + "name": "TV", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 41.9, + "height": 43.2, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Television 01.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 55, + "animations": [] + } + }, + { + "assetStoreId": "f6f1543e862af6074928c5442e533ca2f6a6eb2eb8fe93e2aea4a73d643d955d", + "name": "Commode", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 110, + "height": 52.7, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Gothic Commode 01.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 109, + "animations": [] + } + }, + { + "assetStoreId": "96f46e226bb18efb2ed062df52f9ff6be6bf0ea0d770e6ae6facfbbcaad6c8bc", + "name": "Nightstand", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 50, + "height": 30.3, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Classic Nightstand 01.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 40.6, + "animations": [] + } + }, + { + "assetStoreId": "500d415969d9652fdbdde3e3d0b3b433aa1b0be549925d4c939332316d9f1cb5", + "name": "OilLamp", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 40, + "height": 11, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Vintage Oil Lamp.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 11, + "animations": [] + } + }, + { + "assetStoreId": "19556387b6456dfc07088e5814aa68384052386e78335fd8d9670bb9ca1f1c2b", + "name": "Bookshelf", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 150, + "height": 42.3, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Wooden Bookshelf Worn.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 99.9, + "animations": [] + } + }, + { + "assetStoreId": "2665aeb168c42d3989ce55e14bce3bf2a4bd172d2d5350c514ae5536bb275e10", + "name": "RockingChair", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 85, + "height": 71.2, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Rockingchair 01.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 60.5, + "animations": [] + } + }, + { + "assetStoreId": "f5d09a24caa87fd1053e7fd1338fd7be4beff1826f5d34952e0b57ca059a32bc", + "name": "KitchenCabinet", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 74.2, + "height": 38.7, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Painted Wooden Cabinet.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 75, + "animations": [] + } + }, + { + "assetStoreId": "db860efd1cf6b42d6e35c818efd50760a0199019c90da4b784477a230a08f159", + "name": "Stove", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 70, + "height": 52.8, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Electric Stove.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 41, + "animations": [] + } + }, + { + "assetStoreId": "b9f12b029aff220d8782e368cb4c1c2147121abde6e29bb664afc86361823fb3", + "name": "Cupboard", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 150, + "height": 42.6, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Painted Wooden Cabinet 02.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 58.2, + "animations": [] + } + }, + { + "assetStoreId": "87a675ae0a7e11ca7fac5d51bae0b68e490df1a9cd0e646fccdbddf0eea49e4d", + "name": "Table", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 45.7, + "height": 54.8, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Wooden Table 01.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 150, + "animations": [] + } + }, + { + "assetStoreId": "0d5818577415eb744e269faa04cf4b1b9bb602683e13e38851d80004ad050e94", + "name": "Chair", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 75, + "height": 39.3, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Painted Wooden Chair 02.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 37.9, + "animations": [] + } + }, + { + "assetStoreId": "10115dcfc395499e1a0acba9e71d987e72d15c9fd03377c38d9a63171b85ce6b", + "name": "Clock", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 160, + "height": 31.2, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Vintage Grandfather Clock 01.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 45.7, + "animations": [] + } + }, + { + "assetStoreId": "4d7dc704ef98f3fa713acbe7b52282eb01331394ad8d0e008746996ff63a77d1", + "name": "Desk", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 58.2, + "height": 48, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Small Wooden Table 01.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 100, + "animations": [] + } + }, + { + "assetStoreId": "15749b6e99c14440ff5cf09b81e51ce6340fbcef92bae20d617db43ef3b2346e", + "name": "Rack", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 140, + "height": 44.2, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Worn Metal Rack.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 67.5, + "animations": [] + } + }, + { + "assetStoreId": "1636ee022c1d24309442b64becbd820c585d5caf4d8a4d11a48047c19352d64c", + "name": "Bed", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 85.7, + "height": 65, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Vintage Day Bed.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 150, + "animations": [] + } + }, + { + "assetStoreId": "5bcadbdbee3ab5c5640eeea71b1674ee3ddc022710a20a668d3dfceb9ad3ac5a", + "name": "DoubleBed", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 127.8, + "height": 170, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Gothic Bed 01.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 124.5, + "animations": [] + } + }, + { + "assetStoreId": "71c09213ba9ebe1a72d206e95ec411771bf4ffe904e0cc8f2d34a8e05335c732", + "name": "Mirror", + "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 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": true + } + ], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 130, + "height": 4.5, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Ornate Mirror 01.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 0, + "width": 85, + "animations": [] + } + }, + { + "assetStoreId": "e4e50efbefc794278fc8a62f4244fc0c6f11a3fc9c8416cd95af129006d3d0aa", + "name": "Key", + "type": "Scene3D::Model3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "centerLocation": "BottomCenterZ", + "crossfadeDuration": 0, + "depth": 20.3, + "height": 40, + "isCastingShadow": true, + "isReceivingShadow": true, + "keepAspectRatio": true, + "materialType": "StandardWithoutMetalness", + "modelResourceName": "assets/Key.glb", + "originLocation": "BottomCenterZ", + "rotationX": 90, + "rotationY": 0, + "rotationZ": 90, + "width": 6.6, + "animations": [] + } + }, + { + "assetStoreId": "", + "name": "Lamp", + "type": "Light3D::PointLight3D", + "variant": "", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Color": "255;190;120", + "Intensity": 2.5, + "Decay": 2, + "Range": 480, + "IsCastingShadow": false + } + }, + { + "assetStoreId": "", + "name": "FlickeringLamp", + "type": "Light3D::PointLight3D", + "variant": "", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "Color": "180;200;255", + "Intensity": 1.5, + "Decay": 2, + "Range": 380, + "IsCastingShadow": false + } + }, + { + "assetStoreId": "", + "bold": false, + "italic": false, + "name": "MessageText", + "persistentUuid": "037d2a7f-c229-5cfc-8634-b0fc36604f69", + "smoothed": true, + "type": "TextObject::Text", + "underlined": false, + "variables": [], + "effects": [], + "behaviors": [], + "string": "Find the key to get out of the house.", + "font": "assets/NotoSansArabicUI-Black.ttf", + "textAlignment": "center", + "characterSize": 36, + "color": { + "b": 255, + "g": 255, + "r": 255 + }, + "content": { + "bold": false, + "isOutlineEnabled": true, + "isShadowEnabled": true, + "italic": false, + "outlineColor": "0;0;0", + "outlineThickness": 2, + "shadowAngle": 90, + "shadowBlurRadius": 2, + "shadowColor": "0;0;0", + "shadowDistance": 4, + "shadowOpacity": 127, + "smoothed": true, + "underlined": false, + "text": "Find the key to get out of the house.", + "font": "assets/NotoSansArabicUI-Black.ttf", + "textAlignment": "center", + "verticalTextAlignment": "center", + "characterSize": 36, + "lineHeight": 0, + "color": "255;255;255" + } + }, + { + "assetStoreId": "", + "bold": false, + "italic": false, + "name": "ObjectiveText", + "persistentUuid": "6acace1c-674d-555b-b048-4170fe4075d3", + "smoothed": true, + "type": "TextObject::Text", + "underlined": false, + "variables": [], + "effects": [], + "behaviors": [], + "string": "Find the key", + "font": "assets/NotoSansArabicUI-Black.ttf", + "textAlignment": "left", + "characterSize": 26, + "color": { + "b": 255, + "g": 255, + "r": 255 + }, + "content": { + "bold": false, + "isOutlineEnabled": true, + "isShadowEnabled": true, + "italic": false, + "outlineColor": "0;0;0", + "outlineThickness": 2, + "shadowAngle": 90, + "shadowBlurRadius": 2, + "shadowColor": "0;0;0", + "shadowDistance": 4, + "shadowOpacity": 127, + "smoothed": true, + "underlined": false, + "text": "Find the key", + "font": "assets/NotoSansArabicUI-Black.ttf", + "textAlignment": "left", + "verticalTextAlignment": "center", + "characterSize": 26, + "lineHeight": 0, + "color": "255;255;255" + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "ControlsToggle" + }, + { + "folderName": "Mobile Controls", + "children": [ + { + "objectName": "MovementJoystick" + }, + { + "objectName": "JumpButton" + } + ] + }, + { + "folderName": "House", + "children": [ + { + "objectName": "OuterWall" + }, + { + "objectName": "Wall" + }, + { + "objectName": "Floor" + }, + { + "objectName": "Stairs" + }, + { + "objectName": "Roof" + }, + { + "objectName": "Window" + }, + { + "objectName": "Door" + }, + { + "objectName": "Ground" + }, + { + "objectName": "PushableBox" + } + ] + }, + { + "folderName": "Markers", + "children": [ + { + "objectName": "PatrolPoint" + }, + { + "objectName": "Boundary" + }, + { + "objectName": "ExitZone" + } + ] + }, + { + "folderName": "Yard", + "children": [ + { + "objectName": "Fence" + }, + { + "objectName": "Lightpost" + }, + { + "objectName": "DeadTree" + }, + { + "objectName": "DeadWillow" + }, + { + "objectName": "Rock" + }, + { + "objectName": "SmallRock" + } + ] + }, + { + "folderName": "Furniture", + "children": [ + { + "objectName": "Sofa" + }, + { + "objectName": "TV" + }, + { + "objectName": "Commode" + }, + { + "objectName": "Nightstand" + }, + { + "objectName": "OilLamp" + }, + { + "objectName": "Bookshelf" + }, + { + "objectName": "RockingChair" + }, + { + "objectName": "KitchenCabinet" + }, + { + "objectName": "Stove" + }, + { + "objectName": "Cupboard" + }, + { + "objectName": "Table" + }, + { + "objectName": "Chair" + }, + { + "objectName": "Clock" + }, + { + "objectName": "Desk" + }, + { + "objectName": "Rack" + }, + { + "objectName": "Bed" + }, + { + "objectName": "DoubleBed" + }, + { + "objectName": "Mirror" + } + ] + }, + { + "folderName": "Gameplay", + "children": [ + { + "objectName": "Key" + } + ] + }, + { + "folderName": "Lights", + "children": [ + { + "objectName": "Lamp" + }, + { + "objectName": "FlickeringLamp" + } + ] + }, + { + "folderName": "Interface", + "children": [ + { + "objectName": "MessageText" + }, + { + "objectName": "ObjectiveText" + } + ] + }, + { + "folderName": "Characters", + "children": [ + { + "objectName": "Player" + }, + { + "objectName": "Monster" + }, + { + "objectName": "Flashlight" + } + ] + } + ] + }, + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Hide" + }, + "parameters": [ + "Player" + ] + }, + { + "type": { + "value": "EnableLayerEffect" + }, + "parameters": [ + "", + "", + "\"Fog\"", + "yes" + ] + }, + { + "type": { + "value": "EnableLayerEffect" + }, + "parameters": [ + "", + "", + "\"FullLight\"", + "no" + ] + }, + { + "type": { + "value": "Hide" + }, + "parameters": [ + "PatrolPoint" + ] + }, + { + "type": { + "value": "Hide" + }, + "parameters": [ + "ExitZone" + ] + }, + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"Message\"" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Hide" + }, + "parameters": [ + "MovementJoystick" + ] + }, + { + "type": { + "value": "Hide" + }, + "parameters": [ + "JumpButton" + ] + }, + { + "type": { + "value": "SpriteMultitouchJoystick::SpriteMultitouchJoystick::ActivateControl" + }, + "parameters": [ + "MovementJoystick", + "", + "" + ] + } + ] + } + ] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Monster", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Patrolling logic" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringObjectVariable" + }, + "parameters": [ + "Monster", + "State", + "=", + "\"Chasing\"" + ] + }, + { + "type": { + "inverted": true, + "value": "Distance" + }, + "parameters": [ + "Player", + "Monster", + "600", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringObjectVariable" + }, + "parameters": [ + "Monster", + "State", + "=", + "\"Patrolling\"" + ] + }, + { + "type": { + "value": "ResetObjectTimer" + }, + "parameters": [ + "Monster", + "\"PositionChange\"" + ] + }, + { + "type": { + "value": "NavMeshPathfinding::NavMeshCharacterBehavior::SetMaxSpeed" + }, + "parameters": [ + "Monster", + "NavMeshCharacter", + "=", + "80" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringObjectVariable" + }, + "parameters": [ + "Monster", + "State", + "=", + "\"Patrolling\"" + ] + }, + { + "type": { + "value": "CompareObjectTimer" + }, + "parameters": [ + "Monster", + "\"PositionChange\"", + ">=", + "5" + ] + } + ], + "actions": [ + { + "type": { + "value": "NavMeshPathfinding::MoveTo" + }, + "parameters": [ + "Monster", + "NavMeshCharacter", + "Player.CenterX()", + "Player.CenterY()", + "Player.Object3D::Z()" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "NavMeshPathfinding::PathFound" + }, + "parameters": [ + "Monster", + "NavMeshCharacter" + ] + }, + { + "type": { + "value": "PickRandomInstance" + }, + "parameters": [ + "", + "PatrolPoint" + ] + } + ], + "actions": [ + { + "type": { + "value": "NavMeshPathfinding::MoveTo" + }, + "parameters": [ + "Monster", + "NavMeshCharacter", + "PatrolPoint.CenterX()", + "PatrolPoint.CenterY()", + "PatrolPoint.Object3D::Z()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NavMeshPathfinding::PathFound" + }, + "parameters": [ + "Monster", + "NavMeshCharacter" + ] + } + ], + "actions": [ + { + "type": { + "value": "ResetObjectTimer" + }, + "parameters": [ + "Monster", + "\"PositionChange\"" + ] + } + ] + } + ] + }, + { + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Chasing logic" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringObjectVariable" + }, + "parameters": [ + "Monster", + "State", + "=", + "\"Patrolling\"" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "Distance" + }, + "parameters": [ + "Player", + "Monster", + "300", + "" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::And" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "Raycaster3D::RaycastWithAngle" + }, + "parameters": [ + "", + "MonsterVisionObstacles", + "Monster.CenterX()", + "Monster.CenterY()", + "Monster.Object3D::CenterZ()", + "Monster.Angle()", + "ToDeg(atan((Player.Object3D::CenterZ()-Monster.Object3D::CenterZ())/Monster.Distance(Player)))", + "600", + "" + ] + }, + { + "type": { + "value": "PickedInstancesCount" + }, + "parameters": [ + "Player", + ">", + "0" + ] + } + ] + } + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringObjectVariable" + }, + "parameters": [ + "Monster", + "State", + "=", + "\"Chasing\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringObjectVariable" + }, + "parameters": [ + "Monster", + "State", + "=", + "\"Chasing\"" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [ + { + "type": { + "value": "NavMeshPathfinding::NavMeshCharacterBehavior::SetMaxSpeed" + }, + "parameters": [ + "Monster", + "NavMeshCharacter", + "=", + "190" + ] + }, + { + "type": { + "value": "NavMeshPathfinding::MoveTo" + }, + "parameters": [ + "Monster", + "NavMeshCharacter", + "Player.CenterX()", + "Player.CenterY()", + "Player.Object3D::Z()" + ] + }, + { + "type": { + "value": "ResetObjectTimer" + }, + "parameters": [ + "Monster", + "\"Repath\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CompareObjectTimer" + }, + "parameters": [ + "Monster", + "\"Repath\"", + ">=", + "0.5" + ] + } + ], + "actions": [ + { + "type": { + "value": "NavMeshPathfinding::NavMeshCharacterBehavior::SetMaxSpeed" + }, + "parameters": [ + "Monster", + "NavMeshCharacter", + "=", + "190" + ] + }, + { + "type": { + "value": "NavMeshPathfinding::MoveTo" + }, + "parameters": [ + "Monster", + "NavMeshCharacter", + "Player.CenterX()", + "Player.CenterY()", + "Player.Object3D::Z()" + ] + }, + { + "type": { + "value": "ResetObjectTimer" + }, + "parameters": [ + "Monster", + "\"Repath\"" + ] + } + ] + } + ] + }, + { + "folded": false, + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Player dies" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Physics3D::CollisionStarted" + }, + "parameters": [ + "Player", + "Physics3D", + "Monster", + "Physics3D", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Scene" + }, + "parameters": [ + "", + "CurrentSceneName()", + "" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Controls toggle", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Start with touch controls on touchscreens." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "SystemInfo::HasTouchScreen" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::SetChecked" + }, + "parameters": [ + "ControlsToggle", + "yes", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::HasJustBeenToggled" + }, + "parameters": [ + "ControlsToggle", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Set to Touch" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::IsChecked" + }, + "parameters": [ + "ControlsToggle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "ControlKind", + "=", + "\"Touch\"" + ] + }, + { + "type": { + "value": "Show" + }, + "parameters": [ + "JumpButton", + "" + ] + }, + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "JumpButton", + "MultitouchButton", + "yes" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Set to Keyboard" + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteToggleSwitch::SpriteToggleSwitch::IsChecked" + }, + "parameters": [ + "ControlsToggle", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "ControlKind", + "=", + "\"Keyboard\"" + ] + }, + { + "type": { + "value": "Hide" + }, + "parameters": [ + "JumpButton" + ] + }, + { + "type": { + "value": "ActivateBehavior" + }, + "parameters": [ + "JumpButton", + "MultitouchButton", + "" + ] + } + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Mobile controls", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "StringVariable" + }, + "parameters": [ + "ControlKind", + "=", + "\"Touch\"" + ] + }, + { + "type": { + "inverted": true, + "value": "IsCursorOnObject" + }, + "parameters": [ + "ControlsToggle", + "", + "", + "" + ] + } + ], + "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": "HasAnyTouchOrMouseStarted" + }, + "parameters": [ + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::HasTouchStartedOnScreenSide" + }, + "parameters": [ + "", + "MovementJoystick", + "\"Left\"", + "" + ] + } + ], + "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": [ + "", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "MousePointerLock::RequestPointerLock" + }, + "parameters": [ + "", + "" + ] + } + ] + } + ], + "parameters": [] + }, + { + "colorB": 228, + "colorG": 176, + "colorR": 74, + "creationTime": 0, + "folded": true, + "name": "Flashlight", + "source": "", + "type": "BuiltinCommonInstructions::Group", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetXY" + }, + "parameters": [ + "Flashlight", + "=", + "Player.XFromAngleAndDistance(Player.Angle()-90, -20)", + "=", + "Player.CenterY()" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetZ" + }, + "parameters": [ + "Flashlight", + "Object3D", + "=", + "Player.Object3D::CenterZ()+38" + ] + }, + { + "type": { + "value": "Scene3D::Base3DBehavior::SetRotationY" + }, + "parameters": [ + "Flashlight", + "Object3D", + "=", + "90 - Scene3D::CameraRotationX()" + ] + }, + { + "type": { + "value": "SetAngle" + }, + "parameters": [ + "Flashlight", + "=", + "Player.Angle()" + ] + } + ] + } + ], + "parameters": [] + }, + { + "type": "BuiltinCommonInstructions::Group", + "folded": true, + "name": "Key and door", + "source": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "The key turns on itself until the player picks it up (close to it, on the same floor)." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetAngle" + }, + "parameters": [ + "Key", + "+", + "90 * TimeDelta()" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Distance" + }, + "parameters": [ + "Player", + "Key", + "120", + "" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "abs(Player.Object3D::Z() - Key.Object3D::Z())", + "<", + "100" + ] + } + ], + "actions": [ + { + "type": { + "value": "Delete" + }, + "parameters": [ + "Key", + "" + ] + }, + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "HasKey", + "True", + "" + ] + }, + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "ObjectiveText", + "Text", + "=", + "\"Open the front door\"" + ] + }, + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "MessageText", + "Text", + "=", + "\"You found the key! Run to the front door.\"" + ] + }, + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"Message\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "The front door opens with the key. Without it, it stays locked." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "Distance" + }, + "parameters": [ + "Player", + "Door", + "130", + "" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "abs(Player.Object3D::Z() - Door.Object3D::Z())", + "<", + "100" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "HasKey", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Delete" + }, + "parameters": [ + "Door", + "" + ] + }, + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "ObjectiveText", + "Text", + "=", + "\"Escape through the gate\"" + ] + }, + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "MessageText", + "Text", + "=", + "\"The door is open. Get out!\"" + ] + }, + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"Message\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "HasKey", + "False", + "" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::Once" + }, + "parameters": [] + } + ], + "actions": [ + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "MessageText", + "Text", + "=", + "\"The door is locked. The key must be somewhere in the house.\"" + ] + }, + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"Message\"" + ] + } + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CompareTimer" + }, + "parameters": [ + "", + "\"Message\"", + ">=", + "4" + ] + } + ], + "actions": [ + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "MessageText", + "Text", + "=", + "\"\"" + ] + } + ] + } + ], + "parameters": [], + "colorB": 196, + "colorG": 196, + "colorR": 196, + "creationTime": 0 + }, + { + "type": "BuiltinCommonInstructions::Group", + "folded": true, + "name": "Escape", + "source": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Reaching the gate of the yard: the player escaped, and the game starts again." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CollisionNP" + }, + "parameters": [ + "Player", + "ExitZone", + "", + "", + "" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Escaped", + "False", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "Escaped", + "True", + "" + ] + }, + { + "type": { + "value": "SetStringObjectVariable" + }, + "parameters": [ + "Monster", + "State", + "=", + "\"Gone\"" + ] + }, + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "ObjectiveText", + "Text", + "=", + "\"\"" + ] + }, + { + "type": { + "value": "TextContainerCapability::TextContainerBehavior::SetValue" + }, + "parameters": [ + "MessageText", + "Text", + "=", + "\"You escaped!\"" + ] + }, + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"Escape\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Escaped", + "True", + "" + ] + }, + { + "type": { + "value": "CompareTimer" + }, + "parameters": [ + "", + "\"Escape\"", + ">=", + "3" + ] + } + ], + "actions": [ + { + "type": { + "value": "Scene" + }, + "parameters": [ + "", + "CurrentSceneName()", + "" + ] + } + ] + } + ], + "parameters": [], + "colorB": 196, + "colorG": 196, + "colorR": 196, + "creationTime": 0 + }, + { + "type": "BuiltinCommonInstructions::Group", + "folded": true, + "name": "Flickering lights", + "source": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Some lamps flicker." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "RandomFloat(1)", + "<", + "0.08" + ] + } + ], + "actions": [ + { + "type": { + "value": "Light3D::PointLight3D::SetIntensity" + }, + "parameters": [ + "FlickeringLamp", + "=", + "RandomFloatInRange(0.2, 1.8)", + "" + ] + } + ] + } + ], + "parameters": [], + "colorB": 196, + "colorG": 196, + "colorR": 196, + "creationTime": 0 + } + ], + "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": [ + { + "disabled": true, + "effectType": "Scene3D::ExponentialFog", + "name": "Fog", + "doubleParameters": { + "density": 0.001 + }, + "stringParameters": { + "color": "0;0;0" + }, + "booleanParameters": {} + }, + { + "effectType": "Scene3D::HemisphereLight", + "name": "FullLight", + "doubleParameters": { + "elevation": 45, + "intensity": 0.5, + "rotation": 0 + }, + "stringParameters": { + "groundColor": "145;145;145", + "skyColor": "255;255;255", + "top": "Z+" + }, + "booleanParameters": {} + }, + { + "effectType": "Scene3D::Skybox", + "name": "SkyBox", + "doubleParameters": {}, + "stringParameters": { + "backFaceResourceName": "assets/DarkSkybox_Back.png", + "bottomFaceResourceName": "assets/DarkSkybox_Bottom.png", + "frontFaceResourceName": "assets/DarkSkybox_Front.png", + "leftFaceResourceName": "assets/DarkSkybox_Left.png", + "rightFaceResourceName": "assets/DarkSkybox_Right.png", + "top": "Z+", + "topFaceResourceName": "assets/DarkSkybox_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": "UI", + "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": "MobileControls", + "renderingType": "", + "visibility": true, + "cameras": [], + "effects": [] + } + ], + "behaviorsSharedData": [ + { + "name": "Animation", + "type": "AnimatableCapability::AnimatableBehavior" + }, + { + "name": "ButtonFSM", + "type": "ButtonStates::ButtonFSM" + }, + { + "name": "ButtonScaleTween", + "type": "ButtonStates::ButtonScaleTween" + }, + { + "name": "Effect", + "type": "EffectCapability::EffectBehavior" + }, + { + "name": "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": "Shooter3DKeyboardMapper", + "type": "PhysicsCharacter3DKeyMapper::Shooter3DKeyboardMapper" + }, + { + "name": "Shooter3DMultitouchMapper", + "type": "SpriteMultitouchJoystick::Shooter3DMultitouchMapper" + }, + { + "name": "Tween", + "type": "Tween::TweenBehavior" + }, + { + "name": "NavMeshCharacter", + "type": "NavMeshPathfinding::NavMeshCharacterBehavior", + "cellSize": 5, + "cellDepth": 2, + "slopeMaxAngle": 40, + "stairHeightMax": 20, + "walkableRadius": -1, + "speedScaleY": 1, + "walkableDepth": 150 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior" + } + ] + } + ], + "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.", + "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.setTimeoutMs(120000);", + "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.'", + ");" + ] + }, + { + "name": "Picking up the key", + "description": "Puts the player next to the key: the key is picked up and the player has it.", + "type": "gameplay", + "source": [ + "await harness.goToScene('Game Scene');", + "// Faster in the software renderer used to run tests.", + "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.setTimeoutMs(120000);", + "const DT = { dtMs: 50 };", + "const getPlayer = () => harness.getObjects('Player')[0];", + "const scene = harness.getCurrentRuntimeScene();", + "const variable = (name) => scene.getVariables().get(name).getAsBoolean();", + "// The monster stays away: it is moved to a far corner of the yard and does not patrol.", + "const monster = harness.getObjects('Monster')[0];", + "const parkMonster = () => { harness.setObjectPosition(monster.id, 1800, -300, 1); harness.getRuntimeObject(monster.id).getVariables().get('State').setString('Gone'); };", + "parkMonster();", + "const key = harness.getObjects('Key')[0];", + "harness.assert(!!key, 'There is a key in the house.');", + "harness.assert(!variable(\"HasKey\"), \"The player does not have the key at the start.\");", + "// The position of the player is its top-left corner: its center is 105 px from the key.", + "harness.setObjectPosition(getPlayer().id, key.centerX + 105 - 25, key.centerY - 5 - 25, 321);", + "await harness.stepUntil(() => variable(\"HasKey\"), { maxFrames: 60 });", + "harness.assert(variable(\"HasKey\"), \"The player picked up the key.\");", + "harness.assert(harness.getObjects('Key').length === 0, 'The key is not in the house anymore.');" + ] + }, + { + "name": "The front door opens only with the key", + "description": "Walks the player into the front door: it stays closed without the key, and opens with it.", + "type": "gameplay", + "source": [ + "await harness.goToScene('Game Scene');", + "// Faster in the software renderer used to run tests.", + "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.setTimeoutMs(120000);", + "const DT = { dtMs: 50 };", + "const getPlayer = () => harness.getObjects('Player')[0];", + "const scene = harness.getCurrentRuntimeScene();", + "const variable = (name) => scene.getVariables().get(name).getAsBoolean();", + "// The monster stays away: it is moved to a far corner of the yard and does not patrol.", + "const monster = harness.getObjects('Monster')[0];", + "const parkMonster = () => { harness.setObjectPosition(monster.id, 1800, -300, 1); harness.getRuntimeObject(monster.id).getVariables().get('State').setString('Gone'); };", + "parkMonster();", + "const door = harness.getObjects('Door')[0];", + "// The player stands in the hall and walks to the door.", + "harness.setObjectPosition(getPlayer().id, door.centerX - 25, door.centerY - 160, 1);", + "harness.getRuntimeObject(getPlayer().id).setAngle(90);", + "await harness.stepFrames(2, DT);", + "const startY = getPlayer().centerY;", + "harness.setKeyPressed('w', true);", + "await harness.stepFrames(30, DT);", + "harness.setKeyPressed('w', false);", + "harness.assert(harness.getObjects('Door').length === 1, 'The door stays closed without the key.');", + "harness.assert(getPlayer().centerY > startY + 80, 'The player walked to the door.');", + "harness.assert(getPlayer().centerY < door.centerY, 'The closed door stops the player (player at ' + Math.round(getPlayer().centerY) + ', door at ' + Math.round(door.centerY) + ').');", + "scene.getVariables().get('HasKey').setBoolean(true);", + "harness.setKeyPressed('w', true);", + "const opened = await harness.stepUntil(() => getPlayer().centerY > door.centerY + 60, { maxFrames: 60 });", + "harness.setKeyPressed('w', false);", + "harness.assert(harness.getObjects('Door').length === 0, 'The door opened with the key.');", + "harness.assert(opened, 'The player walked out through the door.');" + ] + }, + { + "name": "Reaching the gate of the yard escapes the house", + "description": "Puts the player in front of the open gate and walks it through: the player escaped, and the game starts again.", + "type": "gameplay", + "source": [ + "await harness.goToScene('Game Scene');", + "// Faster in the software renderer used to run tests.", + "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.setTimeoutMs(120000);", + "const DT = { dtMs: 50 };", + "const getPlayer = () => harness.getObjects('Player')[0];", + "const scene = harness.getCurrentRuntimeScene();", + "const variable = (name) => scene.getVariables().get(name).getAsBoolean();", + "// The monster stays away: it is moved to a far corner of the yard and does not patrol.", + "const monster = harness.getObjects('Monster')[0];", + "const parkMonster = () => { harness.setObjectPosition(monster.id, 1800, -300, 1); harness.getRuntimeObject(monster.id).getVariables().get('State').setString('Gone'); };", + "parkMonster();", + "const exit = harness.getObjects('ExitZone')[0];", + "harness.setObjectPosition(getPlayer().id, exit.centerX - 25, exit.centerY - 160, 1);", + "harness.getRuntimeObject(getPlayer().id).setAngle(90);", + "harness.setKeyPressed('w', true);", + "const escaped = await harness.stepUntil(() => variable(\"Escaped\"), { maxFrames: 90 });", + "harness.setKeyPressed('w', false);", + "harness.assert(escaped, 'The player escaped through the gate.');", + "harness.assert(harness.getObjects('MessageText')[0] && harness.getRuntimeObject(harness.getObjects('MessageText')[0].id).getText() === 'You escaped!', 'The game says the player escaped.');", + "const restarted = await harness.stepUntil(() => harness.getCurrentRuntimeScene() !== scene, { maxFrames: 240 });", + "harness.assert(restarted, 'The game starts again after the escape.');" + ] + }, + { + "name": "The monster chases and catches the player", + "description": "Puts the monster in sight of the player in the hall: it runs to the player and catching them restarts the scene.", + "type": "gameplay", + "source": [ + "await harness.goToScene('Game Scene');", + "// Faster in the software renderer used to run tests.", + "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.setTimeoutMs(120000);", + "const DT = { dtMs: 50 };", + "const getPlayer = () => harness.getObjects('Player')[0];", + "const scene = harness.getCurrentRuntimeScene();", + "const monster = harness.getObjects('Monster')[0];", + "harness.setObjectPosition(getPlayer().id, 600, 880, 1);", + "harness.setObjectPosition(monster.id, 600, 380, 1);", + "harness.getRuntimeObject(monster.id).setAngle(90);", + "harness.getRuntimeObject(monster.id).getVariables().get('State').setString('Patrolling');", + "const caught = await harness.stepUntil(() => harness.getCurrentRuntimeScene() !== scene, { maxFrames: 240 });", + "harness.assert(caught, 'The monster caught the player, which restarted the scene.');" + ] + }, + { + "name": "The monster follows the player upstairs", + "description": "Puts the player upstairs and the monster at the foot of the stairs, hearing the player: the monster climbs the stairs and catches the player.", + "type": "gameplay", + "source": [ + "await harness.goToScene('Game Scene');", + "// Faster in the software renderer used to run tests.", + "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.setTimeoutMs(120000);", + "const DT = { dtMs: 50 };", + "const getPlayer = () => harness.getObjects('Player')[0];", + "const scene = harness.getCurrentRuntimeScene();", + "const monster = harness.getObjects('Monster')[0];", + "harness.setObjectPosition(getPlayer().id, 600, 140, 321);", + "harness.setObjectPosition(monster.id, 795, 940, 1);", + "harness.getRuntimeObject(monster.id).getVariables().get('State').setString('Chasing');", + "// Climbing the stairs and the path around the hall take a while in the software renderer.", + "harness.setTimeoutMs(150000);", + "let highest = 0;", + "let caught = false;", + "// Frames of 50 ms: the monster waits 5 seconds before walking to the player, then climbs.", + "for (let frame = 1; frame <= 400 && !caught; frame++) {", + " await harness.stepFrames(1, DT);", + " caught = harness.getCurrentRuntimeScene() !== scene;", + " if (caught) break;", + " const m = harness.getObjects('Monster')[0];", + " highest = Math.max(highest, m.z);", + " if (frame % 40 === 0) console.log('monster=' + [m.centerX, m.centerY, m.z].map(Math.round));", + "}", + "console.log('highest=' + Math.round(highest));", + "harness.assert(highest > 300, 'The monster climbed the stairs (highest: ' + Math.round(highest) + ').');", + "harness.assert(caught, 'The monster caught the player upstairs, which restarted the scene.');" + ] + } + ], + "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.2", + "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": 2 + } + ], + "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::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } + ], + "actions": [], + "events": [ + { + "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" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "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::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "MousePointerLock::isPointerLocked" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetAngle" + }, + "parameters": [ + "Object", + "+", + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" + ] + }, + { + "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": "3D", + "extensionNamespace": "", + "fullName": "3D lights", + "gdevelopVersion": ">=5.5.222", + "helpPath": "/objects/3d-light", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLWxpZ2h0YnVsYi1vbi1vdXRsaW5lIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTIwLDExSDIzVjEzSDIwVjExTTEsMTFINFYxM0gxVjExTTEzLDFWNEgxMVYxSDEzTTQuOTIsMy41TDcuMDUsNS42NEw1LjYzLDcuMDVMMy41LDQuOTNMNC45MiwzLjVNMTYuOTUsNS42M0wxOS4wNywzLjVMMjAuNSw0LjkzTDE4LjM3LDcuMDVMMTYuOTUsNS42M00xMiw2QTYsNiAwIDAsMSAxOCwxMkMxOCwxNC4yMiAxNi43OSwxNi4xNiAxNSwxNy4yVjE5QTEsMSAwIDAsMSAxNCwyMEgxMEExLDEgMCAwLDEgOSwxOVYxNy4yQzcuMjEsMTYuMTYgNiwxNC4yMiA2LDEyQTYsNiAwIDAsMSAxMiw2TTE0LDIxVjIyQTEsMSAwIDAsMSAxMywyM0gxMUExLDEgMCAwLDEgMTAsMjJWMjFIMTRNMTEsMThIMTNWMTUuODdDMTQuNzMsMTUuNDMgMTYsMTMuODYgMTYsMTJBNCw0IDAgMCwwIDEyLDhBNCw0IDAgMCwwIDgsMTJDOCwxMy44NiA5LjI3LDE1LjQzIDExLDE1Ljg3VjE4WiIgLz48L3N2Zz4=", + "name": "Light3D", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/f237ae4e3b857c556846c7b2c0b132556fd4bcdeff217034b4d9c97dc1aab1d6_lightbulb-on-outline.svg", + "shortDescription": "A collection of light object for 3D.", + "version": "2.0.0", + "description": "A collection of light object for 3D.", + "origin": { + "identifier": "Light3D", + "name": "gdevelop-extension-store" + }, + "tags": [ + "3d", + "light" + ], + "authorIds": [ + "IWykYNRvhCZBN3vEgKEbBPOR3Oc2" + ], + "changelog": [ + { + "version": "2.0.0", + "breaking": "- Point and spot lights now fade with the distance like they do with the 3D renderer of recent GDevelop versions (Three.js r185), including on previous GDevelop versions. Lights made for a previous version of the extension need a higher intensity (and possibly a lower decay) to look the same." + } + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onFirstSceneLoaded", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "// Before Three.js r165, GDevelop rendered with \"legacy lights\": point and spot", + "// lights faded linearly up to their range and every light color was", + "// multiplied by PI. Recent GDevelop versions use the physically based falloff", + "// of Three.js r185 instead, which is what the light properties are tuned for.", + "// Use the same falloff with older GDevelop versions so that lights look the", + "// same with every version:", + "// - distances are divided by 100 because recent GDevelop versions render 3D", + "// with a default world scale of 100 pixels per unit, while older versions", + "// work in pixels,", + "// - the attenuation is divided by PI as the legacy light colors are multiplied by PI.", + "// It must be patched before any shader using lights is compiled, as Three.js", + "// caches compiled shaders.", + "const lightsParsBegin = THREE.ShaderChunk.lights_pars_begin;", + "if (!lightsParsBegin.includes('#if defined ( LEGACY_LIGHTS )')) {", + " return;", + "}", + "THREE.ShaderChunk.lights_pars_begin = lightsParsBegin.replace(", + " /#if defined \\( LEGACY_LIGHTS \\)[\\s\\S]*?#else/,", + " [", + " '#if defined ( LEGACY_LIGHTS )',", + " '\\t\\tfloat distanceFalloff = 1.0 / max( pow( lightDistance * 0.01, decayExponent ), 0.01 );',", + " '\\t\\tif ( cutoffDistance > 0.0 ) {',", + " '\\t\\t\\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );',", + " '\\t\\t}',", + " '\\t\\treturn distanceFalloff * RECIPROCAL_PI;',", + " '\\t#else',", + " ].join('\\n')", + ");" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Define spot light helper classes JavaScript code.", + "fullName": "Define spot light helper classes", + "functionType": "Action", + "name": "DefineSpotLightHelperClasses", + "private": true, + "sentence": "Define spot light helper classes JavaScript code", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "//@ts-ignore", + "if (gdjs.__light3DExtension.spot) {", + " //@ts-ignore", + " return;", + "}", + "", + "/**", + " * @typedef {gdjs.CustomRuntimeObject3D & {__light3D: THREE.SpotLight, _getIsCastingShadow: () => boolean, _getConeAngle: () => number, _getColor: () => string}} SpotLightRuntimeObject", + " */", + "", + "const game = runtimeScene.getGame();", + "", + "const coneLength = 64;", + "", + "class SpotLightHelper extends THREE.Object3D {", + " /** @type {SpotLightRuntimeObject} */", + " object;", + " /** @type {THREE.LineSegments} */", + " cone;", + " /** @type {THREE.LineSegments} */", + " centerLine;", + " /** @type {THREE.Mesh} */", + " originBox;", + "", + "\t/**", + " * @param {SpotLightRuntimeObject} object", + "\t */", + " constructor(object) {", + " super();", + " this.object = object;", + " this.type = 'SpotLightHelper';", + "", + " const coneGeometry = new THREE.BufferGeometry();", + " const conePositions = [", + " 0, 0, 0, 1, 0, 0,", + " 0, 0, 0, 1, 1, 0,", + " 0, 0, 0, 1, - 1, 0,", + " 0, 0, 0, 1, 0, 1,", + " 0, 0, 0, 1, 0, - 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(", + " 1, Math.cos(p1), Math.sin(p1),", + " 1, Math.cos(p2), Math.sin(p2)", + " );", + " }", + " 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, coneLength, 0, 0", + " ], 3));", + " this.centerLine = new THREE.LineSegments(", + " centerLineGeometry,", + " new THREE.LineBasicMaterial({ fog: false, toneMapped: false })", + " );", + " this.add(this.centerLine);", + "", + " this.originBox = new THREE.Mesh(", + " new THREE.BoxGeometry(16, 16, 16),", + " new THREE.MeshBasicMaterial({ fog: false, toneMapped: false })", + " );", + " this.add(this.originBox);", + " this.update();", + "", + " //@ts-ignore", + " this.originBox.gdjsRuntimeObject = object;", + " }", + "", + " dispose() {", + " this.cone.geometry.dispose();", + " this.cone.material.dispose();", + " this.centerLine.geometry.dispose();", + " this.centerLine.material.dispose();", + " this.originBox.geometry.dispose();", + " this.originBox.material.dispose();", + " }", + "", + " update() {", + " /** @type {number} */", + " const coneAngle = gdjs.toRad(this.object._getConeAngle());", + " const coneWidth = coneLength * Math.sin(coneAngle);", + " const coneHeight = coneLength * Math.cos(coneAngle);", + " this.cone.scale.set(coneHeight, coneWidth, coneWidth);", + " const color = gdjs.rgbOrHexStringToNumber(this.object._getColor());", + " this.cone.material.color.set(color);", + " this.centerLine.material.color.set(color);", + " this.originBox.material.color.set(color);", + " }", + "}", + "", + "/**", + " * @param {string} colorString", + " * @param {THREE.Color} threeColor", + " */", + "const setThreeColor = (colorString, threeColor) => {", + " const integerColor = gdjs.rgbOrHexToRGBColor(colorString);", + " threeColor.r = integerColor[0] / 255;", + " threeColor.g = integerColor[1] / 255;", + " threeColor.b = integerColor[2] / 255;", + "};", + "", + "class SpotLightAdapter {", + " /**", + " * @param object {gdjs.CustomRuntimeObject3D}", + " * @param spotLight {THREE.SpotLight}", + " */", + " constructor(object, spotLight) {", + " this.object = object;", + " this.spotLight = spotLight;", + " }", + "", + " /**", + " * @param targetX {number}", + " * @param targetY {number}", + " * @param targetZ {number}", + " */", + " lookAtPosition(targetX, targetY, targetZ) {", + " const runtimeSene = this.object.getRuntimeScene();", + " const inverseWorldScale = runtimeSene.getRenderer3DInverseWorldScale ? runtimeSene.getRenderer3DInverseWorldScale() : 1;", + "", + " // Remove from the parent to avoid the scene scale of -1 on Y to mess with the formula.", + " const parent = this.spotLight.parent;", + " this.spotLight.parent = null;", + " this.spotLight.lookAt(", + " targetX * inverseWorldScale,", + " targetY * inverseWorldScale,", + " targetZ * inverseWorldScale);", + " this.spotLight.parent = parent;", + "", + " // Angle setters update Three.js angles, so we save them first.", + " const rotationX = gdjs.toDegrees(this.spotLight.rotation.x);", + " const rotationY = gdjs.toDegrees(this.spotLight.rotation.y);", + " const rotationZ = gdjs.toDegrees(this.spotLight.rotation.z);", + " this.object.setRotationX(rotationX);", + " this.object.setRotationY(rotationY);", + " this.object.setAngle(rotationZ + 90);", + " }", + "", + " /**", + " * @param color {string}", + " */", + " setColor(color) {", + " setThreeColor(color, this.spotLight.color);", + " }", + "", + " /**", + " * @param intensity {number}", + " */", + " setIntensity(intensity) {", + " this.spotLight.intensity = intensity;", + " }", + "", + " /**", + " * @param decay {number}", + " */", + " setDecay(decay) {", + " this.spotLight.decay = decay;", + " }", + "", + " /**", + " * @param value {number}", + " */", + " setConeLength(value) {", + " const runtimeSene = this.object.getRuntimeScene();", + " const inverseWorldScale = runtimeSene.getRenderer3DInverseWorldScale ? runtimeSene.getRenderer3DInverseWorldScale() : 1;", + " this.spotLight.distance = value * inverseWorldScale;", + " }", + "", + " /**", + " * @param coneAngle {number}", + " */", + " setConeAngle(coneAngle) {", + " this.spotLight.angle = coneAngle * Math.PI / 180;", + " }", + "", + " /**", + " * @param smoothing {number}", + " */", + " setSmoothing(smoothing) {", + " this.spotLight.penumbra = smoothing;", + " }", + "", + " /**", + " * @param isCastingShadow {boolean}", + " */", + " setCastingShadow(isCastingShadow) {", + " // This is applied by applyVisibilityAndShadow", + " }", + "", + " /**", + " * @param shadowQuality {\"Low\" | \"Medium\" | \"High\" | \"VeryHigh\"}", + " */", + " setShadowQuality(shadowQuality) {", + " let size = 512;", + " switch (shadowQuality) {", + " case \"Low\":", + " size = 256;", + " break;", + " case \"Medium\":", + " size = 512;", + " break;", + " case \"High\":", + " size = 1024;", + " break;", + " case \"VeryHigh\":", + " size = 2048;", + " break;", + " }", + " const shadow = this.spotLight.shadow;", + " const mapSize = shadow.mapSize;", + " if (mapSize.width !== size) {", + " mapSize.set(size, size);", + "", + " // Force the recreation of the shadow map texture:", + " if (shadow.map) {", + " shadow.map.dispose();", + " shadow.map = null;", + " }", + " shadow.needsUpdate = true;", + " }", + " }", + "", + " /**", + " * @param value {number}", + " */", + " setShadowCameraNearPlane(value) {", + " const runtimeSene = this.object.getRuntimeScene();", + " const inverseWorldScale = runtimeSene.getRenderer3DInverseWorldScale ? runtimeSene.getRenderer3DInverseWorldScale() : 1;", + " this.spotLight.shadow.camera.near = value * inverseWorldScale;", + " }", + "", + " /**", + " * @param value {number}", + " */", + " setShadowCameraFarPlane(value) {", + " const runtimeSene = this.object.getRuntimeScene();", + " const inverseWorldScale = runtimeSene.getRenderer3DInverseWorldScale ? runtimeSene.getRenderer3DInverseWorldScale() : 1;", + " this.spotLight.shadow.camera.far = value * inverseWorldScale;", + " }", + "", + " /**", + " * @param value {number}", + " * @param quality {\"Low\" | \"Medium\" | \"High\" | \"VeryHigh\"}", + " * @param coneAngle {number}", + " */", + " setShadowBias(value, quality, coneAngle) {", + " const runtimeSene = this.object.getRuntimeScene();", + " const inverseWorldScale = runtimeSene.getRenderer3DInverseWorldScale ? runtimeSene.getRenderer3DInverseWorldScale() : 1;", + " const biasMultiplier =", + " quality === 'Low'", + " ? 8", + " : quality === 'Medium'", + " ? 4", + " : quality === 'High'", + " ? 2 : 1;", + " // Multiply by 0.125 to make values around 0.001 work best.", + " this.spotLight.shadow.bias = -value * 0.125 * biasMultiplier *", + " Math.tan(gdjs.toRad(gdjs.evtTools.common.clamp(coneAngle, 0, 89))) *", + " inverseWorldScale;", + " }", + "", + " /**", + " * @param resourceName {string}", + " */", + " setImage(resourceName) {", + " if (!resourceName) {", + " this.spotLight.map = null;", + " return;", + " }", + " const texture = game.getImageManager().getThreeTexture(resourceName);", + " this.spotLight.map = texture;", + " }", + "}", + "", + "//@ts-ignore", + "gdjs.__light3DExtension.spot = {", + " SpotLightAdapter,", + " SpotLightHelper,", + "}" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Define point light helper classes JavaScript code.", + "fullName": "Define point light helper classes", + "functionType": "Action", + "name": "DefinePointLightHelperClasses", + "private": true, + "sentence": "Define point light helper classes JavaScript code", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "//@ts-ignore", + "if (gdjs.__light3DExtension.point) {", + " //@ts-ignore", + " return;", + "}", + "", + "/**", + " * @typedef {gdjs.CustomRuntimeObject3D & {__light3D: THREE.PointLight, _getIsCastingShadow: () => boolean, _getColor: () => string}} PointLightRuntimeObject", + " */", + "", + "const game = runtimeScene.getGame();", + "", + "/**", + " * @extends {THREE.Mesh}", + " */", + "class PointLightHelper extends THREE.Mesh {", + " /** @type {PointLightRuntimeObject} */", + " object;", + "", + "\t/**", + " * @param {PointLightRuntimeObject} object", + "\t */", + " constructor(object) {", + " const geometry = new THREE.SphereGeometry(16, 4, 2);", + " const material = new THREE.MeshBasicMaterial({ wireframe: true, fog: false, toneMapped: false });", + " super(geometry, material);", + " this.object = object;", + " this.update();", + "", + " //@ts-ignore", + " this.gdjsRuntimeObject = object;", + " }", + "", + " dispose() {", + " this.geometry.dispose();", + " this.material.dispose();", + " }", + "", + " update() {", + " const color = gdjs.rgbOrHexStringToNumber(this.object._getColor());", + " this.material.color.set(color);", + " }", + "}", + "", + "/**", + " * @param {string} colorString", + " * @param {THREE.Color} threeColor", + " */", + "const setThreeColor = (colorString, threeColor) => {", + " const integerColor = gdjs.rgbOrHexToRGBColor(colorString);", + " threeColor.r = integerColor[0] / 255;", + " threeColor.g = integerColor[1] / 255;", + " threeColor.b = integerColor[2] / 255;", + "};", + "", + "class PointLightAdapter {", + " /**", + " * @param object {gdjs.CustomRuntimeObject3D}", + " * @param pointLight {THREE.PointLight}", + " */", + " constructor(object, pointLight) {", + " this.object = object;", + " this.pointLight = pointLight;", + " }", + "", + " /**", + " * @param color {string}", + " */", + " setColor(color) {", + " setThreeColor(color, this.pointLight.color);", + " }", + "", + " /**", + " * @param intensity {number}", + " */", + " setIntensity(intensity) {", + " this.pointLight.intensity = intensity;", + " }", + "", + " /**", + " * @param decay {number}", + " */", + " setDecay(decay) {", + " this.pointLight.decay = decay;", + " }", + "", + " /**", + " * @param value {number}", + " */", + " setRange(value) {", + " const runtimeScene = this.object.getRuntimeScene();", + " const inverseWorldScale = runtimeScene.getRenderer3DInverseWorldScale ? runtimeScene.getRenderer3DInverseWorldScale() : 1;", + " this.pointLight.distance = value * inverseWorldScale;", + " }", + "", + " /**", + " * @param isCastingShadow {boolean}", + " */", + " setCastingShadow(isCastingShadow) {", + " // This is applied by applyVisibilityAndShadow", + " }", + "", + " /**", + " * @param shadowQuality {\"Low\" | \"Medium\" | \"High\"}", + " */", + " setShadowQuality(shadowQuality) {", + " let size = 512;", + " switch (shadowQuality) {", + " case \"Low\":", + " size = 512;", + " break;", + " case \"Medium\":", + " size = 1024;", + " break;", + " case \"High\":", + " size = 2048;", + " break;", + " }", + " const shadow = this.pointLight.shadow;", + " const mapSize = shadow.mapSize;", + " if (mapSize.width !== size) {", + " mapSize.set(size, size);", + "", + " // Force the recreation of the shadow map texture:", + " if (shadow.map) {", + " shadow.map.dispose();", + " shadow.map = null;", + " }", + " shadow.needsUpdate = true;", + " }", + " }", + "", + " /**", + " * @param value {number}", + " */", + " setShadowCameraNearPlane(value) {", + " const runtimeSene = this.object.getRuntimeScene();", + " const inverseWorldScale = runtimeSene.getRenderer3DInverseWorldScale ? runtimeSene.getRenderer3DInverseWorldScale() : 1;", + "", + " this.pointLight.shadow.camera.near = value * inverseWorldScale;", + " }", + "", + " /**", + " * @param value {number}", + " */", + " setShadowCameraFarPlane(value) {", + " const runtimeSene = this.object.getRuntimeScene();", + " const inverseWorldScale = runtimeSene.getRenderer3DInverseWorldScale ? runtimeSene.getRenderer3DInverseWorldScale() : 1;", + "", + " this.pointLight.shadow.camera.far = value * inverseWorldScale;", + " }", + "", + " /**", + " * @param value {number}", + " * @param quality {\"Low\" | \"Medium\" | \"High\"}", + " */", + " setShadowBias(value, quality) {", + " const runtimeSene = this.object.getRuntimeScene();", + " const inverseWorldScale = runtimeSene.getRenderer3DInverseWorldScale ? runtimeSene.getRenderer3DInverseWorldScale() : 1;", + "", + " const biasMultiplier =", + " quality === 'Low'", + " ? 4", + " : quality === 'Medium'", + " ? 2", + " : 1;", + " this.pointLight.shadow.bias = -value * biasMultiplier * inverseWorldScale;", + " }", + "}", + "", + "//@ts-ignore", + "gdjs.__light3DExtension.point = {", + " PointLightAdapter,", + " PointLightHelper,", + "}" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "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": [ + "//@ts-ignore", + "if (gdjs.__light3DExtension) {", + " //@ts-ignore", + " return;", + "}", + "", + "/**", + " * @typedef {gdjs.CustomRuntimeObject3D & {__light3D: THREE.SpotLight | THREE.PointLight, _getIsCastingShadow: () => boolean}} LightRuntimeObject", + " */", + "", + "const game = runtimeScene.getGame();", + "const isInGameEdition = game.isInGameEdition && game.isInGameEdition();", + "const logger = new gdjs.Logger('3D lights');", + "", + "", + "class Light3DRenderer extends gdjs.CustomRuntimeObject3DRenderer {", + "", + " constructor(", + " object,", + " instanceContainer,", + " parent", + " ) {", + " super(object, instanceContainer, parent);", + " }", + "", + " _updateThreeGroup() {", + " /** @type {LightRuntimeObject} */", + " //@ts-ignore", + " const object = this._object;", + " const threeObject3D = this.get3DRendererObject();", + "", + " threeObject3D.rotation.set(", + " gdjs.toRad(object.getRotationX()),", + " gdjs.toRad(object.getRotationY()),", + " gdjs.toRad(object.angle)", + " );", + "", + " threeObject3D.position.set(", + " object.getX(),", + " object.getY(),", + " object.getZ()", + " );", + "", + " // Force the scale to 1 because the light doesn't really has a size.", + " threeObject3D.scale.set(", + " object.isFlippedX() ? -1 : 1,", + " object.isFlippedY() ? -1 : 1,", + " object.isFlippedZ() ? -1 : 1", + " );", + "", + " threeObject3D.visible = !this._object.hidden;", + "", + " const spotLight = object.__light3D;", + "", + " const editor = game.getInGameEditor ? game.getInGameEditor() : null;", + "", + " spotLight.castShadow = false;", + " spotLight.visible = false;", + "", + " // Instances hidden at start stay visible in the editor, but their light must not shine.", + " const isHiddenAtStartInEditor =", + " !!editor && !!editor.isInstanceHiddenAtStart && editor.isInstanceHiddenAtStart(object);", + "", + " if (!object.isHidden() && !isHiddenAtStartInEditor) {", + " let isSelected = false;", + " if (editor) {", + " const selectedObject = editor._selection.getLastSelectedObject();", + " let parentObject = object;", + " isSelected = parentObject === selectedObject;", + " while (!isSelected && parentObject.getInstanceContainer()", + " //@ts-ignore", + " .getOwner) {", + " parentObject = parentObject.getInstanceContainer()", + " //@ts-ignore", + " .getOwner();", + " isSelected = parentObject === selectedObject;", + " }", + " }", + " if (!editor || !editor.areEffectsHidden() || isSelected) {", + " let rootObject = object;", + " while (rootObject.getInstanceContainer()", + " //@ts-ignore", + " .getOwner) {", + " rootObject = rootObject.getInstanceContainer()", + " //@ts-ignore", + " .getOwner();", + " }", + " const runtimeScene = rootObject.getRuntimeScene();", + " let distanceSq = 0;", + " if (!isSelected) {", + " const layerName = rootObject.getLayer();", + " const cameraX = gdjs.evtTools.camera.getCameraX(runtimeScene, layerName, 0);", + " const cameraY = gdjs.evtTools.camera.getCameraY(runtimeScene, layerName, 0);", + " const cameraZ = gdjs.scene3d.camera.getCameraZ(runtimeScene, layerName, 0);", + " const deltaX = rootObject.getX() - cameraX;", + " const deltaY = rootObject.getY() - cameraY;", + " const deltaZ = rootObject.getZ() - cameraZ;", + " distanceSq = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;", + " }", + " getLightManager(", + " //@ts-ignore", + " runtimeScene", + " ).applyVisibilityAndShadow(object, distanceSq);", + " }", + " }", + "", + " // Force the visibility to be checked every frame", + " this._isContainerDirty = true;", + " }", + "}", + "", + "const editorLightCountMax = {", + " value: 20", + "};", + "", + "const lightCountMax = {", + " value: 20", + "};", + "", + "const editorLightShadowCountMax = {", + " value: 4", + "};", + "", + "const lightShadowCountMax = {", + " value: 4", + "};", + "", + "gdjs.registerRuntimeScenePreEventsCallback(", + " runtimeScene => {", + " getLightManager(", + " //@ts-ignore", + " runtimeScene.getScene()", + " ).clear();", + " });", + "if (gdjs.registerInGameEditorPostStepCallback) {", + " gdjs.registerInGameEditorPostStepCallback(", + " inGameEditor => {", + " getLightManager(", + " //@ts-ignore", + " inGameEditor.getEditedInstanceContainer().getScene()", + " ).clear();", + " });", + "}", + "", + "/**", + " * How many lights of each kind this device can take.", + " *", + " * These are hard limits rather than performance trade-offs. three.js binds one", + " * texture unit per shadow map and one more for a spot light's projected", + " * texture, and spends a varying on every shadow casting light. Going over what", + " * the driver allows does not make the scene slower - the shader fails to link", + " * and everything using that material renders black. Only 16 fragment texture", + " * units and 15 varyings are guaranteed across devices, and materials need most", + " * of those, so the budget is read from the device instead of being fixed.", + " *", + " * The budget is shared between the light kinds a scene uses, so it is given", + " * how many of them there are rather than assuming all of them: a scene lit by", + " * point lights alone would otherwise have three quarters of its texture units", + " * held back for kinds that never show up.", + " *", + " * @param {THREE.WebGLRenderer | null} threeRenderer", + " * @param {number} kindCount How many light kinds the scene uses, at least one.", + " * @returns {{shadowCount: number, mapCount: number, count: number}}", + " */", + "function computeDeviceLightBudget(threeRenderer, kindCount) {", + " // When the renderer cannot be reached, assume the guaranteed minimums", + " // rather than the device this happens to be running on.", + " const capabilities = (threeRenderer && threeRenderer.capabilities) || {};", + " const maxTextures = capabilities.maxTextures || 16;", + " const maxVaryings = capabilities.maxVaryings || 15;", + " const maxFragmentUniforms = capabilities.maxFragmentUniforms || 224;", + "", + " // Left for the material itself: colour, normal, roughness, metalness,", + " // emissive, ambient occlusion and the environment map.", + " const textureUnitsForMaterials = 8;", + " // Varyings already spent on positions, normals, uvs and fog.", + " const varyingsForGeometry = 12;", + " // Uniform vectors already spent on matrices, material properties and the", + " // uv transforms of a material's own textures.", + " const uniformVectorsForMaterials = 64;", + " // Measured on a MeshStandardMaterial: a point light costs 4 uniform", + " // vectors, a spot light 7, and casting a shadow adds around 11 more on", + " // top of either. Take the dearest of them, since a kind count says how", + " // many kinds a scene uses but not which ones.", + " const uniformVectorsPerLight = 8;", + " const uniformVectorsPerShadow = 12;", + "", + " const availableTextureUnits = Math.max(", + " kindCount, maxTextures - textureUnitsForMaterials);", + " const availableVaryings = Math.max(", + " kindCount, maxVaryings - varyingsForGeometry);", + " const availableUniformVectors = Math.max(", + " 16, maxFragmentUniforms - uniformVectorsForMaterials);", + "", + " // Every shadow caster costs a texture unit and a varying, whichever kind", + " // it is. Keep a share of the texture units back for projected textures.", + " const shadowCount = Math.max(0, Math.min(", + " Math.floor(availableTextureUnits / (kindCount + 1)),", + " Math.floor(availableVaryings / kindCount)", + " ));", + " // What is left bounds how many spot lights can project a texture. Lights", + " // that neither cast a shadow nor project a texture cost no texture unit.", + " const mapCount = Math.max(0,", + " availableTextureUnits - shadowCount * kindCount);", + " // Shadow casters are paid for first, then the uniform vectors left over", + " // decide how many lights of each kind can be lit at all.", + " const uniformVectorsForShadows =", + " shadowCount * kindCount * uniformVectorsPerShadow;", + " const count = Math.max(1, Math.floor(", + " Math.max(uniformVectorsPerLight * kindCount,", + " availableUniformVectors - uniformVectorsForShadows)", + " / (uniformVectorsPerLight * kindCount)));", + "", + " return { shadowCount, mapCount, count };", + "}", + "", + "/**", + " * A capacity that never goes past what the device can take, however high the", + " * game sets its own maximum. Both sides are read through getters, so that", + " * changing the game's maximum at runtime still takes effect, and so that a", + " * device budget recomputed for more light kinds is picked up as well.", + " *", + " * @param {{value: number}} requested", + " * @param {() => number} getDeviceMax", + " * @returns {{value: number}}", + " */", + "function makeDeviceCappedCapacity(requested, getDeviceMax) {", + " return {", + " get value() {", + " return Math.min(requested.value, getDeviceMax());", + " }", + " };", + "}", + "", + "/**", + " * Get the light manager of a scene.", + " * @param {gdjs.RuntimeScene & {__lightManager: LightManager}} runtimeScene", + " */", + "function getLightManager(runtimeScene) {", + " if (!runtimeScene.__lightManager) {", + " // Create the shared manager if necessary.", + " const requestedCount = isInGameEdition", + " ? editorLightCountMax : lightCountMax;", + " const requestedShadowCount = isInGameEdition", + " ? editorLightShadowCountMax : lightShadowCountMax;", + "", + " // The renderer is read anew on every recomputation: it may not", + " // exist yet when the manager is created on the first frame.", + " runtimeScene.__lightManager = new LightManager(", + " requestedCount, requestedShadowCount,", + " kindCount => computeDeviceLightBudget(", + " runtimeScene.getGame().getRenderer().getThreeRenderer(),", + " kindCount));", + " }", + " return runtimeScene.__lightManager;", + "}", + "", + "/**", + " * Lights kept sorted by their distance to the camera, capped to a fixed", + " * number of entries.", + " *", + " * The cap is a *count* and not a weighted cost, on purpose: three.js compiles", + " * the number of lights of each type into its shaders, so a list whose length", + " * changes from one frame to the next forces a shader recompilation every time", + " * a new length is seen. A count based cap keeps the length pinned to", + " * `capacity` for as long as enough lights are candidates.", + " */", + "class CappedLightList {", + " /** @type {Array<{object: LightRuntimeObject, sortDistance: number}>} */", + " objects = [];", + " /** @type {{value: number}} */", + " capacity;", + " /** @type {(object: LightRuntimeObject) => void} */", + " onInsertion;", + " /** @type {(object: LightRuntimeObject) => void} */", + " onDeletion;", + "", + " /**", + " * @param {{value: number}} capacity", + " * @param {(object: LightRuntimeObject) => void} onInsertion", + " * @param {(object: LightRuntimeObject) => void} onDeletion", + " */", + " constructor(capacity, onInsertion, onDeletion) {", + " this.capacity = capacity;", + " this.onInsertion = onInsertion;", + " this.onDeletion = onDeletion;", + " }", + "", + " clear() {", + " this.objects.length = 0;", + " }", + "", + " /** Drop the farthest lights until the list fits its capacity again. */", + " trimToCapacity() {", + " while (this.objects.length > this.capacity.value) {", + " this.onDeletion(this.objects.pop().object);", + " }", + " }", + "", + " /**", + " * @param object {LightRuntimeObject}", + " * @param sortDistance {number} The squared distance to the camera.", + " */", + " insertByDistance(object, sortDistance) {", + " // A render can happen without a game step in between (the editor", + " // and the gameplay test harness render a paused game), so a light", + " // can be offered again before the lists were cleared. Keep it once,", + " // or the duplicates would evict the other lights - but apply its", + " // state again, as the renderer reset it before offering the light.", + " for (const pair of this.objects) {", + " if (pair.object === object) {", + " this.onInsertion(object);", + " return;", + " }", + " }", + " let insertionIndex = 0;", + " for (let index = this.objects.length - 1; index >= 0; index--) {", + " if (sortDistance >= this.objects[index].sortDistance) {", + " insertionIndex = index + 1;", + " break;", + " }", + " }", + " if (insertionIndex >= this.capacity.value) {", + " // Farther away than every light already filling the list.", + " return;", + " }", + "", + " // Make room first, reusing an evicted pair to avoid allocating one", + " // on every frame.", + " let pair = null;", + " while (this.objects.length >= this.capacity.value) {", + " pair = this.objects.pop();", + " this.onDeletion(pair.object);", + " }", + " if (pair) {", + " pair.object = object;", + " pair.sortDistance = sortDistance;", + " }", + " else {", + " pair = { object, sortDistance };", + " }", + " this.objects.splice(insertionIndex, 0, pair);", + " this.onInsertion(object);", + " }", + "}", + "", + "/** @param object {LightRuntimeObject} */", + "const showLight = (object) => { object.__light3D.visible = true; };", + "/** @param object {LightRuntimeObject} */", + "const hideLight = (object) => { object.__light3D.visible = false; };", + "/** @param object {LightRuntimeObject} */", + "const enableShadow = (object) => { object.__light3D.castShadow = true; };", + "/** @param object {LightRuntimeObject} */", + "const disableShadow = (object) => { object.__light3D.castShadow = false; };", + "", + "/**", + " * The lights of a scene, budgeted so that the numbers three.js compiles into", + " * its shaders stay put.", + " *", + " * three.js keys its shader programs on how many lights of each kind it can", + " * see - NUM_POINT_LIGHTS, NUM_SPOT_LIGHTS, NUM_SPOT_LIGHT_MAPS and the", + " * matching shadow counts. A single budget shared by every kind keeps the", + " * *total* pinned to the cap but lets the mix drift as the camera moves, and", + " * each mix that has not been seen before costs a shader compilation - a", + " * stutter the player feels. So each kind gets its own budget instead, which", + " * pins each of those numbers individually.", + " */", + "class LightManager {", + " /** @type {Map} Visible lights, by light kind. */", + " visibleLists = new Map();", + " /** @type {Map} Shadow casters, by light kind. */", + " shadowLists = new Map();", + " /** @type {Map} Lit light capacity, by light kind. */", + " maxCountByKind;", + " /** @type {Map} Shadow caster capacity, by light kind. */", + " shadowCountByKind;", + " /** @type {Set} The light kinds this scene has actually used. */", + " usedKinds = new Set();", + " /** @type {{shadowCount: number, mapCount: number, count: number}} */", + " deviceBudget;", + " /** @type {(kindCount: number) => {shadowCount: number, mapCount: number, count: number}} */", + " _computeDeviceBudget;", + " /** @type {{value: number}} */", + " _requestedCount;", + " /** @type {{value: number}} */", + " _requestedShadowCount;", + "", + " /**", + " * @param maxCount {{value: number}} The maximum number of lit lights, for", + " * each light kind, as the game asked for it.", + " * @param shadowCount {{value: number}} The maximum number of shadow", + " * casting lights, for each light kind, as the game asked for it.", + " * @param computeDeviceBudget {(kindCount: number) => {shadowCount: number, mapCount: number, count: number}}", + " * What the device can take, for a given number of light kinds.", + " */", + " constructor(maxCount, shadowCount, computeDeviceBudget) {", + " this._computeDeviceBudget = computeDeviceBudget;", + " this._requestedCount = maxCount;", + " this._requestedShadowCount = shadowCount;", + " // A scene that uses lights at all uses at least one kind of them.", + " this.deviceBudget = computeDeviceBudget(1);", + "", + " // Spot lights that project a texture already spend a texture unit on", + " // that texture, so they get the tighter of the two budgets.", + " this.maxCountByKind = new Map([", + " ['point', makeDeviceCappedCapacity(", + " maxCount, () => this.deviceBudget.count)],", + " ['spot', makeDeviceCappedCapacity(", + " maxCount, () => this.deviceBudget.count)],", + " ['spotWithMap', makeDeviceCappedCapacity(maxCount, () => Math.min(", + " this.deviceBudget.count, this.deviceBudget.mapCount))],", + " ]);", + " this.shadowCountByKind = new Map([", + " ['point', makeDeviceCappedCapacity(", + " shadowCount, () => this.deviceBudget.shadowCount)],", + " ['spot', makeDeviceCappedCapacity(", + " shadowCount, () => this.deviceBudget.shadowCount)],", + " ['spotWithMap', makeDeviceCappedCapacity(", + " shadowCount, () => this.deviceBudget.shadowCount)],", + " ]);", + " }", + "", + " /**", + " * Take note that the scene uses lights of this kind, sharing the device", + " * budget between one more kind if it had not been seen before.", + " *", + " * A kind can only ever be added, never dropped: a light that goes away for", + " * a while must not give the others a wider budget, or the numbers three.js", + " * compiles into its shaders would move again.", + " *", + " * @param kind {string}", + " */", + " _useKind(kind) {", + " if (this.usedKinds.has(kind)) {", + " return;", + " }", + " this.usedKinds.add(kind);", + " this.deviceBudget = this._computeDeviceBudget(this.usedKinds.size);", + "", + " // The budget just shrank: evict the lights that no longer fit, so", + " // that even this frame stays within what the device can take.", + " for (const list of this.visibleLists.values()) {", + " list.trimToCapacity();", + " }", + " for (const list of this.shadowLists.values()) {", + " list.trimToCapacity();", + " }", + "", + " if (this.deviceBudget.count < this._requestedCount.value", + " || this.deviceBudget.shadowCount", + " < this._requestedShadowCount.value) {", + " logger.info(", + " 'This device limits 3D lights to ' + this.deviceBudget.count", + " + ' per kind (' + this.deviceBudget.shadowCount", + " + ' casting a shadow, ' + this.deviceBudget.mapCount", + " + ' projecting a texture), now that ' + this.usedKinds.size", + " + ' kind(s) of light share its budget. Going over what it '", + " + 'reports would stop the shaders from linking.'", + " );", + " }", + " }", + "", + " clear() {", + " for (const list of this.visibleLists.values()) {", + " list.clear();", + " }", + " for (const list of this.shadowLists.values()) {", + " list.clear();", + " }", + " }", + "", + " /**", + " * The kind of a light, as far as the shaders are concerned. Lights of", + " * different kinds are counted separately by three.js, so they must be", + " * budgeted separately too.", + " * @param light {THREE.SpotLight | THREE.PointLight}", + " * @returns {string}", + " */", + " static getLightKind(light) {", + " //@ts-ignore - `isSpotLight` is set by three.js on SpotLight instances.", + " if (!light.isSpotLight) {", + " return 'point';", + " }", + " // A projected texture adds to NUM_SPOT_LIGHT_MAPS, so a spot light", + " // that has one is a different kind from a spot light that has not.", + " //@ts-ignore - only spot lights have a projected texture.", + " return light.map ? 'spotWithMap' : 'spot';", + " }", + "", + " /**", + " * @param lists {Map}", + " * @param kind {string}", + " * @param capacity {{value: number}}", + " * @param onInsertion {(object: LightRuntimeObject) => void}", + " * @param onDeletion {(object: LightRuntimeObject) => void}", + " * @returns {CappedLightList}", + " */", + " static _getListForKind(lists, kind, capacity, onInsertion, onDeletion) {", + " let list = lists.get(kind);", + " if (!list) {", + " list = new CappedLightList(capacity, onInsertion, onDeletion);", + " lists.set(kind, list);", + " }", + " return list;", + " }", + "", + " /**", + " * @param object {LightRuntimeObject}", + " * @param distance {number} The squared distance to the camera.", + " */", + " applyVisibilityAndShadow(object, distance) {", + " const light = object.__light3D;", + " const kind = LightManager.getLightKind(light);", + " this._useKind(kind);", + "", + " if (object._getIsCastingShadow()) {", + " LightManager._getListForKind(", + " this.shadowLists, kind, this.shadowCountByKind.get(kind),", + " enableShadow, disableShadow", + " ).insertByDistance(object, distance);", + " }", + " LightManager._getListForKind(", + " this.visibleLists, kind, this.maxCountByKind.get(kind),", + " showLight, hideLight", + " ).insertByDistance(object, distance);", + " }", + "}", + "", + "//@ts-ignore", + "gdjs.__light3DExtension = {", + " Light3DRenderer,", + " lightCountMax,", + " lightShadowCountMax,", + "}" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "the maximum number of nearest lights displayed simultaneously, counted separately for each kind of light (point lights, spot lights, spot lights projecting a texture). Devices that can't take that many lights enforce a lower limit.", + "fullName": "Max lights count", + "functionType": "ExpressionAndCondition", + "name": "LightCountMax", + "sentence": "max lights count", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "eventsFunctionContext.returnValue = gdjs.__light3DExtension.lightCountMax.value;", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "LightCountMax", + "name": "SetLightCountMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const value = eventsFunctionContext.getArgument(\"Value\");", + "", + "gdjs.__light3DExtension.lightCountMax.value = value;" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "the maximum number of nearest lights displayed with shadow simultaneously, counted separately for each kind of light (point lights, spot lights, spot lights projecting a texture). Devices that can't take that many shadows enforce a lower limit.", + "fullName": "Max lights with shadow count", + "functionType": "ExpressionAndCondition", + "name": "LightShadowCountMax", + "sentence": "max lights with shadow count", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "eventsFunctionContext.returnValue = gdjs.__light3DExtension.lightShadowCountMax.value;", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "LightShadowCountMax", + "name": "SetLightShadowCountMax", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const value = eventsFunctionContext.getArgument(\"Value\");", + "", + "gdjs.__light3DExtension.lightShadowCountMax.value = value;" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onFirstSceneLoaded" + }, + { + "functionName": "DefineHelperClasses" + }, + { + "functionName": "DefineSpotLightHelperClasses" + }, + { + "functionName": "DefinePointLightHelperClasses" + }, + { + "functionName": "LightCountMax" + }, + { + "functionName": "SetLightCountMax" + }, + { + "functionName": "LightShadowCountMax" + }, + { + "functionName": "SetLightShadowCountMax" + } + ] + }, + "eventsBasedBehaviors": [], + "eventsBasedObjects": [ + { + "areaMaxX": 20, + "areaMaxY": 20, + "areaMaxZ": 20, + "areaMinX": -20, + "areaMinY": -20, + "areaMinZ": -20, + "defaultName": "SpotLight", + "description": "Light up a cone like a flashlight.", + "fullName": "3D spot light", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLWZsYXNobGlnaHQiIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNOSwxMEw2LDVIMThMMTUsMTBIOU0xOCw0SDZWMkgxOFY0TTksMjJWMTFIMTVWMjJIOU0xMiwxM0ExLDEgMCAwLDAgMTEsMTRBMSwxIDAgMCwwIDEyLDE1QTEsMSAwIDAsMCAxMywxNEExLDEgMCAwLDAgMTIsMTNaIiAvPjwvc3ZnPg==", + "is3D": true, + "isUsingLegacyInstancesRenderer": false, + "name": "SpotLight3D", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/b627f8e676b3aac26945b2c38db0cfa787d34d2eff23755d4eb1289452f6cd28_flashlight.svg", + "objects": [ + { + "assetStoreId": "", + "name": "Placeholder", + "persistentUuid": "6786eefd-3c18-4fcb-b3ae-95b07f953045", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 80, + "height": 80, + "depth": 80, + "enableTextureTransparency": false, + "facesOrientation": "Z", + "frontFaceResourceName": "", + "backFaceResourceName": "", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "", + "rightFaceResourceName": "", + "topFaceResourceName": "", + "bottomFaceResourceName": "", + "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 + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Placeholder" + } + ] + }, + "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": 40, + "height": 40, + "layer": "", + "name": "Placeholder", + "persistentUuid": "1310c768-04d4-4133-98d9-37d836f8a990", + "width": 40, + "x": -20, + "y": -20, + "z": -20, + "zOrder": 1, + "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": 9.06473532531222, + "windowMask": false, + "selectedLayer": "", + "gameEditorMode": "instances-editor" + }, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + }, + { + "type": { + "value": "Light3D::DefineSpotLightHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const {", + " Light3DRenderer,", + " //@ts-ignore", + "} = gdjs.__light3DExtension;", + "const {", + " SpotLightAdapter,", + " SpotLightHelper,", + " //@ts-ignore", + "} = gdjs.__light3DExtension.spot;", + "", + "/** @type {gdjs.CustomRuntimeObject3D} */", + "//@ts-ignore", + "const object = objects[0];", + "", + "const game = runtimeScene.getGame();", + "", + "// This is a hack that may break in future releases.", + "// Replace the group that would hold children objects by the light.", + "const layer = object.getInstanceContainer().getLayer(object.getLayer());", + "const group = object.getRenderer()._threeGroup;", + "layer.getRenderer().remove3DRendererObject(group);", + "", + "const spotLight = new THREE.SpotLight(0xffffff);", + "spotLight.position.copy(group.position);", + "spotLight.rotation.copy(group.rotation);", + "spotLight.add(spotLight.target);", + "spotLight.target.position.x = 100;", + "spotLight.up.set(0, 0, 1);", + "spotLight.shadow.camera.up.set(0, 0, 1);", + "//@ts-ignore", + "object.__light3D = spotLight;", + "//@ts-ignore", + "object.__spotLightAdapter = new SpotLightAdapter(object, spotLight);", + "", + "const spotLight3DRenderer = new Light3DRenderer(object, object._instanceContainer, object.getInstanceContainer());", + "object._renderer = spotLight3DRenderer;", + "if (game.isInGameEdition && game.isInGameEdition()) {", + " const spotLightHelper = new SpotLightHelper(object);", + " spotLightHelper.rotation.order = 'ZYX';", + " spotLightHelper.add(spotLight);", + " spotLight3DRenderer._threeGroup = spotLightHelper;", + " layer.getRenderer().add3DRendererObject(spotLightHelper);", + "}", + "else {", + " spotLight3DRenderer._threeGroup = spotLight;", + " layer.getRenderer().add3DRendererObject(spotLight);", + "}", + "", + "spotLight.updateMatrixWorld(true);", + "", + "// Allow to tween the light color.", + "//@ts-ignore", + "if (!object.setColor && !object.getColor) {", + " const prototype = Object.getPrototypeOf(object);", + " prototype.setColor = function(tint) {", + " this._setColor(tint);", + " this.__spotLightAdapter.setColor(tint);", + " }", + " prototype.getColor = function() {", + " return this._getColor(eventsFunctionContext);", + " }", + "}", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::SpotLight3D::UpdateFromProperties" + }, + "parameters": [ + "Object", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "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.__light3D.dispose();", + "object.get3DRendererObject().dispose();", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::SpotLight3D::UpdateFromProperties" + }, + "parameters": [ + "Object", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "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": "Light3D::SpotLight3D::SetColor" + }, + "parameters": [ + "Object", + "=", + "Color", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::SetIntensity" + }, + "parameters": [ + "Object", + "=", + "Intensity", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::SetDecay" + }, + "parameters": [ + "Object", + "=", + "Decay", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::SetConeLength" + }, + "parameters": [ + "Object", + "=", + "ConeLength", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::SetConeAngle" + }, + "parameters": [ + "Object", + "=", + "ConeAngle", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::SetSmoothing" + }, + "parameters": [ + "Object", + "=", + "Smoothing", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::SetShadowQuality" + }, + "parameters": [ + "Object", + "=", + "ShadowQuality", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::SetShadowCameraNearPlane" + }, + "parameters": [ + "Object", + "=", + "ShadowCameraNearPlane", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::SetShadowCameraFarPlane" + }, + "parameters": [ + "Object", + "=", + "ShadowCameraFarPlane", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::SetShadowBias" + }, + "parameters": [ + "Object", + "=", + "ShadowBias", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::UpdateImage" + }, + "parameters": [ + "Object", + "" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::UpdateHelper" + }, + "parameters": [ + "Object", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsCastingShadow", + "=", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Light3D::SpotLight3D::SetCastingShadow" + }, + "parameters": [ + "Object", + "no", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsCastingShadow", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Light3D::SpotLight3D::SetCastingShadow" + }, + "parameters": [ + "Object", + "yes", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "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 spotLightHelper = objects[0].get3DRendererObject();", + " spotLightHelper.update();", + "}", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "Update image", + "functionType": "Action", + "name": "UpdateImage", + "private": true, + "sentence": "Update the image of _PARAM0_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "/** @type {gdjs.CustomRuntimeObject3D} */\r", + "const object = objects[0];\r", + "const resourceName = object._getImage();\r", + "object.__spotLightAdapter.setImage(resourceName);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the cone angle of the light.", + "fullName": "Cone angle", + "functionType": "ExpressionAndCondition", + "name": "ConeAngle", + "sentence": "the cone angle", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ConeAngle" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ConeAngle", + "name": "SetConeAngle", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ConeAngle", + "=", + "Value" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::ApplyShadowBias" + }, + "parameters": [ + "Object", + "ShadowBias", + "ShadowQuality", + "ConeAngle", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const coneAngle = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__spotLightAdapter.setConeAngle(coneAngle);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the color of the light.", + "fullName": "Color", + "functionType": "ExpressionAndCondition", + "name": "Color", + "sentence": "the color", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "Color" + ] + } + ] + } + ], + "expressionType": { + "type": "color" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Color", + "name": "SetColor", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "Color", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const color = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__spotLightAdapter.setColor(color);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the smoothing of the light. Percent of the spotlight cone that is attenuated due to penumbra (between 0 and 1).", + "fullName": "Smoothing", + "functionType": "ExpressionAndCondition", + "name": "Smoothing", + "sentence": "the smoothing", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Smoothing" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Smoothing", + "name": "SetSmoothing", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Smoothing", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__spotLightAdapter.setSmoothing(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the intensity of the light.", + "fullName": "Intensity", + "functionType": "ExpressionAndCondition", + "name": "Intensity", + "sentence": "the intensity", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Intensity" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Intensity", + "name": "SetIntensity", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Intensity", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__spotLightAdapter.setIntensity(value);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the decay of the light. The amount the light dims along the distance of the light.", + "fullName": "Decay", + "functionType": "ExpressionAndCondition", + "name": "Decay", + "sentence": "the decay", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Decay" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Decay", + "name": "SetDecay", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Decay", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__spotLightAdapter.setDecay(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the light is casting shadows.", + "fullName": "Shadow casting", + "functionType": "Condition", + "group": "3D spot light shadow configuration", + "name": "IsCastingShadow", + "sentence": "_PARAM0_ casting shadows", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsCastingShadow", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Change if the light is casting shadows.", + "fullName": "Shadow casting", + "functionType": "Action", + "group": "3D spot light shadow configuration", + "name": "SetCastingShadow", + "sentence": "_PARAM0_ casting shadows: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "False", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsCastingShadow", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsCastingShadow", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__spotLightAdapter.setCastingShadow(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::SpotLight3D::SetConeLength" + }, + "parameters": [ + "Object", + "=", + "ConeLength", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + }, + { + "defaultValue": "yes", + "description": "Shadow casting", + "name": "Value", + "optional": true, + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "Rotate the light to light up a position.", + "fullName": "Look at position", + "functionType": "Action", + "group": "Angle", + "name": "LookAtPosition", + "sentence": "_PARAM0_ look at _PARAM1_ ; _PARAM2_ ; _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const targetX = eventsFunctionContext.getArgument(\"TargetX\");\r", + "const targetY = eventsFunctionContext.getArgument(\"TargetY\");\r", + "const targetZ = eventsFunctionContext.getArgument(\"TargetZ\");\r", + "\r", + "object.__spotLightAdapter.lookAtPosition(targetX, targetY, targetZ);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + }, + { + "description": "Target X", + "name": "TargetX", + "type": "expression" + }, + { + "description": "Target Y", + "name": "TargetY", + "type": "expression" + }, + { + "description": "Target Z", + "name": "TargetZ", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "Rotate the light to light up an object.", + "fullName": "Look at object", + "functionType": "Action", + "group": "Angle", + "name": "LookAtObject", + "sentence": "_PARAM0_ look at _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::SpotLight3D::LookAtPosition" + }, + "parameters": [ + "Object", + "Target.CenterX()", + "Target.CenterY()", + "Target.Object3D::CenterZ()", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + }, + { + "description": "Object", + "name": "Target", + "type": "objectList" + }, + { + "description": "3D capability", + "name": "Object3D", + "supplementaryInformation": "Scene3D::Base3DBehavior", + "type": "behavior" + } + ], + "objectGroups": [] + }, + { + "description": "the shadow quality of the light.", + "fullName": "Shadow quality", + "functionType": "ExpressionAndCondition", + "group": "3D spot light shadow configuration", + "name": "ShadowQuality", + "sentence": "the shadow quality", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "ShadowQuality" + ] + } + ] + } + ], + "expressionType": { + "supplementaryInformation": "[\"Low\",\"Medium\",\"High\"]", + "type": "stringWithSelector" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ShadowQuality", + "group": "3D spot light shadow configuration", + "name": "SetShadowQuality", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "ShadowQuality", + "=", + "Value" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::ApplyShadowBias" + }, + "parameters": [ + "Object", + "ShadowBias", + "ShadowQuality", + "ConeAngle", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__spotLightAdapter.setShadowQuality(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the shadow camera near plane of the light.", + "fullName": "Shadow camera near plane", + "functionType": "ExpressionAndCondition", + "group": "3D spot light shadow configuration", + "name": "ShadowCameraNearPlane", + "sentence": "the shadow camera near plane", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ShadowCameraNearPlane" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ShadowCameraNearPlane", + "group": "3D spot light shadow configuration", + "name": "SetShadowCameraNearPlane", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ShadowCameraNearPlane", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__spotLightAdapter.setShadowCameraNearPlane(value);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the shadow camera far plane of the light.", + "fullName": "Shadow camera far plane", + "functionType": "ExpressionAndCondition", + "group": "3D spot light shadow configuration", + "name": "ShadowCameraFarPlane", + "sentence": "the shadow camera far plane", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ShadowCameraFarPlane" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ShadowCameraFarPlane", + "group": "3D spot light shadow configuration", + "name": "SetShadowCameraFarPlane", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ShadowCameraFarPlane", + "=", + "Value" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::ApplyShadowCameraFarPlane" + }, + "parameters": [ + "Object", + "min(Value, ConeLength)", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the cone length of the light. 0 means no limit.", + "fullName": "Cone length", + "functionType": "ExpressionAndCondition", + "name": "ConeLength", + "sentence": "the cone length", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ConeLength" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ConeLength", + "name": "SetConeLength", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ConeLength", + "=", + "Value" + ] + } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__spotLightAdapter.setConeLength(value);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Three.js reduces the far plane to match the light range.\nSo, we need to set it again or it will stay at the lowest value the range took.\nWe do the min() on our side to in order:\n- to avoid a side effect when Three.js is upgraded\n- because Three.js keeps the old value in cache and doesn't apply it back unless we set the lower value explicitly ourselves." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::SpotLight3D::ApplyShadowCameraFarPlane" + }, + "parameters": [ + "Object", + "min(Value, ShadowCameraFarPlane)", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "Apply shadow camera far plane", + "functionType": "Action", + "group": "3D spot light shadow configuration", + "name": "ApplyShadowCameraFarPlane", + "private": true, + "sentence": "Apply shadow camera far plane of _PARAM0_ to _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__spotLightAdapter.setShadowCameraFarPlane(value);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the shadow bias of the light. Use this to avoid \"shadow acne\" due to depth buffer precision. Choose a value small enough like 0.001 to avoid creating distance between shadows and objects but not too small to avoid shadow glitches on low/medium quality. This value is used for high quality, and multiplied by 2 for medium quality and 4 for low quality.", + "fullName": "Shadow bias", + "functionType": "ExpressionAndCondition", + "group": "3D spot light shadow configuration", + "name": "ShadowBias", + "sentence": "the shadow bias", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ShadowBias" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ShadowBias", + "group": "3D spot light shadow configuration", + "name": "SetShadowBias", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ShadowBias", + "=", + "Value" + ] + }, + { + "type": { + "value": "Light3D::SpotLight3D::ApplyShadowBias" + }, + "parameters": [ + "Object", + "Value", + "ShadowQuality", + "ConeAngle", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "Apply shadow bias", + "functionType": "Action", + "group": "3D spot light shadow configuration", + "name": "ApplyShadowBias", + "private": true, + "sentence": "Apply shadow bias of _PARAM0_ to _PARAM1_ for a _PARAM2_ quality and cone angle _PARAM3_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "const quality = eventsFunctionContext.getArgument(\"Quality\");\r", + "const coneAngle = eventsFunctionContext.getArgument(\"Angle\");\r", + "\r", + "object.__spotLightAdapter.setShadowBias(value, quality, coneAngle);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::SpotLight3D", + "type": "object" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + }, + { + "description": "Quality", + "name": "Quality", + "type": "string" + }, + { + "description": "Cone angle", + "name": "Angle", + "type": "expression" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "onDestroy" + }, + { + "functionName": "onHotReloading" + }, + { + "functionName": "UpdateFromProperties" + }, + { + "functionName": "UpdateHelper" + }, + { + "functionName": "UpdateImage" + }, + { + "folderName": "Angle", + "children": [ + { + "functionName": "LookAtPosition" + }, + { + "functionName": "LookAtObject" + } + ] + }, + { + "functionName": "Color" + }, + { + "functionName": "SetColor" + }, + { + "functionName": "Intensity" + }, + { + "functionName": "SetIntensity" + }, + { + "functionName": "Decay" + }, + { + "functionName": "SetDecay" + }, + { + "functionName": "ConeLength" + }, + { + "functionName": "SetConeLength" + }, + { + "functionName": "ConeAngle" + }, + { + "functionName": "SetConeAngle" + }, + { + "functionName": "Smoothing" + }, + { + "functionName": "SetSmoothing" + }, + { + "folderName": "3D spot light shadow configuration", + "children": [ + { + "functionName": "IsCastingShadow" + }, + { + "functionName": "SetCastingShadow" + }, + { + "functionName": "ShadowQuality" + }, + { + "functionName": "SetShadowQuality" + }, + { + "functionName": "ShadowCameraNearPlane" + }, + { + "functionName": "SetShadowCameraNearPlane" + }, + { + "functionName": "ShadowCameraFarPlane" + }, + { + "functionName": "SetShadowCameraFarPlane" + }, + { + "functionName": "ApplyShadowCameraFarPlane" + }, + { + "functionName": "ShadowBias" + }, + { + "functionName": "SetShadowBias" + }, + { + "functionName": "ApplyShadowBias" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "255;255;255", + "type": "Color", + "label": "Color", + "name": "Color" + }, + { + "value": "30", + "type": "Number", + "unit": "DegreeAngle", + "label": "Cone angle", + "group": "Cone", + "name": "ConeAngle" + }, + { + "value": "1", + "type": "Number", + "label": "Intensity", + "name": "Intensity" + }, + { + "value": "1", + "type": "Number", + "label": "Smoothing", + "description": "Percent of the spotlight cone that is attenuated due to penumbra (between 0 and 1).", + "group": "Cone", + "name": "Smoothing" + }, + { + "value": "2", + "type": "Number", + "label": "Decay", + "description": "The amount the light dims along the distance of the light.", + "name": "Decay" + }, + { + "value": "", + "type": "Resource", + "label": "Image", + "description": "Shadow casting must be enabled for the image to have any effect.", + "group": "Shadow", + "extraInformation": [ + "image" + ], + "choices": [], + "hidden": true, + "name": "Image" + }, + { + "value": "true", + "type": "Boolean", + "label": "Shadow casting", + "group": "Shadow", + "name": "IsCastingShadow" + }, + { + "value": "Medium", + "type": "Choice", + "label": "Shadow quality", + "group": "Shadow", + "choices": [ + { + "label": "Low", + "value": "Low" + }, + { + "label": "Medium", + "value": "Medium" + }, + { + "label": "High", + "value": "High" + }, + { + "label": "Very high", + "value": "VeryHigh" + } + ], + "name": "ShadowQuality" + }, + { + "value": "20", + "type": "Number", + "unit": "Pixel", + "label": "Shadow camera near plane", + "group": "Shadow", + "advanced": true, + "name": "ShadowCameraNearPlane" + }, + { + "value": "2000", + "type": "Number", + "unit": "Pixel", + "label": "Shadow camera far plane", + "group": "Shadow", + "advanced": true, + "name": "ShadowCameraFarPlane" + }, + { + "value": "2000", + "type": "Number", + "unit": "Pixel", + "label": "Cone length", + "description": "0 means no limit.", + "group": "Cone", + "name": "ConeLength" + }, + { + "value": "0", + "type": "Number", + "label": "Shadow bias", + "description": "Use this to avoid \"shadow acne\" due to depth buffer precision. Choose a value small enough like 0.001 to avoid creating distance between shadows and objects but not too small to avoid shadow glitches on low/medium quality. This value is used for high quality, and multiplied by 2 for medium quality and 4 for low quality.", + "group": "Shadow", + "advanced": true, + "name": "ShadowBias" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Color" + }, + { + "propertyName": "Intensity" + }, + { + "propertyName": "Decay" + }, + { + "folderName": "Cone", + "children": [ + { + "propertyName": "ConeAngle" + }, + { + "propertyName": "ConeLength" + }, + { + "propertyName": "Smoothing" + } + ] + }, + { + "folderName": "Shadow", + "children": [ + { + "propertyName": "IsCastingShadow" + }, + { + "propertyName": "ShadowQuality" + }, + { + "propertyName": "Image" + }, + { + "propertyName": "ShadowCameraNearPlane" + }, + { + "propertyName": "ShadowCameraFarPlane" + }, + { + "propertyName": "ShadowBias" + } + ] + } + ] + }, + "variants": [] + }, + { + "areaMaxX": 20, + "areaMaxY": 20, + "areaMaxZ": 20, + "areaMinX": -20, + "areaMinY": -20, + "areaMinZ": -20, + "defaultName": "PointLight", + "description": "Light up in all directions like a fire.", + "fullName": "3D point light", + "helpPath": "", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXRvcmNoIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTguNiA5LjZDOSAxMC4yIDkuNSAxMC43IDEwLjIgMTFIMTQuMkMxNC41IDEwLjkgMTQuNyAxMC43IDE0LjkgMTAuNUMxNS45IDkuNSAxNi4zIDggMTUuOCA2LjdMMTUuNyA2LjVDMTUuNiA2LjIgMTUuNCA2IDE1LjIgNS44QzE1LjEgNS42IDE0LjkgNS41IDE0LjggNS4zQzE0LjQgNSAxNCA0LjcgMTMuNiA0LjNDMTIuNyAzLjQgMTIuNiAyIDEzLjEgMUMxMi42IDEuMSAxMi4xIDEuNCAxMS43IDEuOEMxMC4yIDMgOS42IDUuMSAxMC4zIDdWNy4yQzEwLjMgNy4zIDEwLjIgNy40IDEwLjEgNy41QzEwIDcuNiA5LjggNy41IDkuNyA3LjRMOS42IDcuM0M5IDYuNSA4LjkgNS4zIDkuMyA0LjNDOC40IDUuMSA3LjkgNi40IDggNy43QzggOCA4LjEgOC4zIDguMiA4LjZDOC4yIDguOSA4LjQgOS4zIDguNiA5LjZNMTIuMyA4LjFDMTIuNCA3LjYgMTIuMiA3LjIgMTIuMSA2LjhDMTIgNi40IDEyIDYgMTIuMiA1LjZMMTIuNSA2LjJDMTIuOSA2LjggMTMuNiA3IDEzLjggNy44VjguMUMxMy44IDguNiAxMy42IDkuMSAxMy4zIDkuNEMxMy4xIDkuNSAxMi45IDkuNyAxMi43IDkuN0MxMi4xIDkuOSAxMS40IDkuNiAxMSA5LjJDMTEuOCA5LjIgMTIuMiA4LjYgMTIuMyA4LjFNMTUgMTJWMTRIMTRMMTMgMjJIMTFMMTAgMTRIOVYxMkgxNVoiIC8+PC9zdmc+", + "is3D": true, + "isUsingLegacyInstancesRenderer": false, + "name": "PointLight3D", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/bf9158c96f4346dabd9e8ae0ddd737809652797812eb50376bae62463ac7fc42_torch.svg", + "objects": [ + { + "assetStoreId": "", + "name": "Placeholder", + "persistentUuid": "6786eefd-3c18-4fcb-b3ae-95b07f953045", + "type": "Scene3D::Cube3DObject", + "variables": [], + "effects": [], + "behaviors": [], + "content": { + "width": 80, + "height": 80, + "depth": 80, + "enableTextureTransparency": false, + "facesOrientation": "Z", + "frontFaceResourceName": "", + "backFaceResourceName": "", + "backFaceUpThroughWhichAxisRotation": "X", + "leftFaceResourceName": "", + "rightFaceResourceName": "", + "topFaceResourceName": "", + "bottomFaceResourceName": "", + "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 + } + } + ], + "objectsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "objectName": "Placeholder" + } + ] + }, + "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": 40, + "height": 40, + "layer": "", + "name": "Placeholder", + "persistentUuid": "1310c768-04d4-4133-98d9-37d836f8a990", + "width": 40, + "x": -20, + "y": -20, + "z": -20, + "zOrder": 1, + "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": 9.06473532531222, + "windowMask": false, + "selectedLayer": "", + "gameEditorMode": "instances-editor" + }, + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onCreated", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::DefineHelperClasses" + }, + "parameters": [ + "", + "" + ] + }, + { + "type": { + "value": "Light3D::DefinePointLightHelperClasses" + }, + "parameters": [ + "", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const {", + " Light3DRenderer,", + " //@ts-ignore", + "} = gdjs.__light3DExtension;", + "const {", + " PointLightAdapter,", + " PointLightHelper,", + " //@ts-ignore", + "} = gdjs.__light3DExtension.point;", + "", + "/** @type {gdjs.CustomRuntimeObject3D} */", + "//@ts-ignore", + "const object = objects[0];", + "", + "const game = runtimeScene.getGame();", + "", + "// This is a hack that may break in future releases.", + "// Replace the group that would hold children objects by the light.", + "const layer = object.getInstanceContainer().getLayer(object.getLayer());", + "const group = object.getRenderer()._threeGroup;", + "layer.getRenderer().remove3DRendererObject(group);", + "", + "const pointLight = new THREE.PointLight(0xffffff);", + "pointLight.position.copy(group.position);", + "pointLight.rotation.copy(group.rotation);", + "pointLight.up.set(0, 0, 1);", + "pointLight.shadow.camera.up.set(0, 0, 1);", + "//@ts-ignore", + "object.__light3D = pointLight;", + "//@ts-ignore", + "object.__pointLightAdapter = new PointLightAdapter(object, pointLight);", + "", + "const pointLight3DRenderer = new Light3DRenderer(object, object._instanceContainer, object.getInstanceContainer());", + "object._renderer = pointLight3DRenderer;", + "if (game.isInGameEdition && game.isInGameEdition()) {", + " const pointLightHelper = new PointLightHelper(object);", + " pointLightHelper.rotation.order = 'ZYX';", + " pointLightHelper.add(pointLight);", + " pointLight3DRenderer._threeGroup = pointLightHelper;", + " layer.getRenderer().add3DRendererObject(pointLightHelper);", + "}", + "else {", + " pointLight3DRenderer._threeGroup = pointLight;", + " layer.getRenderer().add3DRendererObject(pointLight);", + "}", + "", + "pointLight.updateMatrixWorld(true);", + "", + "// Allow to tween the light color.", + "//@ts-ignore", + "if (!object.setColor && !object.getColor) {", + " const prototype = Object.getPrototypeOf(object);", + " prototype.setColor = function(tint) {", + " this._setColor(tint);", + " this.__pointLightAdapter.setColor(tint);", + " }", + " prototype.getColor = function() {", + " return this._getColor(eventsFunctionContext);", + " }", + "}", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::PointLight3D::UpdateFromProperties" + }, + "parameters": [ + "Object", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "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.__light3D.dispose();", + "object.get3DRendererObject().dispose();", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "Action", + "name": "onHotReloading", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::PointLight3D::UpdateFromProperties" + }, + "parameters": [ + "Object", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "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": "Light3D::PointLight3D::SetColor" + }, + "parameters": [ + "Object", + "=", + "Color", + "" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::SetIntensity" + }, + "parameters": [ + "Object", + "=", + "Intensity", + "" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::SetDecay" + }, + "parameters": [ + "Object", + "=", + "Decay", + "" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::SetRange" + }, + "parameters": [ + "Object", + "=", + "Range", + "" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::SetShadowQuality" + }, + "parameters": [ + "Object", + "=", + "ShadowQuality", + "" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::SetShadowCameraNearPlane" + }, + "parameters": [ + "Object", + "=", + "ShadowCameraNearPlane", + "" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::SetShadowCameraFarPlane" + }, + "parameters": [ + "Object", + "=", + "ShadowCameraFarPlane", + "" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::SetShadowBias" + }, + "parameters": [ + "Object", + "=", + "ShadowBias", + "" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::UpdateHelper" + }, + "parameters": [ + "Object", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsCastingShadow", + "=", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Light3D::PointLight3D::SetCastingShadow" + }, + "parameters": [ + "Object", + "no", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsCastingShadow", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "Light3D::PointLight3D::SetCastingShadow" + }, + "parameters": [ + "Object", + "yes", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "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 pointLightHelper = objects[0].get3DRendererObject();", + " pointLightHelper.update();", + "}", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the color of the light.", + "fullName": "Color", + "functionType": "ExpressionAndCondition", + "name": "Color", + "sentence": "the color", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "Color" + ] + } + ] + } + ], + "expressionType": { + "type": "color" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Color", + "name": "SetColor", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "Color", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const color = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__pointLightAdapter.setColor(color);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the intensity of the light.", + "fullName": "Intensity", + "functionType": "ExpressionAndCondition", + "name": "Intensity", + "sentence": "the intensity", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Intensity" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Intensity", + "name": "SetIntensity", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Intensity", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__pointLightAdapter.setIntensity(value);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the decay of the light. The amount the light dims along the distance of the light.", + "fullName": "Decay", + "functionType": "ExpressionAndCondition", + "name": "Decay", + "sentence": "the decay", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Decay" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Decay", + "name": "SetDecay", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Decay", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__pointLightAdapter.setDecay(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the light is casting shadows.", + "fullName": "Shadow casting", + "functionType": "Condition", + "group": "3D point light shadow configuration", + "name": "IsCastingShadow", + "sentence": "_PARAM0_ casting shadows", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "IsCastingShadow", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::SetRange" + }, + "parameters": [ + "Object", + "=", + "Range", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "Change if the light is casting shadows.", + "fullName": "Shadow casting", + "functionType": "Action", + "group": "3D point light shadow configuration", + "name": "SetCastingShadow", + "sentence": "_PARAM0_ casting shadows: _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "False", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsCastingShadow", + "False", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "Value", + "True", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "IsCastingShadow", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__pointLightAdapter.setCastingShadow(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + }, + { + "defaultValue": "yes", + "description": "Shadow casting", + "name": "Value", + "optional": true, + "type": "yesorno" + } + ], + "objectGroups": [] + }, + { + "description": "the shadow quality of the light.", + "fullName": "Shadow quality", + "functionType": "ExpressionAndCondition", + "group": "3D point light shadow configuration", + "name": "ShadowQuality", + "sentence": "the shadow quality", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnString" + }, + "parameters": [ + "ShadowQuality" + ] + } + ] + } + ], + "expressionType": { + "supplementaryInformation": "[\"Low\",\"Medium\",\"High\"]", + "type": "stringWithSelector" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ShadowQuality", + "group": "3D point light shadow configuration", + "name": "SetShadowQuality", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetStringVariable" + }, + "parameters": [ + "ShadowQuality", + "=", + "Value" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::ApplyShadowBias" + }, + "parameters": [ + "Object", + "ShadowBias", + "ShadowQuality", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__pointLightAdapter.setShadowQuality(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the shadow camera near plane of the light.", + "fullName": "Shadow camera near plane", + "functionType": "ExpressionAndCondition", + "group": "3D point light shadow configuration", + "name": "ShadowCameraNearPlane", + "sentence": "the shadow camera near plane", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ShadowCameraNearPlane" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ShadowCameraNearPlane", + "group": "3D point light shadow configuration", + "name": "SetShadowCameraNearPlane", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ShadowCameraNearPlane", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__pointLightAdapter.setShadowCameraNearPlane(value);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::PointLight3D::SetRange" + }, + "parameters": [ + "Object", + "=", + "Range", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the shadow camera far plane of the light.", + "fullName": "Shadow camera far plane", + "functionType": "ExpressionAndCondition", + "group": "3D point light shadow configuration", + "name": "ShadowCameraFarPlane", + "sentence": "the shadow camera far plane", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ShadowCameraFarPlane" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ShadowCameraFarPlane", + "group": "3D point light shadow configuration", + "name": "SetShadowCameraFarPlane", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ShadowCameraFarPlane", + "=", + "Value" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::ApplyShadowCameraFarPlane" + }, + "parameters": [ + "Object", + "min(Value, Range)", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "description": "the range of the light. 0 means no limit.", + "fullName": "Range", + "functionType": "ExpressionAndCondition", + "name": "Range", + "sentence": "the range", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "Range" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "Range", + "name": "SetRange", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "Range", + "=", + "Value" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__pointLightAdapter.setRange(value);" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Three.js reduces the far plane to match the light range.\nSo, we need to set it again or it will stay at the lowest value the range took.\nWe do the min() on our side to in order:\n- to avoid a side effect when Three.js is upgraded\n- because Three.js keeps the old value in cache and doesn't apply it back unless we set the lower value explicitly ourselves." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "Light3D::PointLight3D::ApplyShadowCameraFarPlane" + }, + "parameters": [ + "Object", + "min(Value, ShadowCameraFarPlane)", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "Apply shadow camera far plane", + "functionType": "Action", + "group": "3D point light shadow configuration", + "name": "ApplyShadowCameraFarPlane", + "private": true, + "sentence": "Apply shadow camera far plane of _PARAM0_ to _PARAM1_", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "\r", + "object.__pointLightAdapter.setShadowCameraFarPlane(value);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + } + ], + "objectGroups": [] + }, + { + "description": "the shadow bias of the light. Use this to avoid \"shadow acne\" due to depth buffer precision. Choose a value small enough like 0.001 to avoid creating distance between shadows and objects but not too small to avoid shadow glitches on low/medium quality. This value is used for high quality, and multiplied by 2 for medium quality and 4 for low quality.", + "fullName": "Shadow bias", + "functionType": "ExpressionAndCondition", + "group": "3D point light shadow configuration", + "name": "ShadowBias", + "sentence": "the shadow bias", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetReturnNumber" + }, + "parameters": [ + "ShadowBias" + ] + } + ] + } + ], + "expressionType": { + "type": "expression" + }, + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "", + "functionType": "ActionWithOperator", + "getterName": "ShadowBias", + "group": "3D point light shadow configuration", + "name": "SetShadowBias", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "ShadowBias", + "=", + "Value" + ] + }, + { + "type": { + "value": "Light3D::PointLight3D::ApplyShadowBias" + }, + "parameters": [ + "Object", + "Value", + "ShadowQuality", + "" + ] + } + ] + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + } + ], + "objectGroups": [] + }, + { + "fullName": "Apply shadow bias", + "functionType": "Action", + "group": "3D point light shadow configuration", + "name": "ApplyShadowBias", + "private": true, + "sentence": "Apply shadow bias of _PARAM0_ to _PARAM1_ for a _PARAM2_ quality", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const object = objects[0];\r", + "const value = eventsFunctionContext.getArgument(\"Value\");\r", + "const quality = eventsFunctionContext.getArgument(\"Quality\");\r", + "\r", + "object.__pointLightAdapter.setShadowBias(value, quality);\r", + "" + ], + "parameterObjects": "Object", + "useStrict": true, + "eventsSheetExpanded": false + } + ], + "parameters": [ + { + "description": "Object", + "name": "Object", + "supplementaryInformation": "Light3D::PointLight3D", + "type": "object" + }, + { + "description": "Value", + "name": "Value", + "type": "expression" + }, + { + "description": "Quality", + "name": "Quality", + "type": "string" + } + ], + "objectGroups": [] + } + ], + "eventsFunctionsFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "functionName": "onCreated" + }, + { + "functionName": "onDestroy" + }, + { + "functionName": "onHotReloading" + }, + { + "functionName": "UpdateFromProperties" + }, + { + "functionName": "UpdateHelper" + }, + { + "functionName": "Color" + }, + { + "functionName": "SetColor" + }, + { + "functionName": "Intensity" + }, + { + "functionName": "SetIntensity" + }, + { + "functionName": "Decay" + }, + { + "functionName": "SetDecay" + }, + { + "functionName": "Range" + }, + { + "functionName": "SetRange" + }, + { + "folderName": "3D point light shadow configuration", + "children": [ + { + "functionName": "IsCastingShadow" + }, + { + "functionName": "SetCastingShadow" + }, + { + "functionName": "ShadowQuality" + }, + { + "functionName": "SetShadowQuality" + }, + { + "functionName": "ShadowCameraNearPlane" + }, + { + "functionName": "SetShadowCameraNearPlane" + }, + { + "functionName": "ShadowCameraFarPlane" + }, + { + "functionName": "SetShadowCameraFarPlane" + }, + { + "functionName": "ApplyShadowCameraFarPlane" + }, + { + "functionName": "ShadowBias" + }, + { + "functionName": "SetShadowBias" + }, + { + "functionName": "ApplyShadowBias" + } + ] + } + ] + }, + "propertyDescriptors": [ + { + "value": "255;255;255", + "type": "Color", + "label": "Color", + "name": "Color" + }, + { + "value": "1", + "type": "Number", + "label": "Intensity", + "name": "Intensity" + }, + { + "value": "2", + "type": "Number", + "label": "Decay", + "description": "The amount the light dims along the distance of the light.", + "name": "Decay" + }, + { + "value": "true", + "type": "Boolean", + "label": "Shadow casting", + "group": "Shadow", + "name": "IsCastingShadow" + }, + { + "value": "Medium", + "type": "Choice", + "label": "Shadow quality", + "group": "Shadow", + "choices": [ + { + "label": "", + "value": "Low" + }, + { + "label": "", + "value": "Medium" + }, + { + "label": "", + "value": "High" + } + ], + "name": "ShadowQuality" + }, + { + "value": "20", + "type": "Number", + "unit": "Pixel", + "label": "Shadow camera near plane", + "group": "Shadow", + "advanced": true, + "name": "ShadowCameraNearPlane" + }, + { + "value": "2000", + "type": "Number", + "unit": "Pixel", + "label": "Shadow camera far plane", + "group": "Shadow", + "advanced": true, + "name": "ShadowCameraFarPlane" + }, + { + "value": "2000", + "type": "Number", + "unit": "Pixel", + "label": "Range", + "description": "0 means no limit.", + "name": "Range" + }, + { + "value": "0", + "type": "Number", + "label": "Shadow Bias", + "description": "Use this to avoid \"shadow acne\" due to depth buffer precision. Choose a value small enough like 0.001 to avoid creating distance between shadows and objects but not too small to avoid shadow glitches on low/medium quality. This value is used for high quality, and multiplied by 2 for medium quality and 4 for low quality.", + "group": "Shadow", + "advanced": true, + "name": "ShadowBias" + } + ], + "propertiesFolderStructure": { + "folderName": "__ROOT", + "children": [ + { + "propertyName": "Color" + }, + { + "propertyName": "Intensity" + }, + { + "propertyName": "Decay" + }, + { + "propertyName": "Range" + }, + { + "folderName": "Shadow", + "children": [ + { + "propertyName": "IsCastingShadow" + }, + { + "propertyName": "ShadowQuality" + }, + { + "propertyName": "ShadowCameraNearPlane" + }, + { + "propertyName": "ShadowCameraFarPlane" + }, + { + "propertyName": "ShadowBias" + } + ] + } + ] + }, + "variants": [] + } + ] + }, + { + "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": [] + } + ], + "externalLayouts": [] +} diff --git a/examples/starting-expanded-3d-house-horror/theme-slots.json b/examples/starting-expanded-3d-house-horror/theme-slots.json new file mode 100644 index 000000000..c433f0237 --- /dev/null +++ b/examples/starting-expanded-3d-house-horror/theme-slots.json @@ -0,0 +1,64 @@ +{ + "version": 1, + "objects": { + "scene:Game Scene/Monster": "character.monster", + "scene:Game Scene/PushableBox": "env.crate", + "scene:Game Scene/Ground": { + "front": "env.ground", + "left": "env.ground", + "right": "env.ground", + "top": "env.ground", + "bottom": "env.ground" + }, + "scene:Game Scene/Wall": "env.wall", + "scene:Game Scene/OuterWall": "env.wall", + "scene:Game Scene/Floor": { + "front": "env.ground.tiled", + "back": "env.wall", + "left": "env.wall", + "right": "env.wall", + "top": "env.wall", + "bottom": "env.wall" + }, + "scene:Game Scene/Stairs": "env.ground.tiled", + "scene:Game Scene/Roof": "env.wall", + "scene:Game Scene/DeadTree": "nature.tree", + "scene:Game Scene/DeadWillow": "nature.tree", + "scene:Game Scene/Rock": "nature.rock.big", + "scene:Game Scene/SmallRock": "nature.rock.small" + }, + "effects": { + "effect:Game Scene//SkyBox": "sky.dark" + }, + "ignoredObjects": [ + "scene:Game Scene/Player", + "scene:Game Scene/Window", + "scene:Game Scene/Door", + "scene:Game Scene/Key", + "scene:Game Scene/PatrolPoint", + "scene:Game Scene/ExitZone", + "scene:Game Scene/Boundary", + "object:Light3D::SpotLight3D/Placeholder", + "object:Light3D::PointLight3D/Placeholder", + "scene:Game Scene/Fence", + "scene:Game Scene/Lightpost", + "scene:Game Scene/Sofa", + "scene:Game Scene/TV", + "scene:Game Scene/Commode", + "scene:Game Scene/Nightstand", + "scene:Game Scene/OilLamp", + "scene:Game Scene/Bookshelf", + "scene:Game Scene/RockingChair", + "scene:Game Scene/KitchenCabinet", + "scene:Game Scene/Stove", + "scene:Game Scene/Cupboard", + "scene:Game Scene/Table", + "scene:Game Scene/Chair", + "scene:Game Scene/Clock", + "scene:Game Scene/Desk", + "scene:Game Scene/Rack", + "scene:Game Scene/Bed", + "scene:Game Scene/DoubleBed", + "scene:Game Scene/Mirror" + ] +} diff --git a/examples/starting-expanded-3d-house-horror/thumbnail.png b/examples/starting-expanded-3d-house-horror/thumbnail.png new file mode 100644 index 000000000..6bda647fd Binary files /dev/null and b/examples/starting-expanded-3d-house-horror/thumbnail.png differ diff --git a/examples/starting-expanded-first-person-farming/starting-expanded-first-person-farming.json b/examples/starting-expanded-first-person-farming/starting-expanded-first-person-farming.json index 7db116656..c741d7c97 100644 --- a/examples/starting-expanded-first-person-farming/starting-expanded-first-person-farming.json +++ b/examples/starting-expanded-first-person-farming/starting-expanded-first-person-farming.json @@ -5287,6 +5287,51 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Start with touch controls on touchscreens." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "SystemInfo::HasTouchScreen" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::SetChecked" + }, + "parameters": [ + "ControlsToggle", + "yes", + "" + ] + } + ] + }, { "folded": false, "type": "BuiltinCommonInstructions::Standard", @@ -5537,18 +5582,11 @@ "conditions": [ { "type": { - "value": "MouseButtonFromTextPressed" + "value": "HasAnyTouchOrMouseStarted" }, "parameters": [ - "", - "\"Left\"" + "" ] - }, - { - "type": { - "value": "BuiltinCommonInstructions::Once" - }, - "parameters": [] } ], "actions": [], @@ -5569,17 +5607,6 @@ "" ] }, - { - "type": { - "inverted": true, - "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" - }, - "parameters": [ - "JumpButton", - "MultitouchButton", - "" - ] - }, { "type": { "inverted": true, @@ -17253,7 +17280,7 @@ "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", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -17301,7 +17328,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -17549,16 +17576,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -17741,6 +17798,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -18047,6 +18129,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -18068,7 +18162,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { diff --git a/examples/starting-expanded-first-person-horror/starting-expanded-first-person-horror.json b/examples/starting-expanded-first-person-horror/starting-expanded-first-person-horror.json index 1a13c3121..7b34b604a 100644 --- a/examples/starting-expanded-first-person-horror/starting-expanded-first-person-horror.json +++ b/examples/starting-expanded-first-person-horror/starting-expanded-first-person-horror.json @@ -2796,6 +2796,51 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Start with touch controls on touchscreens." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "SystemInfo::HasTouchScreen" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::SetChecked" + }, + "parameters": [ + "ControlsToggle", + "yes", + "" + ] + } + ] + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -3002,18 +3047,11 @@ "conditions": [ { "type": { - "value": "MouseButtonFromTextPressed" + "value": "HasAnyTouchOrMouseStarted" }, "parameters": [ - "", - "\"Left\"" + "" ] - }, - { - "type": { - "value": "BuiltinCommonInstructions::Once" - }, - "parameters": [] } ], "actions": [], @@ -3032,17 +3070,6 @@ "\"Left\"", "" ] - }, - { - "type": { - "inverted": true, - "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" - }, - "parameters": [ - "JumpButton", - "MultitouchButton", - "" - ] } ], "actions": [ @@ -12589,7 +12616,7 @@ "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", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -12637,7 +12664,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -12885,16 +12912,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -13077,6 +13134,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -13383,6 +13465,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -13404,7 +13498,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { diff --git a/examples/starting-expanded-first-person-shooter-enemies/starting-expanded-first-person-shooter-enemies.json b/examples/starting-expanded-first-person-shooter-enemies/starting-expanded-first-person-shooter-enemies.json index 53d2cc9f4..17da43339 100644 --- a/examples/starting-expanded-first-person-shooter-enemies/starting-expanded-first-person-shooter-enemies.json +++ b/examples/starting-expanded-first-person-shooter-enemies/starting-expanded-first-person-shooter-enemies.json @@ -496,6 +496,30 @@ "persistentUuid": "f1651ed6-c467-4174-a82a-ab153372b2c5", "type": "string", "value": "Keyboard" + }, + { + "name": "TapTouchId", + "persistentUuid": "bc4856a7-5c77-49ba-98c1-d01e6cfb9863", + "type": "number", + "value": 0 + }, + { + "name": "TapStartX", + "persistentUuid": "30e1c901-cac6-4261-b144-023e3c30d658", + "type": "number", + "value": 0 + }, + { + "name": "TapStartY", + "persistentUuid": "f30830c0-5c21-40d3-b7ec-b21e3c9139ee", + "type": "number", + "value": 0 + }, + { + "name": "TapShot", + "persistentUuid": "059bff17-882a-4cd5-9b93-89789610eb28", + "type": "boolean", + "value": false } ], "instances": [ @@ -3302,6 +3326,125 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Keep shooting while the button is held, even when the finger drags away from it to aim." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"ShootCooldown\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "A quick tap anywhere outside the buttons also shoots. Dragging only aims." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasTouchEnded" + }, + "parameters": [ + "", + "TapTouchId" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CompareTimer" + }, + "parameters": [ + "", + "\"TapTouch\"", + "<", + "0.25" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "DistanceBetweenPositions(TapStartX, TapStartY, TouchX(TapTouchId, \"MobileControls\", 0), TouchY(TapTouchId, \"MobileControls\", 0))", + "<", + "30" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "TapShot", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TapTouchId", + "=", + "0" + ] + } + ] + } + ] + }, { "folded": false, "type": "BuiltinCommonInstructions::Standard", @@ -3369,11 +3512,57 @@ }, { "type": { - "value": "ButtonStates::ButtonFSM::IsPressed" + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "ShootButton", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressedOutside" + }, + "parameters": [ + "ShootButton", + "ButtonFSM", + "" + ] + } + ] + } + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::And" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" }, "parameters": [ - "ShootButton", - "ButtonFSM", + "ControlKind", + "=", + "\"Touch\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "TapShot", + "True", "" ] } @@ -3383,12 +3572,26 @@ }, { "type": { - "value": "BuiltinCommonInstructions::Once" + "value": "CompareTimer" }, - "parameters": [] + "parameters": [ + "", + "\"ShootCooldown\"", + ">=", + "0.2" + ] } ], "actions": [ + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"ShootCooldown\"" + ] + }, { "type": { "value": "PlaySound" @@ -3563,6 +3766,22 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "TapShot", + "False", + "" + ] + } + ] } ], "parameters": [] @@ -4134,6 +4353,51 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Start with touch controls on touchscreens." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "SystemInfo::HasTouchScreen" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::SetChecked" + }, + "parameters": [ + "ControlsToggle", + "yes", + "" + ] + } + ] + }, { "folded": false, "type": "BuiltinCommonInstructions::Standard", @@ -4385,18 +4649,11 @@ "conditions": [ { "type": { - "value": "MouseButtonFromTextPressed" + "value": "HasAnyTouchOrMouseStarted" }, "parameters": [ - "", - "\"Left\"" + "" ] - }, - { - "type": { - "value": "BuiltinCommonInstructions::Once" - }, - "parameters": [] } ], "actions": [], @@ -4416,28 +4673,6 @@ "\"Left\"", "" ] - }, - { - "type": { - "inverted": true, - "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" - }, - "parameters": [ - "JumpButton", - "MultitouchButton", - "" - ] - }, - { - "type": { - "inverted": true, - "value": "ButtonStates::ButtonFSM::IsPressed" - }, - "parameters": [ - "ShootButton", - "ButtonFSM", - "" - ] } ], "actions": [ @@ -4450,6 +4685,76 @@ "" ] } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" + }, + "parameters": [ + "JumpButton", + "MultitouchButton", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "ShootButton", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TapTouchId", + "=", + "StartedTouchOrMouseId(0)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TapStartX", + "=", + "TouchX(TapTouchId, \"MobileControls\", 0)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TapStartY", + "=", + "TouchY(TapTouchId, \"MobileControls\", 0)" + ] + }, + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"TapTouch\"" + ] + } + ] + } ] } ] @@ -14047,7 +14352,7 @@ "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", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -14095,7 +14400,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -14343,16 +14648,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -14535,6 +14870,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -14841,6 +15201,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -14862,7 +15234,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { diff --git a/examples/starting-expanded-first-person-shooter-horror/starting-expanded-first-person-shooter-horror.json b/examples/starting-expanded-first-person-shooter-horror/starting-expanded-first-person-shooter-horror.json index 789b5b35f..9741ca3ec 100644 --- a/examples/starting-expanded-first-person-shooter-horror/starting-expanded-first-person-shooter-horror.json +++ b/examples/starting-expanded-first-person-shooter-horror/starting-expanded-first-person-shooter-horror.json @@ -500,6 +500,30 @@ "persistentUuid": "f1651ed6-c467-4174-a82a-ab153372b2c5", "type": "string", "value": "Keyboard" + }, + { + "name": "TapTouchId", + "persistentUuid": "5bce4273-e625-4b6e-a223-ebb81131f092", + "type": "number", + "value": 0 + }, + { + "name": "TapStartX", + "persistentUuid": "b0c2a842-b488-4776-a3df-23ca099e4b8f", + "type": "number", + "value": 0 + }, + { + "name": "TapStartY", + "persistentUuid": "e18ff0bb-2dc3-4cdc-bcb6-53097d2d0ecf", + "type": "number", + "value": 0 + }, + { + "name": "TapShot", + "persistentUuid": "1430c50d-6636-4c7e-bcab-645a1d292dc2", + "type": "boolean", + "value": false } ], "instances": [ @@ -3699,6 +3723,125 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Keep shooting while the button is held, even when the finger drags away from it to aim." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"ShootCooldown\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "A quick tap anywhere outside the buttons also shoots. Dragging only aims." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasTouchEnded" + }, + "parameters": [ + "", + "TapTouchId" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CompareTimer" + }, + "parameters": [ + "", + "\"TapTouch\"", + "<", + "0.25" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "DistanceBetweenPositions(TapStartX, TapStartY, TouchX(TapTouchId, \"MobileControls\", 0), TouchY(TapTouchId, \"MobileControls\", 0))", + "<", + "30" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "TapShot", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TapTouchId", + "=", + "0" + ] + } + ] + } + ] + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -3765,11 +3908,57 @@ }, { "type": { - "value": "ButtonStates::ButtonFSM::IsPressed" + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "ShootButton", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressedOutside" + }, + "parameters": [ + "ShootButton", + "ButtonFSM", + "" + ] + } + ] + } + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::And" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" }, "parameters": [ - "ShootButton", - "ButtonFSM", + "ControlKind", + "=", + "\"Touch\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "TapShot", + "True", "" ] } @@ -3779,12 +3968,26 @@ }, { "type": { - "value": "BuiltinCommonInstructions::Once" + "value": "CompareTimer" }, - "parameters": [] + "parameters": [ + "", + "\"ShootCooldown\"", + ">=", + "0.2" + ] } ], "actions": [ + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"ShootCooldown\"" + ] + }, { "type": { "value": "PlaySound" @@ -3958,6 +4161,22 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "TapShot", + "False", + "" + ] + } + ] } ], "parameters": [] @@ -4581,6 +4800,51 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Start with touch controls on touchscreens." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "SystemInfo::HasTouchScreen" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::SetChecked" + }, + "parameters": [ + "ControlsToggle", + "yes", + "" + ] + } + ] + }, { "folded": false, "type": "BuiltinCommonInstructions::Standard", @@ -4832,18 +5096,11 @@ "conditions": [ { "type": { - "value": "MouseButtonFromTextPressed" + "value": "HasAnyTouchOrMouseStarted" }, "parameters": [ - "", - "\"Left\"" + "" ] - }, - { - "type": { - "value": "BuiltinCommonInstructions::Once" - }, - "parameters": [] } ], "actions": [], @@ -4863,28 +5120,6 @@ "\"Left\"", "" ] - }, - { - "type": { - "inverted": true, - "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" - }, - "parameters": [ - "JumpButton", - "MultitouchButton", - "" - ] - }, - { - "type": { - "inverted": true, - "value": "ButtonStates::ButtonFSM::IsPressed" - }, - "parameters": [ - "ShootButton", - "ButtonFSM", - "" - ] } ], "actions": [ @@ -4897,6 +5132,76 @@ "" ] } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" + }, + "parameters": [ + "JumpButton", + "MultitouchButton", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "ShootButton", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TapTouchId", + "=", + "StartedTouchOrMouseId(0)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TapStartX", + "=", + "TouchX(TapTouchId, \"MobileControls\", 0)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TapStartY", + "=", + "TouchY(TapTouchId, \"MobileControls\", 0)" + ] + }, + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"TapTouch\"" + ] + } + ] + } ] } ] @@ -14517,7 +14822,7 @@ "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", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -14565,7 +14870,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -14813,16 +15118,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -15005,6 +15340,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -15311,6 +15671,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -15332,7 +15704,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { 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 index 06c57aa21..00a46487a 100644 --- 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 @@ -4628,6 +4628,51 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Start with touch controls on touchscreens." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "SystemInfo::HasTouchScreen" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::SetChecked" + }, + "parameters": [ + "ControlsToggle", + "yes", + "" + ] + } + ] + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -4870,18 +4915,11 @@ "conditions": [ { "type": { - "value": "MouseButtonFromTextPressed" + "value": "HasAnyTouchOrMouseStarted" }, "parameters": [ - "", - "\"Left\"" + "" ] - }, - { - "type": { - "value": "BuiltinCommonInstructions::Once" - }, - "parameters": [] } ], "actions": [], @@ -4901,17 +4939,6 @@ "" ] }, - { - "type": { - "inverted": true, - "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" - }, - "parameters": [ - "JumpButton", - "MultitouchButton", - "" - ] - }, { "type": { "inverted": true, @@ -16174,7 +16201,7 @@ "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", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -16222,7 +16249,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -16470,16 +16497,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -16662,6 +16719,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -16968,6 +17050,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -16989,7 +17083,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { diff --git a/examples/starting-first-person-farming/starting-first-person-farming.json b/examples/starting-first-person-farming/starting-first-person-farming.json index a4cf37469..6bd6a123f 100644 --- a/examples/starting-first-person-farming/starting-first-person-farming.json +++ b/examples/starting-first-person-farming/starting-first-person-farming.json @@ -397,6 +397,9 @@ "objects": [ { "name": "DirtPlot" + }, + { + "name": "Terrain" } ] }, @@ -3610,6 +3613,51 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Start with touch controls on touchscreens." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "SystemInfo::HasTouchScreen" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::SetChecked" + }, + "parameters": [ + "ControlsToggle", + "yes", + "" + ] + } + ] + }, { "folded": true, "type": "BuiltinCommonInstructions::Standard", @@ -3860,18 +3908,11 @@ "conditions": [ { "type": { - "value": "MouseButtonFromTextPressed" + "value": "HasAnyTouchOrMouseStarted" }, "parameters": [ - "", - "\"Left\"" + "" ] - }, - { - "type": { - "value": "BuiltinCommonInstructions::Once" - }, - "parameters": [] } ], "actions": [], @@ -3892,17 +3933,6 @@ "" ] }, - { - "type": { - "inverted": true, - "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" - }, - "parameters": [ - "JumpButton", - "MultitouchButton", - "" - ] - }, { "type": { "inverted": true, @@ -6511,6 +6541,51 @@ "// the test results.", "await harness.takeScreenshot('the inventory holding the seed that was harvested');" ] + }, + { + "description": "Holding a carrot seed and looking down at the ground: the carrot seed is shown on the terrain, where the ray from the camera hits it.", + "name": "A held item is shown on the ground where the player looks", + "type": "gameplay", + "source": [ + "// Held items are shown, then placed, where the ray from the center of the", + "// camera hits the ground (the \"RaycastSurface\" group, which the terrain is part of).", + "await harness.goToScene('Game Scene');", + "harness.setTimeoutMs(60000);", + "await harness.stepFrames(6);", + "", + "// Arrange: a carrot seed in the selected inventory slot, as if it was harvested.", + "// The dirt plots are taken out of the level, so that the ray can only hit the terrain.", + "for (const object of harness.getObjects('DirtPlot')) harness.removeObject(object.id);", + "harness.setSceneVariable('Inventory[0].Name', 'Seed_Carrot');", + "harness.setSceneVariable('Inventory[0].Quantity', 1);", + "harness.setSceneVariable('SelectedSlot', 0);", + "harness.setSceneVariable('UpdateInventoryDisplay', true);", + "await harness.stepFrames(2);", + "// The level can have other carrot seeds lying around: the held one is the one marked as held.", + "const getHeldItem = () =>", + " harness.getObjects('Seed_Carrot').find((object) => {", + " const held = harness.getObjectVariable(object.id, 'Held');", + " return !!held && held.value === true;", + " });", + "harness.assert(!!getHeldItem(), 'The carrot seed of the selected slot is held.');", + "", + "// Look down at the ground in front of the player.", + "harness.getRuntimeObject(harness.getObjects('Player')[0].id).setRotationY(40);", + "await harness.stepFrames(2);", + "const heldItem = getHeldItem();", + "const terrain = harness.getRuntimeObject(harness.getObjects('Terrain')[0].id).__terrain3D;", + "const groundZ = terrain.getHeightAt(heldItem.x, heldItem.y);", + "console.log(", + " 'heldItem=' + Math.round(heldItem.x) + ';' + Math.round(heldItem.y) + ';' + heldItem.z.toFixed(1) +", + " ' hidden=' + heldItem.hidden + ' groundZ=' + groundZ.toFixed(1)", + ");", + "harness.assert(!heldItem.hidden, 'The held carrot seed is shown where the player looks at the ground.');", + "harness.assert(", + " Math.abs(heldItem.z - groundZ) < 3,", + " 'The held carrot seed is on the terrain (at Z ' + heldItem.z.toFixed(1) + ', the ground being at ' + groundZ.toFixed(1) + ').'", + ");", + "await harness.takeScreenshot('the held carrot seed shown on the ground');" + ] } ], "eventsFunctionsExtensions": [ @@ -15512,7 +15587,6 @@ { "author": "PANDAKO-GitHub", "category": "Input", - "dimension": "", "extensionNamespace": "", "fullName": "Mouse Pointer Lock", "gdevelopVersion": ">=5.5.222", @@ -15520,8 +15594,8 @@ "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMTksMTkgMjQsMTkgMjQsMjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSI2LDIzIDExLDIzIDExLDE4ICIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTI0LDE5LjRjLTAuNywwLjgtMS40LDEuNi0yLjIsMi40Yy03LDctMTUuMywxMC4yLTE4LjUsN3MtMC4xLTExLjUsNy0xOC41czE1LjMtMTAuMiwxOC41LTcNCgljMS40LDEuNCwxLjYsMy42LDAuOCw2LjMiLz4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xMSwyMi41Yy0wLjMtMC4yLTAuNS0wLjUtMC44LTAuOGMtNy03LTEwLjItMTUuMy03LTE4LjVzMTEuNS0wLjEsMTguNSw3czEwLjIsMTUuMyw3LDE4LjUNCgljLTEuNywxLjctNC44LDEuNi04LjQsMC4xIi8+DQo8L3N2Zz4NCg==", "name": "MousePointerLock", "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Virtual Reality/Virtual Reality_360_rotate_vr_movement.svg", - "shortDescription": "This behavior removes the limit on the distance the mouse can move and hides the cursor.", - "version": "0.3.0", + "shortDescription": "Lock and hide mouse pointer for unlimited movement (e.g., FPS mouse look).", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -15569,7 +15643,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -15817,16 +15891,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -15973,7 +16077,7 @@ "functionType": "Condition", "name": "isEmulatingPointerLock", "private": true, - "sentence": "The mouse pointer is lock is emulated", + "sentence": "The mouse pointer lock is emulated", "events": [ { "type": "BuiltinCommonInstructions::Standard", @@ -16009,6 +16113,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -16290,68 +16419,12 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "onFirstSceneLoaded" - }, - { - "functionName": "onScenePostEvents" - }, - { - "functionName": "RequestPointerLock" - }, - { - "functionName": "ExitPointerLock" - }, - { - "functionName": "isPointerLocked" - }, - { - "functionName": "isPointerActuallyLocked" - }, - { - "functionName": "isEmulatingPointerLock" - }, - { - "functionName": "IsMoving" - }, - { - "functionName": "MovementX" - }, - { - "functionName": "SetMovementX" - }, - { - "functionName": "MovementY" - }, - { - "functionName": "SetMovementY" - }, - { - "functionName": "TouchX" - }, - { - "functionName": "TouchY" - }, - { - "functionName": "TouchSpeedFactor" - }, - { - "functionName": "SetTouchSpeedFactor" - } - ] - }, "eventsBasedBehaviors": [ { "description": "Control camera rotations with a mouse.", "fullName": "First person camera mouse mapper", - "helpPath": "", - "iconUrl": "", "name": "FirstPersonPointerMapper", "objectType": "", - "previewIconUrl": "", "eventsFunctions": [ { "fullName": "", @@ -16371,6 +16444,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -16392,7 +16477,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { @@ -16626,7 +16711,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "HorizontalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetHorizontalSpeed", "sentence": "", "events": [ @@ -16707,7 +16791,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalSpeed", "sentence": "", "events": [ @@ -16788,7 +16871,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMin", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMin", "sentence": "", "events": [ @@ -16869,7 +16951,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMax", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMax", "sentence": "", "events": [ @@ -16950,7 +17031,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "OffsetZ", - "group": "First person camera mouse mapper configuration", "name": "SetOffsetZ", "sentence": "", "events": [ @@ -16987,57 +17067,6 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "doStepPreEvents" - }, - { - "folderName": "Layers and cameras", - "children": [ - { - "functionName": "LookFromObjectEyes" - } - ] - }, - { - "folderName": "First person camera mouse mapper configuration", - "children": [ - { - "functionName": "HorizontalSpeed" - }, - { - "functionName": "SetHorizontalSpeed" - }, - { - "functionName": "VerticalSpeed" - }, - { - "functionName": "SetVerticalSpeed" - }, - { - "functionName": "VerticalAngleMin" - }, - { - "functionName": "SetVerticalAngleMin" - }, - { - "functionName": "VerticalAngleMax" - }, - { - "functionName": "SetVerticalAngleMax" - }, - { - "functionName": "OffsetZ" - }, - { - "functionName": "SetOffsetZ" - } - ] - } - ] - }, "propertyDescriptors": [ { "value": "", @@ -17091,32 +17120,7 @@ "name": "ShouldLock" } ], - "propertiesFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "propertyName": "Object3D" - }, - { - "propertyName": "HorizontalSpeed" - }, - { - "propertyName": "VerticalSpeed" - }, - { - "propertyName": "VerticalAngleMin" - }, - { - "propertyName": "VerticalAngleMax" - }, - { - "propertyName": "OffsetZ" - }, - { - "propertyName": "ShouldLock" - } - ] - } + "sharedPropertyDescriptors": [] } ], "eventsBasedObjects": [] @@ -48368,7 +48372,7 @@ "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", + "version": "0.2.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.", "", @@ -49468,8 +49472,17 @@ "vTerrainLocalXY = position.xy;", "vTerrainLocalZ = position.z;", "vTerrainWorldXY = (modelMatrix * vec4(transformed, 1.0)).xy * terrainWorldScale;", - "vTerrainUp = normalize(transpose(inverse(mat3(modelMatrix))) * objectNormal).z;", + "// The world normal, from the camera space normal (computed by Three.js with a", + "// matrix inverted on the CPU), instead of inverting a matrix for each vertex.", + "vTerrainUp = inverseTransformDirection(transformedNormal, viewMatrix).z;", "`;", + "/** @param {(layer: number) => string} getLayerCode */", + "const forEachLayerCode = (getLayerCode) => [0, 1, 2, 3].map(getLayerCode).join('\\n');", + "", + "// Noise is read from a texture of random values, repeated every", + "// NOISE_MAP_SIZE units of noise: 1 texture read instead of 16 hashes.", + "const NOISE_MAP_SIZE = 256;", + "", "const fragmentShaderDeclarations = `", "uniform sampler2D terrainSplatMap;", "uniform vec2 terrainSplatUvScaleAndOffset;", @@ -49478,11 +49491,11 @@ "uniform sampler2D terrainLayerMap1;", "uniform sampler2D terrainLayerMap2;", "uniform sampler2D terrainLayerMap3;", + "uniform vec3 terrainLayerAverageColors[4];", "uniform sampler2D terrainLayerNormalMap0;", "uniform sampler2D terrainLayerNormalMap1;", "uniform sampler2D terrainLayerNormalMap2;", "uniform sampler2D terrainLayerNormalMap3;", - "uniform float terrainHasNormalMaps;", "uniform float terrainNormalMapStrength;", "uniform float terrainTextureSize;", "uniform float terrainHeightBlending;", @@ -49491,15 +49504,13 @@ "uniform vec2 terrainCliffCosines;", "uniform vec4 terrainBrush;", "uniform sampler2D terrainColorMap;", + "uniform sampler2D terrainNoiseMap;", "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. */", @@ -49511,15 +49522,16 @@ " copyBump.xy = transpose(terrainCopyRotation) * copyBump.xy;", " return mix(bump, copyBump, patches);", "}", - "float terrainNoise(vec2 p) {", + "/**", + " * 4 independent value noises (one by channel), from 0 to 1, smoothly varying", + " * between random values at integer positions.", + " */", + "vec4 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", - " );", + " vec2 f = p - i;", + " // The read position is moved so that the linear interpolation of the", + " // texture between 2 random values becomes a smooth one.", + " return texture2D(terrainNoiseMap, (i + f * f * (3.0 - 2.0 * f) + 0.5) / ${NOISE_MAP_SIZE}.0);", "}", "`;", "// Holes are samples without any painted layer: the interpolated sum of the", @@ -49537,15 +49549,11 @@ "varying vec2 vTerrainLocalXY;", "`;", "", + "// Layers without texture, normal map, height blending or texture variation", + "// are compiled out (see TerrainRenderer.updateMaterial).", "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)", - ");", + "vec4 terrainJitter = terrainNoise(vTerrainWorldXY / 24.0);", "// 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;", @@ -49556,48 +49564,61 @@ "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;", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "vec3 terrainTexture${layer} = texture2D(terrainLayerMap${layer}, terrainUv).rgb;", + "#else", + "vec3 terrainTexture${layer} = vec3(1.0);", + "#endif`", + ")}", "", "// 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);", - "}", + "#ifdef TERRAIN_TEXTURE_VARIATION", + "// 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).x);", + "terrainUv2 = terrainCopyRotation * terrainUv + vec2(0.31, 0.73);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "terrainTexture${layer} = mix(terrainTexture${layer}, texture2D(terrainLayerMap${layer}, terrainUv2).rgb, terrainPatches);", + "#endif`", + ")}", + "terrainColorVariation += terrainTextureVariation * 0.3 *", + " (terrainNoise(terrainUv * 0.11).y + 0.5 * terrainNoise(terrainUv * 0.37 + 3.1).z - 0.75);", + "#endif", "", "// 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);", - "}", + "#ifdef TERRAIN_HEIGHT_BLENDING", + "// Averages are computed once on the CPU, or read from the smallest mipmap", + "// when the image can't be read by the CPU.", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MIPMAP_AVERAGE_${layer}", + "vec3 terrainAverage${layer} = textureLod(terrainLayerMap${layer}, vec2(0.5), 16.0).rgb;", + "#else", + "vec3 terrainAverage${layer} = terrainLayerAverageColors[${layer}];", + "#endif`", + ")}", + "const vec3 terrainLuminance = vec3(0.299, 0.587, 0.114);", + "vec4 terrainHeights = vec4(", + " dot(terrainTexture0 - terrainAverage0, terrainLuminance),", + " dot(terrainTexture1 - terrainAverage1, terrainLuminance),", + " dot(terrainTexture2 - terrainAverage2, terrainLuminance),", + " dot(terrainTexture3 - terrainAverage3, 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);", + "#endif", "", "vec3 terrainColor = terrainColorVariation * (", " terrainWeights.x * terrainLayerColors[0] * terrainTexture0 +", @@ -49629,26 +49650,30 @@ "`;", "", "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;", + "#ifdef TERRAIN_NORMAL_MAPS", + "// Layers without normal map are flat.", + "vec3 terrainMapNormal = vec3(0.0);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_NORMAL_MAP_${layer}", + "terrainMapNormal += terrainWeights[${layer}] * terrainSampleNormal(terrainLayerNormalMap${layer}, terrainUv, terrainUv2, terrainPatches);", + "#else", + "terrainMapNormal.z += terrainWeights[${layer}];", + "#endif`", + ")}", + "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);", - " // 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);", "}", + "terrainTangent = normalize(terrainTangent);", + "vec3 terrainBitangent = cross(normal, terrainTangent);", + "normal = normalize(mat3(terrainTangent, terrainBitangent, normal) * terrainMapNormal);", + "#endif", "`;", "", "let whiteTexture = null;", @@ -49669,7 +49694,30 @@ " return flatNormalTexture;", "};", "", + "let noiseTexture = null;", + "/** Random values, in 4 independent channels, read by `terrainNoise` in the shader. */", + "const getNoiseTexture = () => {", + " if (!noiseTexture) {", + " const random = createRandom(1);", + " const bytes = new Uint8Array(NOISE_MAP_SIZE * NOISE_MAP_SIZE * 4);", + " for (let index = 0; index < bytes.length; index++) {", + " bytes[index] = Math.floor(random() * 256);", + " }", + " noiseTexture = new THREE.DataTexture(bytes, NOISE_MAP_SIZE, NOISE_MAP_SIZE);", + " noiseTexture.wrapS = THREE.RepeatWrapping;", + " noiseTexture.wrapT = THREE.RepeatWrapping;", + " // No mipmaps: the shader moves the read position between samples.", + " noiseTexture.magFilter = THREE.LinearFilter;", + " noiseTexture.minFilter = THREE.LinearFilter;", + " noiseTexture.needsUpdate = true;", + " }", + " return noiseTexture;", + "};", + "", + "const DEFAULT_ANISOTROPY = 4;", + "/** @type {WeakMap>} */", "const layerTextures = new WeakMap();", + "/** @type {WeakMap>} */", "const layerNormalMaps = new WeakMap();", "/**", " * Textures of the image manager have no mipmaps: they would shimmer when", @@ -49677,12 +49725,18 @@ " * @param {gdjs.RuntimeGame} game", " * @param {string} resourceName", " * @param {boolean} isNormalMap Normal maps are directions, not colors.", + " * @param {number} anisotropy", " */", - "const getLayerTexture = (game, resourceName, isNormalMap) => {", + "const getLayerTexture = (game, resourceName, isNormalMap, anisotropy) => {", " if (!resourceName) return isNormalMap ? getFlatNormalTexture() : getWhiteTexture();", " const imageTexture = game.getImageManager().getThreeTexture(resourceName);", - " const textures = isNormalMap ? layerNormalMaps : layerTextures;", - " let texture = textures.get(imageTexture);", + " const texturesByImage = isNormalMap ? layerNormalMaps : layerTextures;", + " let texturesByAnisotropy = texturesByImage.get(imageTexture);", + " if (!texturesByAnisotropy) {", + " texturesByAnisotropy = new Map();", + " texturesByImage.set(imageTexture, texturesByAnisotropy);", + " }", + " let texture = texturesByAnisotropy.get(anisotropy);", " if (!texture) {", " texture = imageTexture.clone();", " if (isNormalMap) texture.colorSpace = THREE.NoColorSpace;", @@ -49690,13 +49744,71 @@ " texture.wrapT = THREE.RepeatWrapping;", " texture.minFilter = THREE.LinearMipmapLinearFilter;", " texture.generateMipmaps = true;", - " texture.anisotropy = 4;", + " texture.anisotropy = anisotropy;", " texture.needsUpdate = true;", - " textures.set(imageTexture, texture);", + " texturesByAnisotropy.set(anisotropy, texture);", " }", " return texture;", "};", "", + "/** @type {Float32Array | null} */", + "let srgbToLinearValues = null;", + "/** @type {WeakMap} */", + "const linearAverageColors = new WeakMap();", + "/**", + " * @param {THREE.Texture} texture", + " * @returns {THREE.Vector3 | null} The average color of the texture, in the", + " * linear color space (like colors read by shaders), or null if the image", + " * can't be read: not loaded yet, or from another domain without CORS.", + " */", + "const getLinearAverageColor = (texture) => {", + " const image = texture.image;", + " if (!image) return null;", + " const cachedColor = linearAverageColors.get(image);", + " if (cachedColor) return cachedColor;", + " 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 null;", + " const size = 64;", + " let pixels;", + " try {", + " const canvas = document.createElement('canvas');", + " canvas.width = size;", + " canvas.height = size;", + " const context = canvas.getContext('2d');", + " // Downscaled with filtering, so that all pixels are counted.", + " context.imageSmoothingEnabled = true;", + " context.imageSmoothingQuality = 'high';", + " context.drawImage(image, 0, 0, size, size);", + " pixels = context.getImageData(0, 0, size, size).data;", + " } catch (error) {", + " return null;", + " }", + " if (!srgbToLinearValues) {", + " srgbToLinearValues = new Float32Array(256);", + " for (let value = 0; value < 256; value++) {", + " const srgb = value / 255;", + " srgbToLinearValues[value] = srgb <= 0.04045 ? srgb / 12.92 : Math.pow((srgb + 0.055) / 1.055, 2.4);", + " }", + " }", + " const isSrgb = texture.colorSpace === THREE.SRGBColorSpace;", + " let red = 0;", + " let green = 0;", + " let blue = 0;", + " for (let index = 0; index < pixels.length; index += 4) {", + " red += isSrgb ? srgbToLinearValues[pixels[index]] : pixels[index] / 255;", + " green += isSrgb ? srgbToLinearValues[pixels[index + 1]] : pixels[index + 1] / 255;", + " blue += isSrgb ? srgbToLinearValues[pixels[index + 2]] : pixels[index + 2] / 255;", + " }", + " const count = size * size;", + " const color = new THREE.Vector3(red / count, green / count, blue / count);", + " linearAverageColors.set(image, color);", + " return color;", + "};", + "", "/**", " * A texture of 4 bytes per sample of a grid of `size * size` samples (layer", " * weights, tint and wetness...), interpolated between samples.", @@ -49755,11 +49867,14 @@ " terrainLayerMap1: { value: getWhiteTexture() },", " terrainLayerMap2: { value: getWhiteTexture() },", " terrainLayerMap3: { value: getWhiteTexture() },", + " // Used by the height blending, in the linear color space.", + " terrainLayerAverageColors: {", + " value: [new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1)],", + " },", " terrainLayerNormalMap0: { value: getFlatNormalTexture() },", " terrainLayerNormalMap1: { value: getFlatNormalTexture() },", " terrainLayerNormalMap2: { value: getFlatNormalTexture() },", " terrainLayerNormalMap3: { value: getFlatNormalTexture() },", - " terrainHasNormalMaps: { value: 0 },", " terrainNormalMapStrength: { value: 1 },", " terrainTextureSize: { value: 256 },", " terrainHeightBlending: { value: 0 },", @@ -49768,6 +49883,7 @@ " terrainCliffCosines: { value: new THREE.Vector2() },", " terrainBrush: { value: new THREE.Vector4() },", " terrainColorMap: { value: getWhiteTexture() },", + " terrainNoiseMap: { value: getNoiseTexture() },", " // 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.", @@ -49787,8 +49903,12 @@ " ['#include ', '#include \\n' + fragmentShaderNormalCode],", " ], 'Terrain3D');", " };", - " this.material.customProgramCacheKey = () => (this.hasHoles ? 'Terrain3DWithHoles' : 'Terrain3D');", " this.hasHoles = false;", + " /** The defines of the features used by the terrain (see `updateMaterial`), without holes. */", + " this.featureDefines = [];", + " /** All the defines of the shader, separated by semicolons. */", + " this.shaderKey = '';", + " this.material.customProgramCacheKey = () => 'Terrain3D;' + this.shaderKey;", " // Without them, holes would cast shadows.", " this.depthMaterial = this._createHoleDepthMaterial(new THREE.MeshDepthMaterial());", " this.distanceMaterial = this._createHoleDepthMaterial(new THREE.MeshDistanceMaterial());", @@ -49825,11 +49945,28 @@ " setHasHoles(hasHoles) {", " if (hasHoles === this.hasHoles) return;", " this.hasHoles = hasHoles;", + " this._updateShaderDefines();", + " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", + " }", + "", + " /**", + " * Compile the shader only with the features used by the terrain. It is", + " * compiled again only when they change: properties only change in the", + " * editor, holes can be made by actions.", + " */", + " _updateShaderDefines() {", + " const defines = this.hasHoles ? [...this.featureDefines, 'TERRAIN_HOLES'] : this.featureDefines;", + " const shaderKey = defines.join(';');", + " if (shaderKey === this.shaderKey) return;", " // The material has its own defines (STANDARD).", - " if (hasHoles) this.material.defines.TERRAIN_HOLES = '';", - " else delete this.material.defines.TERRAIN_HOLES;", + " for (const name of this.shaderKey ? this.shaderKey.split(';') : []) {", + " delete this.material.defines[name];", + " }", + " for (const name of defines) {", + " this.material.defines[name] = '';", + " }", + " this.shaderKey = shaderKey;", " this.material.needsUpdate = true;", - " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", " }", "", " /** @param {THREE.Mesh} mesh */", @@ -50061,16 +50198,38 @@ " object._getLayer3NormalMap(),", " object._getLayer4NormalMap(),", " ];", + " const anisotropy = Math.max(parseInt(object._getAnisotropy(), 10) || DEFAULT_ANISOTROPY, 1);", + " const heightBlending = clamp01(object._getHeightBlending());", + " const textureVariation = clamp01(object._getTextureVariation());", + " const featureDefines = [];", " 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);", + " const layerTexture = getLayerTexture(game, textures[layer], false, anisotropy);", + " this.uniforms['terrainLayerMap' + layer].value = layerTexture;", + " this.uniforms['terrainLayerNormalMap' + layer].value = getLayerTexture(game, normalMaps[layer], true, anisotropy);", + " const averageColor = this.uniforms.terrainLayerAverageColors.value[layer];", + " if (textures[layer]) {", + " featureDefines.push('TERRAIN_LAYER_MAP_' + layer);", + " const linearAverageColor = heightBlending > 0 ? getLinearAverageColor(layerTexture) : null;", + " if (linearAverageColor) {", + " averageColor.copy(linearAverageColor);", + " } else if (heightBlending > 0) {", + " featureDefines.push('TERRAIN_LAYER_MIPMAP_AVERAGE_' + layer);", + " }", + " } else {", + " averageColor.set(1, 1, 1);", + " }", + " if (normalMaps[layer]) featureDefines.push('TERRAIN_LAYER_NORMAL_MAP_' + layer);", " }", - " this.uniforms.terrainHasNormalMaps.value = normalMaps.some((normalMap) => !!normalMap) ? 1 : 0;", + " if (normalMaps.some((normalMap) => !!normalMap)) featureDefines.push('TERRAIN_NORMAL_MAPS');", + " if (heightBlending > 0) featureDefines.push('TERRAIN_HEIGHT_BLENDING');", + " if (textureVariation > 0) featureDefines.push('TERRAIN_TEXTURE_VARIATION');", + " this.featureDefines = featureDefines;", + " this._updateShaderDefines();", " 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());", + " this.uniforms.terrainHeightBlending.value = heightBlending;", + " this.uniforms.terrainTextureVariation.value = textureVariation;", "", " const cliffLayer = parseInt(object._getCliffLayer(), 10);", " const cliffAngle = gdjs.toRad(object._getCliffAngle());", @@ -50988,40 +51147,21 @@ " return typeof scene.getRenderer3DWorldScale === 'function' ? scene.getRenderer3DWorldScale() : 1;", "};", "", + "/** @type {WeakMap} */", "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).", + " * @returns {THREE.Color} The average color of the texture (white when it can't be read).", " */", "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 linearAverageColor = getLinearAverageColor(texture);", + " if (!linearAverageColor) return white || (white = new THREE.Color(1, 1, 1));", + " let color = averageColors.get(linearAverageColor);", + " if (!color) {", + " color = new THREE.Color(linearAverageColor.x, linearAverageColor.y, linearAverageColor.z);", + " averageColors.set(linearAverageColor, color);", " }", - " 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;", "};", "", @@ -54635,6 +54775,37 @@ "group": "Rendering", "name": "ReceiveShadow" }, + { + "value": "4", + "type": "Choice", + "label": "Anisotropic filtering", + "description": "Keeps layer textures sharp where the ground is seen at a low angle (like from a car). Lower is faster on slow graphics cards, but blurs the ground in the distance.", + "group": "Rendering", + "choices": [ + { + "label": "None (fastest)", + "value": "1" + }, + { + "label": "2x", + "value": "2" + }, + { + "label": "4x", + "value": "4" + }, + { + "label": "8x", + "value": "8" + }, + { + "label": "16x (sharpest)", + "value": "16" + } + ], + "advanced": true, + "name": "Anisotropy" + }, { "value": "", "type": "String", @@ -54748,6 +54919,9 @@ }, { "propertyName": "ReceiveShadow" + }, + { + "propertyName": "Anisotropy" } ] }, diff --git a/examples/starting-first-person-farming/theme-slots.json b/examples/starting-first-person-farming/theme-slots.json index 647bcb506..8c63c5b6e 100644 --- a/examples/starting-first-person-farming/theme-slots.json +++ b/examples/starting-first-person-farming/theme-slots.json @@ -1,8 +1,11 @@ { "version": 1, "objects": { - "scene:Game Scene/Ground": { - "front": "env.ground" + "scene:Game Scene/Terrain": { + "Layer1Texture": { + "slot": "terrain.ground", + "normalMap": "Layer1NormalMap" + } }, "scene:Game Scene/DirtPlot": { "front": "env.dirt", @@ -29,6 +32,9 @@ "ignoredObjects": [ "scene:Game Scene/Player", "scene:Game Scene/Seed_Beet", - "scene:Game Scene/Seed_Carrot" + "scene:Game Scene/Seed_Carrot", + "object:Terrain3D::Terrain3D/AreaPlaceholder", + "object:Terrain3D::TerrainGrass/AreaPlaceholder", + "object:Terrain3D::ScatteredModels/AreaPlaceholder" ] } diff --git a/examples/starting-first-person-horror/starting-first-person-horror.json b/examples/starting-first-person-horror/starting-first-person-horror.json index 4fd6628f3..2bdbd0267 100644 --- a/examples/starting-first-person-horror/starting-first-person-horror.json +++ b/examples/starting-first-person-horror/starting-first-person-horror.json @@ -2097,6 +2097,51 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Start with touch controls on touchscreens." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "SystemInfo::HasTouchScreen" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::SetChecked" + }, + "parameters": [ + "ControlsToggle", + "yes", + "" + ] + } + ] + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -2303,18 +2348,11 @@ "conditions": [ { "type": { - "value": "MouseButtonFromTextPressed" + "value": "HasAnyTouchOrMouseStarted" }, "parameters": [ - "", - "\"Left\"" + "" ] - }, - { - "type": { - "value": "BuiltinCommonInstructions::Once" - }, - "parameters": [] } ], "actions": [], @@ -2333,17 +2371,6 @@ "\"Left\"", "" ] - }, - { - "type": { - "inverted": true, - "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" - }, - "parameters": [ - "JumpButton", - "MultitouchButton", - "" - ] } ], "actions": [ @@ -11845,7 +11872,6 @@ { "author": "PANDAKO-GitHub", "category": "Input", - "dimension": "", "extensionNamespace": "", "fullName": "Mouse Pointer Lock", "gdevelopVersion": ">=5.5.222", @@ -11853,8 +11879,8 @@ "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMTksMTkgMjQsMTkgMjQsMjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSI2LDIzIDExLDIzIDExLDE4ICIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTI0LDE5LjRjLTAuNywwLjgtMS40LDEuNi0yLjIsMi40Yy03LDctMTUuMywxMC4yLTE4LjUsN3MtMC4xLTExLjUsNy0xOC41czE1LjMtMTAuMiwxOC41LTcNCgljMS40LDEuNCwxLjYsMy42LDAuOCw2LjMiLz4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xMSwyMi41Yy0wLjMtMC4yLTAuNS0wLjUtMC44LTAuOGMtNy03LTEwLjItMTUuMy03LTE4LjVzMTEuNS0wLjEsMTguNSw3czEwLjIsMTUuMyw3LDE4LjUNCgljLTEuNywxLjctNC44LDEuNi04LjQsMC4xIi8+DQo8L3N2Zz4NCg==", "name": "MousePointerLock", "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Virtual Reality/Virtual Reality_360_rotate_vr_movement.svg", - "shortDescription": "This behavior removes the limit on the distance the mouse can move and hides the cursor.", - "version": "0.3.0", + "shortDescription": "Lock and hide mouse pointer for unlimited movement (e.g., FPS mouse look).", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -11902,7 +11928,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -12150,16 +12176,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -12306,7 +12362,7 @@ "functionType": "Condition", "name": "isEmulatingPointerLock", "private": true, - "sentence": "The mouse pointer is lock is emulated", + "sentence": "The mouse pointer lock is emulated", "events": [ { "type": "BuiltinCommonInstructions::Standard", @@ -12342,6 +12398,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -12623,68 +12704,12 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "onFirstSceneLoaded" - }, - { - "functionName": "onScenePostEvents" - }, - { - "functionName": "RequestPointerLock" - }, - { - "functionName": "ExitPointerLock" - }, - { - "functionName": "isPointerLocked" - }, - { - "functionName": "isPointerActuallyLocked" - }, - { - "functionName": "isEmulatingPointerLock" - }, - { - "functionName": "IsMoving" - }, - { - "functionName": "MovementX" - }, - { - "functionName": "SetMovementX" - }, - { - "functionName": "MovementY" - }, - { - "functionName": "SetMovementY" - }, - { - "functionName": "TouchX" - }, - { - "functionName": "TouchY" - }, - { - "functionName": "TouchSpeedFactor" - }, - { - "functionName": "SetTouchSpeedFactor" - } - ] - }, "eventsBasedBehaviors": [ { "description": "Control camera rotations with a mouse.", "fullName": "First person camera mouse mapper", - "helpPath": "", - "iconUrl": "", "name": "FirstPersonPointerMapper", "objectType": "", - "previewIconUrl": "", "eventsFunctions": [ { "fullName": "", @@ -12704,6 +12729,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -12725,7 +12762,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { @@ -12959,7 +12996,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "HorizontalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetHorizontalSpeed", "sentence": "", "events": [ @@ -13040,7 +13076,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalSpeed", "sentence": "", "events": [ @@ -13121,7 +13156,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMin", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMin", "sentence": "", "events": [ @@ -13202,7 +13236,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMax", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMax", "sentence": "", "events": [ @@ -13283,7 +13316,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "OffsetZ", - "group": "First person camera mouse mapper configuration", "name": "SetOffsetZ", "sentence": "", "events": [ @@ -13320,57 +13352,6 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "doStepPreEvents" - }, - { - "folderName": "Layers and cameras", - "children": [ - { - "functionName": "LookFromObjectEyes" - } - ] - }, - { - "folderName": "First person camera mouse mapper configuration", - "children": [ - { - "functionName": "HorizontalSpeed" - }, - { - "functionName": "SetHorizontalSpeed" - }, - { - "functionName": "VerticalSpeed" - }, - { - "functionName": "SetVerticalSpeed" - }, - { - "functionName": "VerticalAngleMin" - }, - { - "functionName": "SetVerticalAngleMin" - }, - { - "functionName": "VerticalAngleMax" - }, - { - "functionName": "SetVerticalAngleMax" - }, - { - "functionName": "OffsetZ" - }, - { - "functionName": "SetOffsetZ" - } - ] - } - ] - }, "propertyDescriptors": [ { "value": "", @@ -13424,32 +13405,7 @@ "name": "ShouldLock" } ], - "propertiesFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "propertyName": "Object3D" - }, - { - "propertyName": "HorizontalSpeed" - }, - { - "propertyName": "VerticalSpeed" - }, - { - "propertyName": "VerticalAngleMin" - }, - { - "propertyName": "VerticalAngleMax" - }, - { - "propertyName": "OffsetZ" - }, - { - "propertyName": "ShouldLock" - } - ] - } + "sharedPropertyDescriptors": [] } ], "eventsBasedObjects": [] @@ -30071,7 +30027,7 @@ "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", + "version": "0.2.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.", "", @@ -31171,8 +31127,17 @@ "vTerrainLocalXY = position.xy;", "vTerrainLocalZ = position.z;", "vTerrainWorldXY = (modelMatrix * vec4(transformed, 1.0)).xy * terrainWorldScale;", - "vTerrainUp = normalize(transpose(inverse(mat3(modelMatrix))) * objectNormal).z;", + "// The world normal, from the camera space normal (computed by Three.js with a", + "// matrix inverted on the CPU), instead of inverting a matrix for each vertex.", + "vTerrainUp = inverseTransformDirection(transformedNormal, viewMatrix).z;", "`;", + "/** @param {(layer: number) => string} getLayerCode */", + "const forEachLayerCode = (getLayerCode) => [0, 1, 2, 3].map(getLayerCode).join('\\n');", + "", + "// Noise is read from a texture of random values, repeated every", + "// NOISE_MAP_SIZE units of noise: 1 texture read instead of 16 hashes.", + "const NOISE_MAP_SIZE = 256;", + "", "const fragmentShaderDeclarations = `", "uniform sampler2D terrainSplatMap;", "uniform vec2 terrainSplatUvScaleAndOffset;", @@ -31181,11 +31146,11 @@ "uniform sampler2D terrainLayerMap1;", "uniform sampler2D terrainLayerMap2;", "uniform sampler2D terrainLayerMap3;", + "uniform vec3 terrainLayerAverageColors[4];", "uniform sampler2D terrainLayerNormalMap0;", "uniform sampler2D terrainLayerNormalMap1;", "uniform sampler2D terrainLayerNormalMap2;", "uniform sampler2D terrainLayerNormalMap3;", - "uniform float terrainHasNormalMaps;", "uniform float terrainNormalMapStrength;", "uniform float terrainTextureSize;", "uniform float terrainHeightBlending;", @@ -31194,15 +31159,13 @@ "uniform vec2 terrainCliffCosines;", "uniform vec4 terrainBrush;", "uniform sampler2D terrainColorMap;", + "uniform sampler2D terrainNoiseMap;", "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. */", @@ -31214,15 +31177,16 @@ " copyBump.xy = transpose(terrainCopyRotation) * copyBump.xy;", " return mix(bump, copyBump, patches);", "}", - "float terrainNoise(vec2 p) {", + "/**", + " * 4 independent value noises (one by channel), from 0 to 1, smoothly varying", + " * between random values at integer positions.", + " */", + "vec4 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", - " );", + " vec2 f = p - i;", + " // The read position is moved so that the linear interpolation of the", + " // texture between 2 random values becomes a smooth one.", + " return texture2D(terrainNoiseMap, (i + f * f * (3.0 - 2.0 * f) + 0.5) / ${NOISE_MAP_SIZE}.0);", "}", "`;", "// Holes are samples without any painted layer: the interpolated sum of the", @@ -31240,15 +31204,11 @@ "varying vec2 vTerrainLocalXY;", "`;", "", + "// Layers without texture, normal map, height blending or texture variation", + "// are compiled out (see TerrainRenderer.updateMaterial).", "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)", - ");", + "vec4 terrainJitter = terrainNoise(vTerrainWorldXY / 24.0);", "// 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;", @@ -31259,48 +31219,61 @@ "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;", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "vec3 terrainTexture${layer} = texture2D(terrainLayerMap${layer}, terrainUv).rgb;", + "#else", + "vec3 terrainTexture${layer} = vec3(1.0);", + "#endif`", + ")}", "", "// 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);", - "}", + "#ifdef TERRAIN_TEXTURE_VARIATION", + "// 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).x);", + "terrainUv2 = terrainCopyRotation * terrainUv + vec2(0.31, 0.73);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "terrainTexture${layer} = mix(terrainTexture${layer}, texture2D(terrainLayerMap${layer}, terrainUv2).rgb, terrainPatches);", + "#endif`", + ")}", + "terrainColorVariation += terrainTextureVariation * 0.3 *", + " (terrainNoise(terrainUv * 0.11).y + 0.5 * terrainNoise(terrainUv * 0.37 + 3.1).z - 0.75);", + "#endif", "", "// 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);", - "}", + "#ifdef TERRAIN_HEIGHT_BLENDING", + "// Averages are computed once on the CPU, or read from the smallest mipmap", + "// when the image can't be read by the CPU.", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MIPMAP_AVERAGE_${layer}", + "vec3 terrainAverage${layer} = textureLod(terrainLayerMap${layer}, vec2(0.5), 16.0).rgb;", + "#else", + "vec3 terrainAverage${layer} = terrainLayerAverageColors[${layer}];", + "#endif`", + ")}", + "const vec3 terrainLuminance = vec3(0.299, 0.587, 0.114);", + "vec4 terrainHeights = vec4(", + " dot(terrainTexture0 - terrainAverage0, terrainLuminance),", + " dot(terrainTexture1 - terrainAverage1, terrainLuminance),", + " dot(terrainTexture2 - terrainAverage2, terrainLuminance),", + " dot(terrainTexture3 - terrainAverage3, 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);", + "#endif", "", "vec3 terrainColor = terrainColorVariation * (", " terrainWeights.x * terrainLayerColors[0] * terrainTexture0 +", @@ -31332,26 +31305,30 @@ "`;", "", "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;", + "#ifdef TERRAIN_NORMAL_MAPS", + "// Layers without normal map are flat.", + "vec3 terrainMapNormal = vec3(0.0);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_NORMAL_MAP_${layer}", + "terrainMapNormal += terrainWeights[${layer}] * terrainSampleNormal(terrainLayerNormalMap${layer}, terrainUv, terrainUv2, terrainPatches);", + "#else", + "terrainMapNormal.z += terrainWeights[${layer}];", + "#endif`", + ")}", + "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);", - " // 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);", "}", + "terrainTangent = normalize(terrainTangent);", + "vec3 terrainBitangent = cross(normal, terrainTangent);", + "normal = normalize(mat3(terrainTangent, terrainBitangent, normal) * terrainMapNormal);", + "#endif", "`;", "", "let whiteTexture = null;", @@ -31372,7 +31349,30 @@ " return flatNormalTexture;", "};", "", + "let noiseTexture = null;", + "/** Random values, in 4 independent channels, read by `terrainNoise` in the shader. */", + "const getNoiseTexture = () => {", + " if (!noiseTexture) {", + " const random = createRandom(1);", + " const bytes = new Uint8Array(NOISE_MAP_SIZE * NOISE_MAP_SIZE * 4);", + " for (let index = 0; index < bytes.length; index++) {", + " bytes[index] = Math.floor(random() * 256);", + " }", + " noiseTexture = new THREE.DataTexture(bytes, NOISE_MAP_SIZE, NOISE_MAP_SIZE);", + " noiseTexture.wrapS = THREE.RepeatWrapping;", + " noiseTexture.wrapT = THREE.RepeatWrapping;", + " // No mipmaps: the shader moves the read position between samples.", + " noiseTexture.magFilter = THREE.LinearFilter;", + " noiseTexture.minFilter = THREE.LinearFilter;", + " noiseTexture.needsUpdate = true;", + " }", + " return noiseTexture;", + "};", + "", + "const DEFAULT_ANISOTROPY = 4;", + "/** @type {WeakMap>} */", "const layerTextures = new WeakMap();", + "/** @type {WeakMap>} */", "const layerNormalMaps = new WeakMap();", "/**", " * Textures of the image manager have no mipmaps: they would shimmer when", @@ -31380,12 +31380,18 @@ " * @param {gdjs.RuntimeGame} game", " * @param {string} resourceName", " * @param {boolean} isNormalMap Normal maps are directions, not colors.", + " * @param {number} anisotropy", " */", - "const getLayerTexture = (game, resourceName, isNormalMap) => {", + "const getLayerTexture = (game, resourceName, isNormalMap, anisotropy) => {", " if (!resourceName) return isNormalMap ? getFlatNormalTexture() : getWhiteTexture();", " const imageTexture = game.getImageManager().getThreeTexture(resourceName);", - " const textures = isNormalMap ? layerNormalMaps : layerTextures;", - " let texture = textures.get(imageTexture);", + " const texturesByImage = isNormalMap ? layerNormalMaps : layerTextures;", + " let texturesByAnisotropy = texturesByImage.get(imageTexture);", + " if (!texturesByAnisotropy) {", + " texturesByAnisotropy = new Map();", + " texturesByImage.set(imageTexture, texturesByAnisotropy);", + " }", + " let texture = texturesByAnisotropy.get(anisotropy);", " if (!texture) {", " texture = imageTexture.clone();", " if (isNormalMap) texture.colorSpace = THREE.NoColorSpace;", @@ -31393,13 +31399,71 @@ " texture.wrapT = THREE.RepeatWrapping;", " texture.minFilter = THREE.LinearMipmapLinearFilter;", " texture.generateMipmaps = true;", - " texture.anisotropy = 4;", + " texture.anisotropy = anisotropy;", " texture.needsUpdate = true;", - " textures.set(imageTexture, texture);", + " texturesByAnisotropy.set(anisotropy, texture);", " }", " return texture;", "};", "", + "/** @type {Float32Array | null} */", + "let srgbToLinearValues = null;", + "/** @type {WeakMap} */", + "const linearAverageColors = new WeakMap();", + "/**", + " * @param {THREE.Texture} texture", + " * @returns {THREE.Vector3 | null} The average color of the texture, in the", + " * linear color space (like colors read by shaders), or null if the image", + " * can't be read: not loaded yet, or from another domain without CORS.", + " */", + "const getLinearAverageColor = (texture) => {", + " const image = texture.image;", + " if (!image) return null;", + " const cachedColor = linearAverageColors.get(image);", + " if (cachedColor) return cachedColor;", + " 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 null;", + " const size = 64;", + " let pixels;", + " try {", + " const canvas = document.createElement('canvas');", + " canvas.width = size;", + " canvas.height = size;", + " const context = canvas.getContext('2d');", + " // Downscaled with filtering, so that all pixels are counted.", + " context.imageSmoothingEnabled = true;", + " context.imageSmoothingQuality = 'high';", + " context.drawImage(image, 0, 0, size, size);", + " pixels = context.getImageData(0, 0, size, size).data;", + " } catch (error) {", + " return null;", + " }", + " if (!srgbToLinearValues) {", + " srgbToLinearValues = new Float32Array(256);", + " for (let value = 0; value < 256; value++) {", + " const srgb = value / 255;", + " srgbToLinearValues[value] = srgb <= 0.04045 ? srgb / 12.92 : Math.pow((srgb + 0.055) / 1.055, 2.4);", + " }", + " }", + " const isSrgb = texture.colorSpace === THREE.SRGBColorSpace;", + " let red = 0;", + " let green = 0;", + " let blue = 0;", + " for (let index = 0; index < pixels.length; index += 4) {", + " red += isSrgb ? srgbToLinearValues[pixels[index]] : pixels[index] / 255;", + " green += isSrgb ? srgbToLinearValues[pixels[index + 1]] : pixels[index + 1] / 255;", + " blue += isSrgb ? srgbToLinearValues[pixels[index + 2]] : pixels[index + 2] / 255;", + " }", + " const count = size * size;", + " const color = new THREE.Vector3(red / count, green / count, blue / count);", + " linearAverageColors.set(image, color);", + " return color;", + "};", + "", "/**", " * A texture of 4 bytes per sample of a grid of `size * size` samples (layer", " * weights, tint and wetness...), interpolated between samples.", @@ -31458,11 +31522,14 @@ " terrainLayerMap1: { value: getWhiteTexture() },", " terrainLayerMap2: { value: getWhiteTexture() },", " terrainLayerMap3: { value: getWhiteTexture() },", + " // Used by the height blending, in the linear color space.", + " terrainLayerAverageColors: {", + " value: [new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1)],", + " },", " terrainLayerNormalMap0: { value: getFlatNormalTexture() },", " terrainLayerNormalMap1: { value: getFlatNormalTexture() },", " terrainLayerNormalMap2: { value: getFlatNormalTexture() },", " terrainLayerNormalMap3: { value: getFlatNormalTexture() },", - " terrainHasNormalMaps: { value: 0 },", " terrainNormalMapStrength: { value: 1 },", " terrainTextureSize: { value: 256 },", " terrainHeightBlending: { value: 0 },", @@ -31471,6 +31538,7 @@ " terrainCliffCosines: { value: new THREE.Vector2() },", " terrainBrush: { value: new THREE.Vector4() },", " terrainColorMap: { value: getWhiteTexture() },", + " terrainNoiseMap: { value: getNoiseTexture() },", " // 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.", @@ -31490,8 +31558,12 @@ " ['#include ', '#include \\n' + fragmentShaderNormalCode],", " ], 'Terrain3D');", " };", - " this.material.customProgramCacheKey = () => (this.hasHoles ? 'Terrain3DWithHoles' : 'Terrain3D');", " this.hasHoles = false;", + " /** The defines of the features used by the terrain (see `updateMaterial`), without holes. */", + " this.featureDefines = [];", + " /** All the defines of the shader, separated by semicolons. */", + " this.shaderKey = '';", + " this.material.customProgramCacheKey = () => 'Terrain3D;' + this.shaderKey;", " // Without them, holes would cast shadows.", " this.depthMaterial = this._createHoleDepthMaterial(new THREE.MeshDepthMaterial());", " this.distanceMaterial = this._createHoleDepthMaterial(new THREE.MeshDistanceMaterial());", @@ -31528,11 +31600,28 @@ " setHasHoles(hasHoles) {", " if (hasHoles === this.hasHoles) return;", " this.hasHoles = hasHoles;", + " this._updateShaderDefines();", + " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", + " }", + "", + " /**", + " * Compile the shader only with the features used by the terrain. It is", + " * compiled again only when they change: properties only change in the", + " * editor, holes can be made by actions.", + " */", + " _updateShaderDefines() {", + " const defines = this.hasHoles ? [...this.featureDefines, 'TERRAIN_HOLES'] : this.featureDefines;", + " const shaderKey = defines.join(';');", + " if (shaderKey === this.shaderKey) return;", " // The material has its own defines (STANDARD).", - " if (hasHoles) this.material.defines.TERRAIN_HOLES = '';", - " else delete this.material.defines.TERRAIN_HOLES;", + " for (const name of this.shaderKey ? this.shaderKey.split(';') : []) {", + " delete this.material.defines[name];", + " }", + " for (const name of defines) {", + " this.material.defines[name] = '';", + " }", + " this.shaderKey = shaderKey;", " this.material.needsUpdate = true;", - " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", " }", "", " /** @param {THREE.Mesh} mesh */", @@ -31764,16 +31853,38 @@ " object._getLayer3NormalMap(),", " object._getLayer4NormalMap(),", " ];", + " const anisotropy = Math.max(parseInt(object._getAnisotropy(), 10) || DEFAULT_ANISOTROPY, 1);", + " const heightBlending = clamp01(object._getHeightBlending());", + " const textureVariation = clamp01(object._getTextureVariation());", + " const featureDefines = [];", " 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);", + " const layerTexture = getLayerTexture(game, textures[layer], false, anisotropy);", + " this.uniforms['terrainLayerMap' + layer].value = layerTexture;", + " this.uniforms['terrainLayerNormalMap' + layer].value = getLayerTexture(game, normalMaps[layer], true, anisotropy);", + " const averageColor = this.uniforms.terrainLayerAverageColors.value[layer];", + " if (textures[layer]) {", + " featureDefines.push('TERRAIN_LAYER_MAP_' + layer);", + " const linearAverageColor = heightBlending > 0 ? getLinearAverageColor(layerTexture) : null;", + " if (linearAverageColor) {", + " averageColor.copy(linearAverageColor);", + " } else if (heightBlending > 0) {", + " featureDefines.push('TERRAIN_LAYER_MIPMAP_AVERAGE_' + layer);", + " }", + " } else {", + " averageColor.set(1, 1, 1);", + " }", + " if (normalMaps[layer]) featureDefines.push('TERRAIN_LAYER_NORMAL_MAP_' + layer);", " }", - " this.uniforms.terrainHasNormalMaps.value = normalMaps.some((normalMap) => !!normalMap) ? 1 : 0;", + " if (normalMaps.some((normalMap) => !!normalMap)) featureDefines.push('TERRAIN_NORMAL_MAPS');", + " if (heightBlending > 0) featureDefines.push('TERRAIN_HEIGHT_BLENDING');", + " if (textureVariation > 0) featureDefines.push('TERRAIN_TEXTURE_VARIATION');", + " this.featureDefines = featureDefines;", + " this._updateShaderDefines();", " 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());", + " this.uniforms.terrainHeightBlending.value = heightBlending;", + " this.uniforms.terrainTextureVariation.value = textureVariation;", "", " const cliffLayer = parseInt(object._getCliffLayer(), 10);", " const cliffAngle = gdjs.toRad(object._getCliffAngle());", @@ -32691,40 +32802,21 @@ " return typeof scene.getRenderer3DWorldScale === 'function' ? scene.getRenderer3DWorldScale() : 1;", "};", "", + "/** @type {WeakMap} */", "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).", + " * @returns {THREE.Color} The average color of the texture (white when it can't be read).", " */", "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 linearAverageColor = getLinearAverageColor(texture);", + " if (!linearAverageColor) return white || (white = new THREE.Color(1, 1, 1));", + " let color = averageColors.get(linearAverageColor);", + " if (!color) {", + " color = new THREE.Color(linearAverageColor.x, linearAverageColor.y, linearAverageColor.z);", + " averageColors.set(linearAverageColor, color);", " }", - " 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;", "};", "", @@ -36338,6 +36430,37 @@ "group": "Rendering", "name": "ReceiveShadow" }, + { + "value": "4", + "type": "Choice", + "label": "Anisotropic filtering", + "description": "Keeps layer textures sharp where the ground is seen at a low angle (like from a car). Lower is faster on slow graphics cards, but blurs the ground in the distance.", + "group": "Rendering", + "choices": [ + { + "label": "None (fastest)", + "value": "1" + }, + { + "label": "2x", + "value": "2" + }, + { + "label": "4x", + "value": "4" + }, + { + "label": "8x", + "value": "8" + }, + { + "label": "16x (sharpest)", + "value": "16" + } + ], + "advanced": true, + "name": "Anisotropy" + }, { "value": "", "type": "String", @@ -36451,6 +36574,9 @@ }, { "propertyName": "ReceiveShadow" + }, + { + "propertyName": "Anisotropy" } ] }, diff --git a/examples/starting-first-person-horror/theme-slots.json b/examples/starting-first-person-horror/theme-slots.json index b0cb3b77c..938fb30bb 100644 --- a/examples/starting-first-person-horror/theme-slots.json +++ b/examples/starting-first-person-horror/theme-slots.json @@ -1,12 +1,11 @@ { "version": 1, "objects": { - "scene:Game Scene/Ground": { - "front": "env.ground", - "left": "env.wall", - "right": "env.wall", - "top": "env.wall", - "bottom": "env.wall" + "scene:Game Scene/Terrain": { + "Layer1Texture": { + "slot": "terrain.ground", + "normalMap": "Layer1NormalMap" + } }, "scene:Game Scene/Obstacle": { "front": "env.wall", @@ -25,6 +24,9 @@ "scene:Game Scene/PathfindingObstacle", "scene:Game Scene/Player", "object:Light3D::SpotLight3D/Placeholder", - "object:Light3D::PointLight3D/Placeholder" + "object:Light3D::PointLight3D/Placeholder", + "object:Terrain3D::Terrain3D/AreaPlaceholder", + "object:Terrain3D::TerrainGrass/AreaPlaceholder", + "object:Terrain3D::ScatteredModels/AreaPlaceholder" ] } diff --git a/examples/starting-first-person-shooter-enemies/starting-first-person-shooter-enemies.json b/examples/starting-first-person-shooter-enemies/starting-first-person-shooter-enemies.json index bd508ab1d..663b6e69c 100644 --- a/examples/starting-first-person-shooter-enemies/starting-first-person-shooter-enemies.json +++ b/examples/starting-first-person-shooter-enemies/starting-first-person-shooter-enemies.json @@ -350,6 +350,30 @@ "persistentUuid": "f1651ed6-c467-4174-a82a-ab153372b2c5", "type": "string", "value": "Keyboard" + }, + { + "name": "TapTouchId", + "persistentUuid": "1df99264-f71e-4e5a-bbac-2a63e9ae0584", + "type": "number", + "value": 0 + }, + { + "name": "TapStartX", + "persistentUuid": "85ba283b-6bfe-4109-88e5-4c89d73355ab", + "type": "number", + "value": 0 + }, + { + "name": "TapStartY", + "persistentUuid": "a9c8e66f-b54a-4e72-8b4a-83b31fcafc89", + "type": "number", + "value": 0 + }, + { + "name": "TapShot", + "persistentUuid": "99a8dcab-cf2c-4dd6-adbb-229300aec174", + "type": "boolean", + "value": false } ], "instances": [ @@ -1847,6 +1871,13 @@ "gravityScale": 1, "layers": 17, "masks": 17 + }, + { + "name": "NavMeshObstacle", + "type": "NavMeshPathfinding::NavMeshObstacleBehavior", + "shape": "Box", + "meshShapeResourceName": "", + "obstacleOnly": false } ], "content": { @@ -2041,6 +2072,125 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Keep shooting while the button is held, even when the finger drags away from it to aim." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"ShootCooldown\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "A quick tap anywhere outside the buttons also shoots. Dragging only aims." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasTouchEnded" + }, + "parameters": [ + "", + "TapTouchId" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CompareTimer" + }, + "parameters": [ + "", + "\"TapTouch\"", + "<", + "0.25" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "DistanceBetweenPositions(TapStartX, TapStartY, TouchX(TapTouchId, \"MobileControls\", 0), TouchY(TapTouchId, \"MobileControls\", 0))", + "<", + "30" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "TapShot", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TapTouchId", + "=", + "0" + ] + } + ] + } + ] + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -2107,11 +2257,57 @@ }, { "type": { - "value": "ButtonStates::ButtonFSM::IsPressed" + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "ShootButton", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressedOutside" + }, + "parameters": [ + "ShootButton", + "ButtonFSM", + "" + ] + } + ] + } + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::And" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" }, "parameters": [ - "ShootButton", - "ButtonFSM", + "ControlKind", + "=", + "\"Touch\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "TapShot", + "True", "" ] } @@ -2121,12 +2317,26 @@ }, { "type": { - "value": "BuiltinCommonInstructions::Once" + "value": "CompareTimer" }, - "parameters": [] + "parameters": [ + "", + "\"ShootCooldown\"", + ">=", + "0.2" + ] } ], "actions": [ + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"ShootCooldown\"" + ] + }, { "type": { "value": "PlaySound" @@ -2299,6 +2509,22 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "TapShot", + "False", + "" + ] + } + ] } ], "parameters": [] @@ -2844,6 +3070,51 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Start with touch controls on touchscreens." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "SystemInfo::HasTouchScreen" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::SetChecked" + }, + "parameters": [ + "ControlsToggle", + "yes", + "" + ] + } + ] + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -3087,18 +3358,11 @@ "conditions": [ { "type": { - "value": "MouseButtonFromTextPressed" + "value": "HasAnyTouchOrMouseStarted" }, "parameters": [ - "", - "\"Left\"" + "" ] - }, - { - "type": { - "value": "BuiltinCommonInstructions::Once" - }, - "parameters": [] } ], "actions": [], @@ -3117,28 +3381,6 @@ "\"Left\"", "" ] - }, - { - "type": { - "inverted": true, - "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" - }, - "parameters": [ - "JumpButton", - "MultitouchButton", - "" - ] - }, - { - "type": { - "inverted": true, - "value": "ButtonStates::ButtonFSM::IsPressed" - }, - "parameters": [ - "ShootButton", - "ButtonFSM", - "" - ] } ], "actions": [ @@ -3151,6 +3393,76 @@ "" ] } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" + }, + "parameters": [ + "JumpButton", + "MultitouchButton", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "ShootButton", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TapTouchId", + "=", + "StartedTouchOrMouseId(0)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TapStartX", + "=", + "TouchX(TapTouchId, \"MobileControls\", 0)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TapStartY", + "=", + "TouchY(TapTouchId, \"MobileControls\", 0)" + ] + }, + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"TapTouch\"" + ] + } + ] + } ] } ] @@ -12713,7 +13025,7 @@ "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", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -12761,7 +13073,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -13009,16 +13321,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -13201,6 +13543,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -13563,6 +13930,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -13584,7 +13963,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { @@ -40894,7 +41273,7 @@ "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", + "version": "0.2.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.", "", @@ -41994,8 +42373,17 @@ "vTerrainLocalXY = position.xy;", "vTerrainLocalZ = position.z;", "vTerrainWorldXY = (modelMatrix * vec4(transformed, 1.0)).xy * terrainWorldScale;", - "vTerrainUp = normalize(transpose(inverse(mat3(modelMatrix))) * objectNormal).z;", + "// The world normal, from the camera space normal (computed by Three.js with a", + "// matrix inverted on the CPU), instead of inverting a matrix for each vertex.", + "vTerrainUp = inverseTransformDirection(transformedNormal, viewMatrix).z;", "`;", + "/** @param {(layer: number) => string} getLayerCode */", + "const forEachLayerCode = (getLayerCode) => [0, 1, 2, 3].map(getLayerCode).join('\\n');", + "", + "// Noise is read from a texture of random values, repeated every", + "// NOISE_MAP_SIZE units of noise: 1 texture read instead of 16 hashes.", + "const NOISE_MAP_SIZE = 256;", + "", "const fragmentShaderDeclarations = `", "uniform sampler2D terrainSplatMap;", "uniform vec2 terrainSplatUvScaleAndOffset;", @@ -42004,11 +42392,11 @@ "uniform sampler2D terrainLayerMap1;", "uniform sampler2D terrainLayerMap2;", "uniform sampler2D terrainLayerMap3;", + "uniform vec3 terrainLayerAverageColors[4];", "uniform sampler2D terrainLayerNormalMap0;", "uniform sampler2D terrainLayerNormalMap1;", "uniform sampler2D terrainLayerNormalMap2;", "uniform sampler2D terrainLayerNormalMap3;", - "uniform float terrainHasNormalMaps;", "uniform float terrainNormalMapStrength;", "uniform float terrainTextureSize;", "uniform float terrainHeightBlending;", @@ -42017,15 +42405,13 @@ "uniform vec2 terrainCliffCosines;", "uniform vec4 terrainBrush;", "uniform sampler2D terrainColorMap;", + "uniform sampler2D terrainNoiseMap;", "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. */", @@ -42037,15 +42423,16 @@ " copyBump.xy = transpose(terrainCopyRotation) * copyBump.xy;", " return mix(bump, copyBump, patches);", "}", - "float terrainNoise(vec2 p) {", + "/**", + " * 4 independent value noises (one by channel), from 0 to 1, smoothly varying", + " * between random values at integer positions.", + " */", + "vec4 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", - " );", + " vec2 f = p - i;", + " // The read position is moved so that the linear interpolation of the", + " // texture between 2 random values becomes a smooth one.", + " return texture2D(terrainNoiseMap, (i + f * f * (3.0 - 2.0 * f) + 0.5) / ${NOISE_MAP_SIZE}.0);", "}", "`;", "// Holes are samples without any painted layer: the interpolated sum of the", @@ -42063,15 +42450,11 @@ "varying vec2 vTerrainLocalXY;", "`;", "", + "// Layers without texture, normal map, height blending or texture variation", + "// are compiled out (see TerrainRenderer.updateMaterial).", "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)", - ");", + "vec4 terrainJitter = terrainNoise(vTerrainWorldXY / 24.0);", "// 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;", @@ -42082,48 +42465,61 @@ "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;", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "vec3 terrainTexture${layer} = texture2D(terrainLayerMap${layer}, terrainUv).rgb;", + "#else", + "vec3 terrainTexture${layer} = vec3(1.0);", + "#endif`", + ")}", "", "// 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);", - "}", + "#ifdef TERRAIN_TEXTURE_VARIATION", + "// 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).x);", + "terrainUv2 = terrainCopyRotation * terrainUv + vec2(0.31, 0.73);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "terrainTexture${layer} = mix(terrainTexture${layer}, texture2D(terrainLayerMap${layer}, terrainUv2).rgb, terrainPatches);", + "#endif`", + ")}", + "terrainColorVariation += terrainTextureVariation * 0.3 *", + " (terrainNoise(terrainUv * 0.11).y + 0.5 * terrainNoise(terrainUv * 0.37 + 3.1).z - 0.75);", + "#endif", "", "// 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);", - "}", + "#ifdef TERRAIN_HEIGHT_BLENDING", + "// Averages are computed once on the CPU, or read from the smallest mipmap", + "// when the image can't be read by the CPU.", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MIPMAP_AVERAGE_${layer}", + "vec3 terrainAverage${layer} = textureLod(terrainLayerMap${layer}, vec2(0.5), 16.0).rgb;", + "#else", + "vec3 terrainAverage${layer} = terrainLayerAverageColors[${layer}];", + "#endif`", + ")}", + "const vec3 terrainLuminance = vec3(0.299, 0.587, 0.114);", + "vec4 terrainHeights = vec4(", + " dot(terrainTexture0 - terrainAverage0, terrainLuminance),", + " dot(terrainTexture1 - terrainAverage1, terrainLuminance),", + " dot(terrainTexture2 - terrainAverage2, terrainLuminance),", + " dot(terrainTexture3 - terrainAverage3, 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);", + "#endif", "", "vec3 terrainColor = terrainColorVariation * (", " terrainWeights.x * terrainLayerColors[0] * terrainTexture0 +", @@ -42155,26 +42551,30 @@ "`;", "", "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;", + "#ifdef TERRAIN_NORMAL_MAPS", + "// Layers without normal map are flat.", + "vec3 terrainMapNormal = vec3(0.0);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_NORMAL_MAP_${layer}", + "terrainMapNormal += terrainWeights[${layer}] * terrainSampleNormal(terrainLayerNormalMap${layer}, terrainUv, terrainUv2, terrainPatches);", + "#else", + "terrainMapNormal.z += terrainWeights[${layer}];", + "#endif`", + ")}", + "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);", - " // 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);", "}", + "terrainTangent = normalize(terrainTangent);", + "vec3 terrainBitangent = cross(normal, terrainTangent);", + "normal = normalize(mat3(terrainTangent, terrainBitangent, normal) * terrainMapNormal);", + "#endif", "`;", "", "let whiteTexture = null;", @@ -42195,7 +42595,30 @@ " return flatNormalTexture;", "};", "", + "let noiseTexture = null;", + "/** Random values, in 4 independent channels, read by `terrainNoise` in the shader. */", + "const getNoiseTexture = () => {", + " if (!noiseTexture) {", + " const random = createRandom(1);", + " const bytes = new Uint8Array(NOISE_MAP_SIZE * NOISE_MAP_SIZE * 4);", + " for (let index = 0; index < bytes.length; index++) {", + " bytes[index] = Math.floor(random() * 256);", + " }", + " noiseTexture = new THREE.DataTexture(bytes, NOISE_MAP_SIZE, NOISE_MAP_SIZE);", + " noiseTexture.wrapS = THREE.RepeatWrapping;", + " noiseTexture.wrapT = THREE.RepeatWrapping;", + " // No mipmaps: the shader moves the read position between samples.", + " noiseTexture.magFilter = THREE.LinearFilter;", + " noiseTexture.minFilter = THREE.LinearFilter;", + " noiseTexture.needsUpdate = true;", + " }", + " return noiseTexture;", + "};", + "", + "const DEFAULT_ANISOTROPY = 4;", + "/** @type {WeakMap>} */", "const layerTextures = new WeakMap();", + "/** @type {WeakMap>} */", "const layerNormalMaps = new WeakMap();", "/**", " * Textures of the image manager have no mipmaps: they would shimmer when", @@ -42203,12 +42626,18 @@ " * @param {gdjs.RuntimeGame} game", " * @param {string} resourceName", " * @param {boolean} isNormalMap Normal maps are directions, not colors.", + " * @param {number} anisotropy", " */", - "const getLayerTexture = (game, resourceName, isNormalMap) => {", + "const getLayerTexture = (game, resourceName, isNormalMap, anisotropy) => {", " if (!resourceName) return isNormalMap ? getFlatNormalTexture() : getWhiteTexture();", " const imageTexture = game.getImageManager().getThreeTexture(resourceName);", - " const textures = isNormalMap ? layerNormalMaps : layerTextures;", - " let texture = textures.get(imageTexture);", + " const texturesByImage = isNormalMap ? layerNormalMaps : layerTextures;", + " let texturesByAnisotropy = texturesByImage.get(imageTexture);", + " if (!texturesByAnisotropy) {", + " texturesByAnisotropy = new Map();", + " texturesByImage.set(imageTexture, texturesByAnisotropy);", + " }", + " let texture = texturesByAnisotropy.get(anisotropy);", " if (!texture) {", " texture = imageTexture.clone();", " if (isNormalMap) texture.colorSpace = THREE.NoColorSpace;", @@ -42216,13 +42645,71 @@ " texture.wrapT = THREE.RepeatWrapping;", " texture.minFilter = THREE.LinearMipmapLinearFilter;", " texture.generateMipmaps = true;", - " texture.anisotropy = 4;", + " texture.anisotropy = anisotropy;", " texture.needsUpdate = true;", - " textures.set(imageTexture, texture);", + " texturesByAnisotropy.set(anisotropy, texture);", " }", " return texture;", "};", "", + "/** @type {Float32Array | null} */", + "let srgbToLinearValues = null;", + "/** @type {WeakMap} */", + "const linearAverageColors = new WeakMap();", + "/**", + " * @param {THREE.Texture} texture", + " * @returns {THREE.Vector3 | null} The average color of the texture, in the", + " * linear color space (like colors read by shaders), or null if the image", + " * can't be read: not loaded yet, or from another domain without CORS.", + " */", + "const getLinearAverageColor = (texture) => {", + " const image = texture.image;", + " if (!image) return null;", + " const cachedColor = linearAverageColors.get(image);", + " if (cachedColor) return cachedColor;", + " 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 null;", + " const size = 64;", + " let pixels;", + " try {", + " const canvas = document.createElement('canvas');", + " canvas.width = size;", + " canvas.height = size;", + " const context = canvas.getContext('2d');", + " // Downscaled with filtering, so that all pixels are counted.", + " context.imageSmoothingEnabled = true;", + " context.imageSmoothingQuality = 'high';", + " context.drawImage(image, 0, 0, size, size);", + " pixels = context.getImageData(0, 0, size, size).data;", + " } catch (error) {", + " return null;", + " }", + " if (!srgbToLinearValues) {", + " srgbToLinearValues = new Float32Array(256);", + " for (let value = 0; value < 256; value++) {", + " const srgb = value / 255;", + " srgbToLinearValues[value] = srgb <= 0.04045 ? srgb / 12.92 : Math.pow((srgb + 0.055) / 1.055, 2.4);", + " }", + " }", + " const isSrgb = texture.colorSpace === THREE.SRGBColorSpace;", + " let red = 0;", + " let green = 0;", + " let blue = 0;", + " for (let index = 0; index < pixels.length; index += 4) {", + " red += isSrgb ? srgbToLinearValues[pixels[index]] : pixels[index] / 255;", + " green += isSrgb ? srgbToLinearValues[pixels[index + 1]] : pixels[index + 1] / 255;", + " blue += isSrgb ? srgbToLinearValues[pixels[index + 2]] : pixels[index + 2] / 255;", + " }", + " const count = size * size;", + " const color = new THREE.Vector3(red / count, green / count, blue / count);", + " linearAverageColors.set(image, color);", + " return color;", + "};", + "", "/**", " * A texture of 4 bytes per sample of a grid of `size * size` samples (layer", " * weights, tint and wetness...), interpolated between samples.", @@ -42281,11 +42768,14 @@ " terrainLayerMap1: { value: getWhiteTexture() },", " terrainLayerMap2: { value: getWhiteTexture() },", " terrainLayerMap3: { value: getWhiteTexture() },", + " // Used by the height blending, in the linear color space.", + " terrainLayerAverageColors: {", + " value: [new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1)],", + " },", " terrainLayerNormalMap0: { value: getFlatNormalTexture() },", " terrainLayerNormalMap1: { value: getFlatNormalTexture() },", " terrainLayerNormalMap2: { value: getFlatNormalTexture() },", " terrainLayerNormalMap3: { value: getFlatNormalTexture() },", - " terrainHasNormalMaps: { value: 0 },", " terrainNormalMapStrength: { value: 1 },", " terrainTextureSize: { value: 256 },", " terrainHeightBlending: { value: 0 },", @@ -42294,6 +42784,7 @@ " terrainCliffCosines: { value: new THREE.Vector2() },", " terrainBrush: { value: new THREE.Vector4() },", " terrainColorMap: { value: getWhiteTexture() },", + " terrainNoiseMap: { value: getNoiseTexture() },", " // 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.", @@ -42313,8 +42804,12 @@ " ['#include ', '#include \\n' + fragmentShaderNormalCode],", " ], 'Terrain3D');", " };", - " this.material.customProgramCacheKey = () => (this.hasHoles ? 'Terrain3DWithHoles' : 'Terrain3D');", " this.hasHoles = false;", + " /** The defines of the features used by the terrain (see `updateMaterial`), without holes. */", + " this.featureDefines = [];", + " /** All the defines of the shader, separated by semicolons. */", + " this.shaderKey = '';", + " this.material.customProgramCacheKey = () => 'Terrain3D;' + this.shaderKey;", " // Without them, holes would cast shadows.", " this.depthMaterial = this._createHoleDepthMaterial(new THREE.MeshDepthMaterial());", " this.distanceMaterial = this._createHoleDepthMaterial(new THREE.MeshDistanceMaterial());", @@ -42351,11 +42846,28 @@ " setHasHoles(hasHoles) {", " if (hasHoles === this.hasHoles) return;", " this.hasHoles = hasHoles;", + " this._updateShaderDefines();", + " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", + " }", + "", + " /**", + " * Compile the shader only with the features used by the terrain. It is", + " * compiled again only when they change: properties only change in the", + " * editor, holes can be made by actions.", + " */", + " _updateShaderDefines() {", + " const defines = this.hasHoles ? [...this.featureDefines, 'TERRAIN_HOLES'] : this.featureDefines;", + " const shaderKey = defines.join(';');", + " if (shaderKey === this.shaderKey) return;", " // The material has its own defines (STANDARD).", - " if (hasHoles) this.material.defines.TERRAIN_HOLES = '';", - " else delete this.material.defines.TERRAIN_HOLES;", + " for (const name of this.shaderKey ? this.shaderKey.split(';') : []) {", + " delete this.material.defines[name];", + " }", + " for (const name of defines) {", + " this.material.defines[name] = '';", + " }", + " this.shaderKey = shaderKey;", " this.material.needsUpdate = true;", - " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", " }", "", " /** @param {THREE.Mesh} mesh */", @@ -42587,16 +43099,38 @@ " object._getLayer3NormalMap(),", " object._getLayer4NormalMap(),", " ];", + " const anisotropy = Math.max(parseInt(object._getAnisotropy(), 10) || DEFAULT_ANISOTROPY, 1);", + " const heightBlending = clamp01(object._getHeightBlending());", + " const textureVariation = clamp01(object._getTextureVariation());", + " const featureDefines = [];", " 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);", + " const layerTexture = getLayerTexture(game, textures[layer], false, anisotropy);", + " this.uniforms['terrainLayerMap' + layer].value = layerTexture;", + " this.uniforms['terrainLayerNormalMap' + layer].value = getLayerTexture(game, normalMaps[layer], true, anisotropy);", + " const averageColor = this.uniforms.terrainLayerAverageColors.value[layer];", + " if (textures[layer]) {", + " featureDefines.push('TERRAIN_LAYER_MAP_' + layer);", + " const linearAverageColor = heightBlending > 0 ? getLinearAverageColor(layerTexture) : null;", + " if (linearAverageColor) {", + " averageColor.copy(linearAverageColor);", + " } else if (heightBlending > 0) {", + " featureDefines.push('TERRAIN_LAYER_MIPMAP_AVERAGE_' + layer);", + " }", + " } else {", + " averageColor.set(1, 1, 1);", + " }", + " if (normalMaps[layer]) featureDefines.push('TERRAIN_LAYER_NORMAL_MAP_' + layer);", " }", - " this.uniforms.terrainHasNormalMaps.value = normalMaps.some((normalMap) => !!normalMap) ? 1 : 0;", + " if (normalMaps.some((normalMap) => !!normalMap)) featureDefines.push('TERRAIN_NORMAL_MAPS');", + " if (heightBlending > 0) featureDefines.push('TERRAIN_HEIGHT_BLENDING');", + " if (textureVariation > 0) featureDefines.push('TERRAIN_TEXTURE_VARIATION');", + " this.featureDefines = featureDefines;", + " this._updateShaderDefines();", " 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());", + " this.uniforms.terrainHeightBlending.value = heightBlending;", + " this.uniforms.terrainTextureVariation.value = textureVariation;", "", " const cliffLayer = parseInt(object._getCliffLayer(), 10);", " const cliffAngle = gdjs.toRad(object._getCliffAngle());", @@ -43514,40 +44048,21 @@ " return typeof scene.getRenderer3DWorldScale === 'function' ? scene.getRenderer3DWorldScale() : 1;", "};", "", + "/** @type {WeakMap} */", "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).", + " * @returns {THREE.Color} The average color of the texture (white when it can't be read).", " */", "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 linearAverageColor = getLinearAverageColor(texture);", + " if (!linearAverageColor) return white || (white = new THREE.Color(1, 1, 1));", + " let color = averageColors.get(linearAverageColor);", + " if (!color) {", + " color = new THREE.Color(linearAverageColor.x, linearAverageColor.y, linearAverageColor.z);", + " averageColors.set(linearAverageColor, color);", " }", - " 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;", "};", "", @@ -47161,6 +47676,37 @@ "group": "Rendering", "name": "ReceiveShadow" }, + { + "value": "4", + "type": "Choice", + "label": "Anisotropic filtering", + "description": "Keeps layer textures sharp where the ground is seen at a low angle (like from a car). Lower is faster on slow graphics cards, but blurs the ground in the distance.", + "group": "Rendering", + "choices": [ + { + "label": "None (fastest)", + "value": "1" + }, + { + "label": "2x", + "value": "2" + }, + { + "label": "4x", + "value": "4" + }, + { + "label": "8x", + "value": "8" + }, + { + "label": "16x (sharpest)", + "value": "16" + } + ], + "advanced": true, + "name": "Anisotropy" + }, { "value": "", "type": "String", @@ -47274,6 +47820,9 @@ }, { "propertyName": "ReceiveShadow" + }, + { + "propertyName": "Anisotropy" } ] }, diff --git a/examples/starting-first-person-shooter-enemies/theme-slots.json b/examples/starting-first-person-shooter-enemies/theme-slots.json index d2e84383d..46ddccca0 100644 --- a/examples/starting-first-person-shooter-enemies/theme-slots.json +++ b/examples/starting-first-person-shooter-enemies/theme-slots.json @@ -1,12 +1,11 @@ { "version": 1, "objects": { - "scene:Game Scene/Ground": { - "front": "env.ground", - "left": "env.wall", - "right": "env.wall", - "top": "env.wall", - "bottom": "env.wall" + "scene:Game Scene/Terrain": { + "Layer1Texture": { + "slot": "terrain.ground", + "normalMap": "Layer1NormalMap" + } }, "scene:Game Scene/Obstacle": { "front": "env.wall", @@ -23,6 +22,9 @@ "effect:Game Scene//SkyBox": "sky.day" }, "ignoredObjects": [ - "scene:Game Scene/Player" + "scene:Game Scene/Player", + "object:Terrain3D::Terrain3D/AreaPlaceholder", + "object:Terrain3D::TerrainGrass/AreaPlaceholder", + "object:Terrain3D::ScatteredModels/AreaPlaceholder" ] } diff --git a/examples/starting-first-person-shooter-horror/starting-first-person-shooter-horror.json b/examples/starting-first-person-shooter-horror/starting-first-person-shooter-horror.json index d7fc13677..cf8167dec 100644 --- a/examples/starting-first-person-shooter-horror/starting-first-person-shooter-horror.json +++ b/examples/starting-first-person-shooter-horror/starting-first-person-shooter-horror.json @@ -358,6 +358,30 @@ "persistentUuid": "f1651ed6-c467-4174-a82a-ab153372b2c5", "type": "string", "value": "Keyboard" + }, + { + "name": "TapTouchId", + "persistentUuid": "6974289c-65b0-4107-a412-30c77b1e1324", + "type": "number", + "value": 0 + }, + { + "name": "TapStartX", + "persistentUuid": "2da0f146-de58-4b90-a986-08fd498eff27", + "type": "number", + "value": 0 + }, + { + "name": "TapStartY", + "persistentUuid": "023b3adc-20e9-4c78-b147-91d2cc10b339", + "type": "number", + "value": 0 + }, + { + "name": "TapShot", + "persistentUuid": "80f7538c-8236-4d3c-8e04-27816d9c7ef3", + "type": "boolean", + "value": false } ], "instances": [ @@ -2140,6 +2164,125 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Keep shooting while the button is held, even when the finger drags away from it to aim." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"ShootCooldown\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "A quick tap anywhere outside the buttons also shoots. Dragging only aims." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasTouchEnded" + }, + "parameters": [ + "", + "TapTouchId" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CompareTimer" + }, + "parameters": [ + "", + "\"TapTouch\"", + "<", + "0.25" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "DistanceBetweenPositions(TapStartX, TapStartY, TouchX(TapTouchId, \"MobileControls\", 0), TouchY(TapTouchId, \"MobileControls\", 0))", + "<", + "30" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "TapShot", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TapTouchId", + "=", + "0" + ] + } + ] + } + ] + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -2206,11 +2349,57 @@ }, { "type": { - "value": "ButtonStates::ButtonFSM::IsPressed" + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "ShootButton", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressedOutside" + }, + "parameters": [ + "ShootButton", + "ButtonFSM", + "" + ] + } + ] + } + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::And" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" }, "parameters": [ - "ShootButton", - "ButtonFSM", + "ControlKind", + "=", + "\"Touch\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "TapShot", + "True", "" ] } @@ -2220,12 +2409,26 @@ }, { "type": { - "value": "BuiltinCommonInstructions::Once" + "value": "CompareTimer" }, - "parameters": [] + "parameters": [ + "", + "\"ShootCooldown\"", + ">=", + "0.2" + ] } ], "actions": [ + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"ShootCooldown\"" + ] + }, { "type": { "value": "PlaySound" @@ -2377,6 +2580,22 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "TapShot", + "False", + "" + ] + } + ] } ], "parameters": [] @@ -2894,6 +3113,51 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Start with touch controls on touchscreens." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "SystemInfo::HasTouchScreen" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::SetChecked" + }, + "parameters": [ + "ControlsToggle", + "yes", + "" + ] + } + ] + }, { "folded": true, "type": "BuiltinCommonInstructions::Standard", @@ -3145,18 +3409,11 @@ "conditions": [ { "type": { - "value": "MouseButtonFromTextPressed" + "value": "HasAnyTouchOrMouseStarted" }, "parameters": [ - "", - "\"Left\"" + "" ] - }, - { - "type": { - "value": "BuiltinCommonInstructions::Once" - }, - "parameters": [] } ], "actions": [], @@ -3176,28 +3433,6 @@ "\"Left\"", "" ] - }, - { - "type": { - "inverted": true, - "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" - }, - "parameters": [ - "JumpButton", - "MultitouchButton", - "" - ] - }, - { - "type": { - "inverted": true, - "value": "ButtonStates::ButtonFSM::IsPressed" - }, - "parameters": [ - "ShootButton", - "ButtonFSM", - "" - ] } ], "actions": [ @@ -3210,6 +3445,76 @@ "" ] } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" + }, + "parameters": [ + "JumpButton", + "MultitouchButton", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "ShootButton", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TapTouchId", + "=", + "StartedTouchOrMouseId(0)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TapStartX", + "=", + "TouchX(TapTouchId, \"MobileControls\", 0)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TapStartY", + "=", + "TouchY(TapTouchId, \"MobileControls\", 0)" + ] + }, + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"TapTouch\"" + ] + } + ] + } ] } ] @@ -12739,7 +13044,6 @@ { "author": "PANDAKO-GitHub", "category": "Input", - "dimension": "", "extensionNamespace": "", "fullName": "Mouse Pointer Lock", "gdevelopVersion": ">=5.5.222", @@ -12747,8 +13051,8 @@ "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMTksMTkgMjQsMTkgMjQsMjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSI2LDIzIDExLDIzIDExLDE4ICIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTI0LDE5LjRjLTAuNywwLjgtMS40LDEuNi0yLjIsMi40Yy03LDctMTUuMywxMC4yLTE4LjUsN3MtMC4xLTExLjUsNy0xOC41czE1LjMtMTAuMiwxOC41LTcNCgljMS40LDEuNCwxLjYsMy42LDAuOCw2LjMiLz4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xMSwyMi41Yy0wLjMtMC4yLTAuNS0wLjUtMC44LTAuOGMtNy03LTEwLjItMTUuMy03LTE4LjVzMTEuNS0wLjEsMTguNSw3czEwLjIsMTUuMyw3LDE4LjUNCgljLTEuNywxLjctNC44LDEuNi04LjQsMC4xIi8+DQo8L3N2Zz4NCg==", "name": "MousePointerLock", "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Virtual Reality/Virtual Reality_360_rotate_vr_movement.svg", - "shortDescription": "This behavior removes the limit on the distance the mouse can move and hides the cursor.", - "version": "0.3.0", + "shortDescription": "Lock and hide mouse pointer for unlimited movement (e.g., FPS mouse look).", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -12796,7 +13100,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -13044,16 +13348,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -13200,7 +13534,7 @@ "functionType": "Condition", "name": "isEmulatingPointerLock", "private": true, - "sentence": "The mouse pointer is lock is emulated", + "sentence": "The mouse pointer lock is emulated", "events": [ { "type": "BuiltinCommonInstructions::Standard", @@ -13236,6 +13570,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -13517,68 +13876,12 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "onFirstSceneLoaded" - }, - { - "functionName": "onScenePostEvents" - }, - { - "functionName": "RequestPointerLock" - }, - { - "functionName": "ExitPointerLock" - }, - { - "functionName": "isPointerLocked" - }, - { - "functionName": "isPointerActuallyLocked" - }, - { - "functionName": "isEmulatingPointerLock" - }, - { - "functionName": "IsMoving" - }, - { - "functionName": "MovementX" - }, - { - "functionName": "SetMovementX" - }, - { - "functionName": "MovementY" - }, - { - "functionName": "SetMovementY" - }, - { - "functionName": "TouchX" - }, - { - "functionName": "TouchY" - }, - { - "functionName": "TouchSpeedFactor" - }, - { - "functionName": "SetTouchSpeedFactor" - } - ] - }, "eventsBasedBehaviors": [ { "description": "Control camera rotations with a mouse.", "fullName": "First person camera mouse mapper", - "helpPath": "", - "iconUrl": "", "name": "FirstPersonPointerMapper", "objectType": "", - "previewIconUrl": "", "eventsFunctions": [ { "fullName": "", @@ -13598,6 +13901,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -13619,7 +13934,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { @@ -13853,7 +14168,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "HorizontalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetHorizontalSpeed", "sentence": "", "events": [ @@ -13934,7 +14248,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalSpeed", "sentence": "", "events": [ @@ -14015,7 +14328,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMin", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMin", "sentence": "", "events": [ @@ -14096,7 +14408,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMax", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMax", "sentence": "", "events": [ @@ -14177,7 +14488,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "OffsetZ", - "group": "First person camera mouse mapper configuration", "name": "SetOffsetZ", "sentence": "", "events": [ @@ -14214,57 +14524,6 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "doStepPreEvents" - }, - { - "folderName": "Layers and cameras", - "children": [ - { - "functionName": "LookFromObjectEyes" - } - ] - }, - { - "folderName": "First person camera mouse mapper configuration", - "children": [ - { - "functionName": "HorizontalSpeed" - }, - { - "functionName": "SetHorizontalSpeed" - }, - { - "functionName": "VerticalSpeed" - }, - { - "functionName": "SetVerticalSpeed" - }, - { - "functionName": "VerticalAngleMin" - }, - { - "functionName": "SetVerticalAngleMin" - }, - { - "functionName": "VerticalAngleMax" - }, - { - "functionName": "SetVerticalAngleMax" - }, - { - "functionName": "OffsetZ" - }, - { - "functionName": "SetOffsetZ" - } - ] - } - ] - }, "propertyDescriptors": [ { "value": "", @@ -14318,32 +14577,7 @@ "name": "ShouldLock" } ], - "propertiesFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "propertyName": "Object3D" - }, - { - "propertyName": "HorizontalSpeed" - }, - { - "propertyName": "VerticalSpeed" - }, - { - "propertyName": "VerticalAngleMin" - }, - { - "propertyName": "VerticalAngleMax" - }, - { - "propertyName": "OffsetZ" - }, - { - "propertyName": "ShouldLock" - } - ] - } + "sharedPropertyDescriptors": [] } ], "eventsBasedObjects": [] @@ -45810,7 +46044,7 @@ "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", + "version": "0.2.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.", "", @@ -46910,8 +47144,17 @@ "vTerrainLocalXY = position.xy;", "vTerrainLocalZ = position.z;", "vTerrainWorldXY = (modelMatrix * vec4(transformed, 1.0)).xy * terrainWorldScale;", - "vTerrainUp = normalize(transpose(inverse(mat3(modelMatrix))) * objectNormal).z;", + "// The world normal, from the camera space normal (computed by Three.js with a", + "// matrix inverted on the CPU), instead of inverting a matrix for each vertex.", + "vTerrainUp = inverseTransformDirection(transformedNormal, viewMatrix).z;", "`;", + "/** @param {(layer: number) => string} getLayerCode */", + "const forEachLayerCode = (getLayerCode) => [0, 1, 2, 3].map(getLayerCode).join('\\n');", + "", + "// Noise is read from a texture of random values, repeated every", + "// NOISE_MAP_SIZE units of noise: 1 texture read instead of 16 hashes.", + "const NOISE_MAP_SIZE = 256;", + "", "const fragmentShaderDeclarations = `", "uniform sampler2D terrainSplatMap;", "uniform vec2 terrainSplatUvScaleAndOffset;", @@ -46920,11 +47163,11 @@ "uniform sampler2D terrainLayerMap1;", "uniform sampler2D terrainLayerMap2;", "uniform sampler2D terrainLayerMap3;", + "uniform vec3 terrainLayerAverageColors[4];", "uniform sampler2D terrainLayerNormalMap0;", "uniform sampler2D terrainLayerNormalMap1;", "uniform sampler2D terrainLayerNormalMap2;", "uniform sampler2D terrainLayerNormalMap3;", - "uniform float terrainHasNormalMaps;", "uniform float terrainNormalMapStrength;", "uniform float terrainTextureSize;", "uniform float terrainHeightBlending;", @@ -46933,15 +47176,13 @@ "uniform vec2 terrainCliffCosines;", "uniform vec4 terrainBrush;", "uniform sampler2D terrainColorMap;", + "uniform sampler2D terrainNoiseMap;", "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. */", @@ -46953,15 +47194,16 @@ " copyBump.xy = transpose(terrainCopyRotation) * copyBump.xy;", " return mix(bump, copyBump, patches);", "}", - "float terrainNoise(vec2 p) {", + "/**", + " * 4 independent value noises (one by channel), from 0 to 1, smoothly varying", + " * between random values at integer positions.", + " */", + "vec4 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", - " );", + " vec2 f = p - i;", + " // The read position is moved so that the linear interpolation of the", + " // texture between 2 random values becomes a smooth one.", + " return texture2D(terrainNoiseMap, (i + f * f * (3.0 - 2.0 * f) + 0.5) / ${NOISE_MAP_SIZE}.0);", "}", "`;", "// Holes are samples without any painted layer: the interpolated sum of the", @@ -46979,15 +47221,11 @@ "varying vec2 vTerrainLocalXY;", "`;", "", + "// Layers without texture, normal map, height blending or texture variation", + "// are compiled out (see TerrainRenderer.updateMaterial).", "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)", - ");", + "vec4 terrainJitter = terrainNoise(vTerrainWorldXY / 24.0);", "// 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;", @@ -46998,48 +47236,61 @@ "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;", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "vec3 terrainTexture${layer} = texture2D(terrainLayerMap${layer}, terrainUv).rgb;", + "#else", + "vec3 terrainTexture${layer} = vec3(1.0);", + "#endif`", + ")}", "", "// 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);", - "}", + "#ifdef TERRAIN_TEXTURE_VARIATION", + "// 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).x);", + "terrainUv2 = terrainCopyRotation * terrainUv + vec2(0.31, 0.73);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "terrainTexture${layer} = mix(terrainTexture${layer}, texture2D(terrainLayerMap${layer}, terrainUv2).rgb, terrainPatches);", + "#endif`", + ")}", + "terrainColorVariation += terrainTextureVariation * 0.3 *", + " (terrainNoise(terrainUv * 0.11).y + 0.5 * terrainNoise(terrainUv * 0.37 + 3.1).z - 0.75);", + "#endif", "", "// 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);", - "}", + "#ifdef TERRAIN_HEIGHT_BLENDING", + "// Averages are computed once on the CPU, or read from the smallest mipmap", + "// when the image can't be read by the CPU.", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MIPMAP_AVERAGE_${layer}", + "vec3 terrainAverage${layer} = textureLod(terrainLayerMap${layer}, vec2(0.5), 16.0).rgb;", + "#else", + "vec3 terrainAverage${layer} = terrainLayerAverageColors[${layer}];", + "#endif`", + ")}", + "const vec3 terrainLuminance = vec3(0.299, 0.587, 0.114);", + "vec4 terrainHeights = vec4(", + " dot(terrainTexture0 - terrainAverage0, terrainLuminance),", + " dot(terrainTexture1 - terrainAverage1, terrainLuminance),", + " dot(terrainTexture2 - terrainAverage2, terrainLuminance),", + " dot(terrainTexture3 - terrainAverage3, 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);", + "#endif", "", "vec3 terrainColor = terrainColorVariation * (", " terrainWeights.x * terrainLayerColors[0] * terrainTexture0 +", @@ -47071,26 +47322,30 @@ "`;", "", "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;", + "#ifdef TERRAIN_NORMAL_MAPS", + "// Layers without normal map are flat.", + "vec3 terrainMapNormal = vec3(0.0);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_NORMAL_MAP_${layer}", + "terrainMapNormal += terrainWeights[${layer}] * terrainSampleNormal(terrainLayerNormalMap${layer}, terrainUv, terrainUv2, terrainPatches);", + "#else", + "terrainMapNormal.z += terrainWeights[${layer}];", + "#endif`", + ")}", + "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);", - " // 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);", "}", + "terrainTangent = normalize(terrainTangent);", + "vec3 terrainBitangent = cross(normal, terrainTangent);", + "normal = normalize(mat3(terrainTangent, terrainBitangent, normal) * terrainMapNormal);", + "#endif", "`;", "", "let whiteTexture = null;", @@ -47111,7 +47366,30 @@ " return flatNormalTexture;", "};", "", + "let noiseTexture = null;", + "/** Random values, in 4 independent channels, read by `terrainNoise` in the shader. */", + "const getNoiseTexture = () => {", + " if (!noiseTexture) {", + " const random = createRandom(1);", + " const bytes = new Uint8Array(NOISE_MAP_SIZE * NOISE_MAP_SIZE * 4);", + " for (let index = 0; index < bytes.length; index++) {", + " bytes[index] = Math.floor(random() * 256);", + " }", + " noiseTexture = new THREE.DataTexture(bytes, NOISE_MAP_SIZE, NOISE_MAP_SIZE);", + " noiseTexture.wrapS = THREE.RepeatWrapping;", + " noiseTexture.wrapT = THREE.RepeatWrapping;", + " // No mipmaps: the shader moves the read position between samples.", + " noiseTexture.magFilter = THREE.LinearFilter;", + " noiseTexture.minFilter = THREE.LinearFilter;", + " noiseTexture.needsUpdate = true;", + " }", + " return noiseTexture;", + "};", + "", + "const DEFAULT_ANISOTROPY = 4;", + "/** @type {WeakMap>} */", "const layerTextures = new WeakMap();", + "/** @type {WeakMap>} */", "const layerNormalMaps = new WeakMap();", "/**", " * Textures of the image manager have no mipmaps: they would shimmer when", @@ -47119,12 +47397,18 @@ " * @param {gdjs.RuntimeGame} game", " * @param {string} resourceName", " * @param {boolean} isNormalMap Normal maps are directions, not colors.", + " * @param {number} anisotropy", " */", - "const getLayerTexture = (game, resourceName, isNormalMap) => {", + "const getLayerTexture = (game, resourceName, isNormalMap, anisotropy) => {", " if (!resourceName) return isNormalMap ? getFlatNormalTexture() : getWhiteTexture();", " const imageTexture = game.getImageManager().getThreeTexture(resourceName);", - " const textures = isNormalMap ? layerNormalMaps : layerTextures;", - " let texture = textures.get(imageTexture);", + " const texturesByImage = isNormalMap ? layerNormalMaps : layerTextures;", + " let texturesByAnisotropy = texturesByImage.get(imageTexture);", + " if (!texturesByAnisotropy) {", + " texturesByAnisotropy = new Map();", + " texturesByImage.set(imageTexture, texturesByAnisotropy);", + " }", + " let texture = texturesByAnisotropy.get(anisotropy);", " if (!texture) {", " texture = imageTexture.clone();", " if (isNormalMap) texture.colorSpace = THREE.NoColorSpace;", @@ -47132,13 +47416,71 @@ " texture.wrapT = THREE.RepeatWrapping;", " texture.minFilter = THREE.LinearMipmapLinearFilter;", " texture.generateMipmaps = true;", - " texture.anisotropy = 4;", + " texture.anisotropy = anisotropy;", " texture.needsUpdate = true;", - " textures.set(imageTexture, texture);", + " texturesByAnisotropy.set(anisotropy, texture);", " }", " return texture;", "};", "", + "/** @type {Float32Array | null} */", + "let srgbToLinearValues = null;", + "/** @type {WeakMap} */", + "const linearAverageColors = new WeakMap();", + "/**", + " * @param {THREE.Texture} texture", + " * @returns {THREE.Vector3 | null} The average color of the texture, in the", + " * linear color space (like colors read by shaders), or null if the image", + " * can't be read: not loaded yet, or from another domain without CORS.", + " */", + "const getLinearAverageColor = (texture) => {", + " const image = texture.image;", + " if (!image) return null;", + " const cachedColor = linearAverageColors.get(image);", + " if (cachedColor) return cachedColor;", + " 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 null;", + " const size = 64;", + " let pixels;", + " try {", + " const canvas = document.createElement('canvas');", + " canvas.width = size;", + " canvas.height = size;", + " const context = canvas.getContext('2d');", + " // Downscaled with filtering, so that all pixels are counted.", + " context.imageSmoothingEnabled = true;", + " context.imageSmoothingQuality = 'high';", + " context.drawImage(image, 0, 0, size, size);", + " pixels = context.getImageData(0, 0, size, size).data;", + " } catch (error) {", + " return null;", + " }", + " if (!srgbToLinearValues) {", + " srgbToLinearValues = new Float32Array(256);", + " for (let value = 0; value < 256; value++) {", + " const srgb = value / 255;", + " srgbToLinearValues[value] = srgb <= 0.04045 ? srgb / 12.92 : Math.pow((srgb + 0.055) / 1.055, 2.4);", + " }", + " }", + " const isSrgb = texture.colorSpace === THREE.SRGBColorSpace;", + " let red = 0;", + " let green = 0;", + " let blue = 0;", + " for (let index = 0; index < pixels.length; index += 4) {", + " red += isSrgb ? srgbToLinearValues[pixels[index]] : pixels[index] / 255;", + " green += isSrgb ? srgbToLinearValues[pixels[index + 1]] : pixels[index + 1] / 255;", + " blue += isSrgb ? srgbToLinearValues[pixels[index + 2]] : pixels[index + 2] / 255;", + " }", + " const count = size * size;", + " const color = new THREE.Vector3(red / count, green / count, blue / count);", + " linearAverageColors.set(image, color);", + " return color;", + "};", + "", "/**", " * A texture of 4 bytes per sample of a grid of `size * size` samples (layer", " * weights, tint and wetness...), interpolated between samples.", @@ -47197,11 +47539,14 @@ " terrainLayerMap1: { value: getWhiteTexture() },", " terrainLayerMap2: { value: getWhiteTexture() },", " terrainLayerMap3: { value: getWhiteTexture() },", + " // Used by the height blending, in the linear color space.", + " terrainLayerAverageColors: {", + " value: [new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1)],", + " },", " terrainLayerNormalMap0: { value: getFlatNormalTexture() },", " terrainLayerNormalMap1: { value: getFlatNormalTexture() },", " terrainLayerNormalMap2: { value: getFlatNormalTexture() },", " terrainLayerNormalMap3: { value: getFlatNormalTexture() },", - " terrainHasNormalMaps: { value: 0 },", " terrainNormalMapStrength: { value: 1 },", " terrainTextureSize: { value: 256 },", " terrainHeightBlending: { value: 0 },", @@ -47210,6 +47555,7 @@ " terrainCliffCosines: { value: new THREE.Vector2() },", " terrainBrush: { value: new THREE.Vector4() },", " terrainColorMap: { value: getWhiteTexture() },", + " terrainNoiseMap: { value: getNoiseTexture() },", " // 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.", @@ -47229,8 +47575,12 @@ " ['#include ', '#include \\n' + fragmentShaderNormalCode],", " ], 'Terrain3D');", " };", - " this.material.customProgramCacheKey = () => (this.hasHoles ? 'Terrain3DWithHoles' : 'Terrain3D');", " this.hasHoles = false;", + " /** The defines of the features used by the terrain (see `updateMaterial`), without holes. */", + " this.featureDefines = [];", + " /** All the defines of the shader, separated by semicolons. */", + " this.shaderKey = '';", + " this.material.customProgramCacheKey = () => 'Terrain3D;' + this.shaderKey;", " // Without them, holes would cast shadows.", " this.depthMaterial = this._createHoleDepthMaterial(new THREE.MeshDepthMaterial());", " this.distanceMaterial = this._createHoleDepthMaterial(new THREE.MeshDistanceMaterial());", @@ -47267,11 +47617,28 @@ " setHasHoles(hasHoles) {", " if (hasHoles === this.hasHoles) return;", " this.hasHoles = hasHoles;", + " this._updateShaderDefines();", + " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", + " }", + "", + " /**", + " * Compile the shader only with the features used by the terrain. It is", + " * compiled again only when they change: properties only change in the", + " * editor, holes can be made by actions.", + " */", + " _updateShaderDefines() {", + " const defines = this.hasHoles ? [...this.featureDefines, 'TERRAIN_HOLES'] : this.featureDefines;", + " const shaderKey = defines.join(';');", + " if (shaderKey === this.shaderKey) return;", " // The material has its own defines (STANDARD).", - " if (hasHoles) this.material.defines.TERRAIN_HOLES = '';", - " else delete this.material.defines.TERRAIN_HOLES;", + " for (const name of this.shaderKey ? this.shaderKey.split(';') : []) {", + " delete this.material.defines[name];", + " }", + " for (const name of defines) {", + " this.material.defines[name] = '';", + " }", + " this.shaderKey = shaderKey;", " this.material.needsUpdate = true;", - " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", " }", "", " /** @param {THREE.Mesh} mesh */", @@ -47503,16 +47870,38 @@ " object._getLayer3NormalMap(),", " object._getLayer4NormalMap(),", " ];", + " const anisotropy = Math.max(parseInt(object._getAnisotropy(), 10) || DEFAULT_ANISOTROPY, 1);", + " const heightBlending = clamp01(object._getHeightBlending());", + " const textureVariation = clamp01(object._getTextureVariation());", + " const featureDefines = [];", " 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);", + " const layerTexture = getLayerTexture(game, textures[layer], false, anisotropy);", + " this.uniforms['terrainLayerMap' + layer].value = layerTexture;", + " this.uniforms['terrainLayerNormalMap' + layer].value = getLayerTexture(game, normalMaps[layer], true, anisotropy);", + " const averageColor = this.uniforms.terrainLayerAverageColors.value[layer];", + " if (textures[layer]) {", + " featureDefines.push('TERRAIN_LAYER_MAP_' + layer);", + " const linearAverageColor = heightBlending > 0 ? getLinearAverageColor(layerTexture) : null;", + " if (linearAverageColor) {", + " averageColor.copy(linearAverageColor);", + " } else if (heightBlending > 0) {", + " featureDefines.push('TERRAIN_LAYER_MIPMAP_AVERAGE_' + layer);", + " }", + " } else {", + " averageColor.set(1, 1, 1);", + " }", + " if (normalMaps[layer]) featureDefines.push('TERRAIN_LAYER_NORMAL_MAP_' + layer);", " }", - " this.uniforms.terrainHasNormalMaps.value = normalMaps.some((normalMap) => !!normalMap) ? 1 : 0;", + " if (normalMaps.some((normalMap) => !!normalMap)) featureDefines.push('TERRAIN_NORMAL_MAPS');", + " if (heightBlending > 0) featureDefines.push('TERRAIN_HEIGHT_BLENDING');", + " if (textureVariation > 0) featureDefines.push('TERRAIN_TEXTURE_VARIATION');", + " this.featureDefines = featureDefines;", + " this._updateShaderDefines();", " 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());", + " this.uniforms.terrainHeightBlending.value = heightBlending;", + " this.uniforms.terrainTextureVariation.value = textureVariation;", "", " const cliffLayer = parseInt(object._getCliffLayer(), 10);", " const cliffAngle = gdjs.toRad(object._getCliffAngle());", @@ -48430,40 +48819,21 @@ " return typeof scene.getRenderer3DWorldScale === 'function' ? scene.getRenderer3DWorldScale() : 1;", "};", "", + "/** @type {WeakMap} */", "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).", + " * @returns {THREE.Color} The average color of the texture (white when it can't be read).", " */", "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 linearAverageColor = getLinearAverageColor(texture);", + " if (!linearAverageColor) return white || (white = new THREE.Color(1, 1, 1));", + " let color = averageColors.get(linearAverageColor);", + " if (!color) {", + " color = new THREE.Color(linearAverageColor.x, linearAverageColor.y, linearAverageColor.z);", + " averageColors.set(linearAverageColor, color);", " }", - " 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;", "};", "", @@ -52077,6 +52447,37 @@ "group": "Rendering", "name": "ReceiveShadow" }, + { + "value": "4", + "type": "Choice", + "label": "Anisotropic filtering", + "description": "Keeps layer textures sharp where the ground is seen at a low angle (like from a car). Lower is faster on slow graphics cards, but blurs the ground in the distance.", + "group": "Rendering", + "choices": [ + { + "label": "None (fastest)", + "value": "1" + }, + { + "label": "2x", + "value": "2" + }, + { + "label": "4x", + "value": "4" + }, + { + "label": "8x", + "value": "8" + }, + { + "label": "16x (sharpest)", + "value": "16" + } + ], + "advanced": true, + "name": "Anisotropy" + }, { "value": "", "type": "String", @@ -52190,6 +52591,9 @@ }, { "propertyName": "ReceiveShadow" + }, + { + "propertyName": "Anisotropy" } ] }, diff --git a/examples/starting-first-person-shooter-horror/theme-slots.json b/examples/starting-first-person-shooter-horror/theme-slots.json index e592de59b..f49394ca2 100644 --- a/examples/starting-first-person-shooter-horror/theme-slots.json +++ b/examples/starting-first-person-shooter-horror/theme-slots.json @@ -1,12 +1,11 @@ { "version": 1, "objects": { - "scene:Game Scene/Ground": { - "front": "env.ground", - "left": "env.wall", - "right": "env.wall", - "top": "env.wall", - "bottom": "env.wall" + "scene:Game Scene/Terrain": { + "Layer1Texture": { + "slot": "terrain.ground", + "normalMap": "Layer1NormalMap" + } }, "scene:Game Scene/Obstacle": { "front": "env.wall", @@ -26,6 +25,9 @@ "scene:Game Scene/PathfindingObstacle", "scene:Game Scene/Player", "object:Light3D::SpotLight3D/Placeholder", - "object:Light3D::PointLight3D/Placeholder" + "object:Light3D::PointLight3D/Placeholder", + "object:Terrain3D::Terrain3D/AreaPlaceholder", + "object:Terrain3D::TerrainGrass/AreaPlaceholder", + "object:Terrain3D::ScatteredModels/AreaPlaceholder" ] } diff --git a/examples/starting-first-person-shooter/starting-first-person-shooter.json b/examples/starting-first-person-shooter/starting-first-person-shooter.json index 018f097fc..05a010415 100644 --- a/examples/starting-first-person-shooter/starting-first-person-shooter.json +++ b/examples/starting-first-person-shooter/starting-first-person-shooter.json @@ -337,6 +337,30 @@ "persistentUuid": "f7dcc002-63ed-45a9-acf0-51088ff88d26", "type": "string", "value": "Keyboard" + }, + { + "name": "TapTouchId", + "persistentUuid": "b141fd05-d337-448e-84bd-bfee1266f43f", + "type": "number", + "value": 0 + }, + { + "name": "TapStartX", + "persistentUuid": "fe844a2e-8ea8-492a-ada1-198e11d3f53b", + "type": "number", + "value": 0 + }, + { + "name": "TapStartY", + "persistentUuid": "3e6804fc-6c63-4e44-be6e-d742aebb0f17", + "type": "number", + "value": 0 + }, + { + "name": "TapShot", + "persistentUuid": "fe218464-64fc-445e-bbc1-ab44977d61cb", + "type": "boolean", + "value": false } ], "instances": [ @@ -1788,6 +1812,125 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Keep shooting while the button is held, even when the finger drags away from it to aim." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"ShootCooldown\"" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "A quick tap anywhere outside the buttons also shoots. Dragging only aims." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "HasTouchEnded" + }, + "parameters": [ + "", + "TapTouchId" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "CompareTimer" + }, + "parameters": [ + "", + "\"TapTouch\"", + "<", + "0.25" + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::CompareNumbers" + }, + "parameters": [ + "DistanceBetweenPositions(TapStartX, TapStartY, TouchX(TapTouchId, \"MobileControls\", 0), TouchY(TapTouchId, \"MobileControls\", 0))", + "<", + "30" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "TapShot", + "True", + "" + ] + } + ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TapTouchId", + "=", + "0" + ] + } + ] + } + ] + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -1854,11 +1997,57 @@ }, { "type": { - "value": "ButtonStates::ButtonFSM::IsPressed" + "value": "BuiltinCommonInstructions::Or" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "ShootButton", + "ButtonFSM", + "" + ] + }, + { + "type": { + "value": "ButtonStates::ButtonFSM::IsPressedOutside" + }, + "parameters": [ + "ShootButton", + "ButtonFSM", + "" + ] + } + ] + } + ] + }, + { + "type": { + "value": "BuiltinCommonInstructions::And" + }, + "parameters": [], + "subInstructions": [ + { + "type": { + "value": "StringVariable" }, "parameters": [ - "ShootButton", - "ButtonFSM", + "ControlKind", + "=", + "\"Touch\"" + ] + }, + { + "type": { + "value": "BooleanVariable" + }, + "parameters": [ + "TapShot", + "True", "" ] } @@ -1868,12 +2057,26 @@ }, { "type": { - "value": "BuiltinCommonInstructions::Once" + "value": "CompareTimer" }, - "parameters": [] + "parameters": [ + "", + "\"ShootCooldown\"", + ">=", + "0.2" + ] } ], "actions": [ + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"ShootCooldown\"" + ] + }, { "type": { "value": "PlaySound" @@ -1976,6 +2179,22 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [], + "actions": [ + { + "type": { + "value": "SetBooleanVariable" + }, + "parameters": [ + "TapShot", + "False", + "" + ] + } + ] } ], "parameters": [] @@ -1990,6 +2209,51 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Start with touch controls on touchscreens." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "SystemInfo::HasTouchScreen" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::SetChecked" + }, + "parameters": [ + "ControlsToggle", + "yes", + "" + ] + } + ] + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -2233,18 +2497,11 @@ "conditions": [ { "type": { - "value": "MouseButtonFromTextPressed" + "value": "HasAnyTouchOrMouseStarted" }, "parameters": [ - "", - "\"Left\"" + "" ] - }, - { - "type": { - "value": "BuiltinCommonInstructions::Once" - }, - "parameters": [] } ], "actions": [], @@ -2263,28 +2520,6 @@ "\"Left\"", "" ] - }, - { - "type": { - "inverted": true, - "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" - }, - "parameters": [ - "JumpButton", - "MultitouchButton", - "" - ] - }, - { - "type": { - "inverted": true, - "value": "ButtonStates::ButtonFSM::IsPressed" - }, - "parameters": [ - "ShootButton", - "ButtonFSM", - "" - ] } ], "actions": [ @@ -2297,6 +2532,76 @@ "" ] } + ], + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "inverted": true, + "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" + }, + "parameters": [ + "JumpButton", + "MultitouchButton", + "" + ] + }, + { + "type": { + "inverted": true, + "value": "ButtonStates::ButtonFSM::IsPressed" + }, + "parameters": [ + "ShootButton", + "ButtonFSM", + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TapTouchId", + "=", + "StartedTouchOrMouseId(0)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TapStartX", + "=", + "TouchX(TapTouchId, \"MobileControls\", 0)" + ] + }, + { + "type": { + "value": "SetNumberVariable" + }, + "parameters": [ + "TapStartY", + "=", + "TouchY(TapTouchId, \"MobileControls\", 0)" + ] + }, + { + "type": { + "value": "ResetTimer" + }, + "parameters": [ + "", + "\"TapTouch\"" + ] + } + ] + } ] } ] @@ -11832,7 +12137,6 @@ { "author": "PANDAKO-GitHub", "category": "Input", - "dimension": "", "extensionNamespace": "", "fullName": "Mouse Pointer Lock", "gdevelopVersion": ">=5.5.222", @@ -11840,8 +12144,8 @@ "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMTksMTkgMjQsMTkgMjQsMjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSI2LDIzIDExLDIzIDExLDE4ICIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTI0LDE5LjRjLTAuNywwLjgtMS40LDEuNi0yLjIsMi40Yy03LDctMTUuMywxMC4yLTE4LjUsN3MtMC4xLTExLjUsNy0xOC41czE1LjMtMTAuMiwxOC41LTcNCgljMS40LDEuNCwxLjYsMy42LDAuOCw2LjMiLz4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xMSwyMi41Yy0wLjMtMC4yLTAuNS0wLjUtMC44LTAuOGMtNy03LTEwLjItMTUuMy03LTE4LjVzMTEuNS0wLjEsMTguNSw3czEwLjIsMTUuMyw3LDE4LjUNCgljLTEuNywxLjctNC44LDEuNi04LjQsMC4xIi8+DQo8L3N2Zz4NCg==", "name": "MousePointerLock", "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Virtual Reality/Virtual Reality_360_rotate_vr_movement.svg", - "shortDescription": "This behavior removes the limit on the distance the mouse can move and hides the cursor.", - "version": "0.3.0", + "shortDescription": "Lock and hide mouse pointer for unlimited movement (e.g., FPS mouse look).", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -11889,7 +12193,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -12137,16 +12441,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -12293,7 +12627,7 @@ "functionType": "Condition", "name": "isEmulatingPointerLock", "private": true, - "sentence": "The mouse pointer is lock is emulated", + "sentence": "The mouse pointer lock is emulated", "events": [ { "type": "BuiltinCommonInstructions::Standard", @@ -12329,6 +12663,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -12610,68 +12969,12 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "onFirstSceneLoaded" - }, - { - "functionName": "onScenePostEvents" - }, - { - "functionName": "RequestPointerLock" - }, - { - "functionName": "ExitPointerLock" - }, - { - "functionName": "isPointerLocked" - }, - { - "functionName": "isPointerActuallyLocked" - }, - { - "functionName": "isEmulatingPointerLock" - }, - { - "functionName": "IsMoving" - }, - { - "functionName": "MovementX" - }, - { - "functionName": "SetMovementX" - }, - { - "functionName": "MovementY" - }, - { - "functionName": "SetMovementY" - }, - { - "functionName": "TouchX" - }, - { - "functionName": "TouchY" - }, - { - "functionName": "TouchSpeedFactor" - }, - { - "functionName": "SetTouchSpeedFactor" - } - ] - }, "eventsBasedBehaviors": [ { "description": "Control camera rotations with a mouse.", "fullName": "First person camera mouse mapper", - "helpPath": "", - "iconUrl": "", "name": "FirstPersonPointerMapper", "objectType": "", - "previewIconUrl": "", "eventsFunctions": [ { "fullName": "", @@ -12691,6 +12994,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -12712,7 +13027,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { @@ -12946,7 +13261,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "HorizontalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetHorizontalSpeed", "sentence": "", "events": [ @@ -13027,7 +13341,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalSpeed", "sentence": "", "events": [ @@ -13108,7 +13421,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMin", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMin", "sentence": "", "events": [ @@ -13189,7 +13501,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMax", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMax", "sentence": "", "events": [ @@ -13270,7 +13581,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "OffsetZ", - "group": "First person camera mouse mapper configuration", "name": "SetOffsetZ", "sentence": "", "events": [ @@ -13307,57 +13617,6 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "doStepPreEvents" - }, - { - "folderName": "Layers and cameras", - "children": [ - { - "functionName": "LookFromObjectEyes" - } - ] - }, - { - "folderName": "First person camera mouse mapper configuration", - "children": [ - { - "functionName": "HorizontalSpeed" - }, - { - "functionName": "SetHorizontalSpeed" - }, - { - "functionName": "VerticalSpeed" - }, - { - "functionName": "SetVerticalSpeed" - }, - { - "functionName": "VerticalAngleMin" - }, - { - "functionName": "SetVerticalAngleMin" - }, - { - "functionName": "VerticalAngleMax" - }, - { - "functionName": "SetVerticalAngleMax" - }, - { - "functionName": "OffsetZ" - }, - { - "functionName": "SetOffsetZ" - } - ] - } - ] - }, "propertyDescriptors": [ { "value": "", @@ -13411,32 +13670,7 @@ "name": "ShouldLock" } ], - "propertiesFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "propertyName": "Object3D" - }, - { - "propertyName": "HorizontalSpeed" - }, - { - "propertyName": "VerticalSpeed" - }, - { - "propertyName": "VerticalAngleMin" - }, - { - "propertyName": "VerticalAngleMax" - }, - { - "propertyName": "OffsetZ" - }, - { - "propertyName": "ShouldLock" - } - ] - } + "sharedPropertyDescriptors": [] } ], "eventsBasedObjects": [] @@ -39819,7 +40053,7 @@ "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", + "version": "0.2.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.", "", @@ -40919,8 +41153,17 @@ "vTerrainLocalXY = position.xy;", "vTerrainLocalZ = position.z;", "vTerrainWorldXY = (modelMatrix * vec4(transformed, 1.0)).xy * terrainWorldScale;", - "vTerrainUp = normalize(transpose(inverse(mat3(modelMatrix))) * objectNormal).z;", + "// The world normal, from the camera space normal (computed by Three.js with a", + "// matrix inverted on the CPU), instead of inverting a matrix for each vertex.", + "vTerrainUp = inverseTransformDirection(transformedNormal, viewMatrix).z;", "`;", + "/** @param {(layer: number) => string} getLayerCode */", + "const forEachLayerCode = (getLayerCode) => [0, 1, 2, 3].map(getLayerCode).join('\\n');", + "", + "// Noise is read from a texture of random values, repeated every", + "// NOISE_MAP_SIZE units of noise: 1 texture read instead of 16 hashes.", + "const NOISE_MAP_SIZE = 256;", + "", "const fragmentShaderDeclarations = `", "uniform sampler2D terrainSplatMap;", "uniform vec2 terrainSplatUvScaleAndOffset;", @@ -40929,11 +41172,11 @@ "uniform sampler2D terrainLayerMap1;", "uniform sampler2D terrainLayerMap2;", "uniform sampler2D terrainLayerMap3;", + "uniform vec3 terrainLayerAverageColors[4];", "uniform sampler2D terrainLayerNormalMap0;", "uniform sampler2D terrainLayerNormalMap1;", "uniform sampler2D terrainLayerNormalMap2;", "uniform sampler2D terrainLayerNormalMap3;", - "uniform float terrainHasNormalMaps;", "uniform float terrainNormalMapStrength;", "uniform float terrainTextureSize;", "uniform float terrainHeightBlending;", @@ -40942,15 +41185,13 @@ "uniform vec2 terrainCliffCosines;", "uniform vec4 terrainBrush;", "uniform sampler2D terrainColorMap;", + "uniform sampler2D terrainNoiseMap;", "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. */", @@ -40962,15 +41203,16 @@ " copyBump.xy = transpose(terrainCopyRotation) * copyBump.xy;", " return mix(bump, copyBump, patches);", "}", - "float terrainNoise(vec2 p) {", + "/**", + " * 4 independent value noises (one by channel), from 0 to 1, smoothly varying", + " * between random values at integer positions.", + " */", + "vec4 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", - " );", + " vec2 f = p - i;", + " // The read position is moved so that the linear interpolation of the", + " // texture between 2 random values becomes a smooth one.", + " return texture2D(terrainNoiseMap, (i + f * f * (3.0 - 2.0 * f) + 0.5) / ${NOISE_MAP_SIZE}.0);", "}", "`;", "// Holes are samples without any painted layer: the interpolated sum of the", @@ -40988,15 +41230,11 @@ "varying vec2 vTerrainLocalXY;", "`;", "", + "// Layers without texture, normal map, height blending or texture variation", + "// are compiled out (see TerrainRenderer.updateMaterial).", "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)", - ");", + "vec4 terrainJitter = terrainNoise(vTerrainWorldXY / 24.0);", "// 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;", @@ -41007,48 +41245,61 @@ "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;", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "vec3 terrainTexture${layer} = texture2D(terrainLayerMap${layer}, terrainUv).rgb;", + "#else", + "vec3 terrainTexture${layer} = vec3(1.0);", + "#endif`", + ")}", "", "// 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);", - "}", + "#ifdef TERRAIN_TEXTURE_VARIATION", + "// 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).x);", + "terrainUv2 = terrainCopyRotation * terrainUv + vec2(0.31, 0.73);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "terrainTexture${layer} = mix(terrainTexture${layer}, texture2D(terrainLayerMap${layer}, terrainUv2).rgb, terrainPatches);", + "#endif`", + ")}", + "terrainColorVariation += terrainTextureVariation * 0.3 *", + " (terrainNoise(terrainUv * 0.11).y + 0.5 * terrainNoise(terrainUv * 0.37 + 3.1).z - 0.75);", + "#endif", "", "// 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);", - "}", + "#ifdef TERRAIN_HEIGHT_BLENDING", + "// Averages are computed once on the CPU, or read from the smallest mipmap", + "// when the image can't be read by the CPU.", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MIPMAP_AVERAGE_${layer}", + "vec3 terrainAverage${layer} = textureLod(terrainLayerMap${layer}, vec2(0.5), 16.0).rgb;", + "#else", + "vec3 terrainAverage${layer} = terrainLayerAverageColors[${layer}];", + "#endif`", + ")}", + "const vec3 terrainLuminance = vec3(0.299, 0.587, 0.114);", + "vec4 terrainHeights = vec4(", + " dot(terrainTexture0 - terrainAverage0, terrainLuminance),", + " dot(terrainTexture1 - terrainAverage1, terrainLuminance),", + " dot(terrainTexture2 - terrainAverage2, terrainLuminance),", + " dot(terrainTexture3 - terrainAverage3, 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);", + "#endif", "", "vec3 terrainColor = terrainColorVariation * (", " terrainWeights.x * terrainLayerColors[0] * terrainTexture0 +", @@ -41080,26 +41331,30 @@ "`;", "", "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;", + "#ifdef TERRAIN_NORMAL_MAPS", + "// Layers without normal map are flat.", + "vec3 terrainMapNormal = vec3(0.0);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_NORMAL_MAP_${layer}", + "terrainMapNormal += terrainWeights[${layer}] * terrainSampleNormal(terrainLayerNormalMap${layer}, terrainUv, terrainUv2, terrainPatches);", + "#else", + "terrainMapNormal.z += terrainWeights[${layer}];", + "#endif`", + ")}", + "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);", - " // 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);", "}", + "terrainTangent = normalize(terrainTangent);", + "vec3 terrainBitangent = cross(normal, terrainTangent);", + "normal = normalize(mat3(terrainTangent, terrainBitangent, normal) * terrainMapNormal);", + "#endif", "`;", "", "let whiteTexture = null;", @@ -41120,7 +41375,30 @@ " return flatNormalTexture;", "};", "", + "let noiseTexture = null;", + "/** Random values, in 4 independent channels, read by `terrainNoise` in the shader. */", + "const getNoiseTexture = () => {", + " if (!noiseTexture) {", + " const random = createRandom(1);", + " const bytes = new Uint8Array(NOISE_MAP_SIZE * NOISE_MAP_SIZE * 4);", + " for (let index = 0; index < bytes.length; index++) {", + " bytes[index] = Math.floor(random() * 256);", + " }", + " noiseTexture = new THREE.DataTexture(bytes, NOISE_MAP_SIZE, NOISE_MAP_SIZE);", + " noiseTexture.wrapS = THREE.RepeatWrapping;", + " noiseTexture.wrapT = THREE.RepeatWrapping;", + " // No mipmaps: the shader moves the read position between samples.", + " noiseTexture.magFilter = THREE.LinearFilter;", + " noiseTexture.minFilter = THREE.LinearFilter;", + " noiseTexture.needsUpdate = true;", + " }", + " return noiseTexture;", + "};", + "", + "const DEFAULT_ANISOTROPY = 4;", + "/** @type {WeakMap>} */", "const layerTextures = new WeakMap();", + "/** @type {WeakMap>} */", "const layerNormalMaps = new WeakMap();", "/**", " * Textures of the image manager have no mipmaps: they would shimmer when", @@ -41128,12 +41406,18 @@ " * @param {gdjs.RuntimeGame} game", " * @param {string} resourceName", " * @param {boolean} isNormalMap Normal maps are directions, not colors.", + " * @param {number} anisotropy", " */", - "const getLayerTexture = (game, resourceName, isNormalMap) => {", + "const getLayerTexture = (game, resourceName, isNormalMap, anisotropy) => {", " if (!resourceName) return isNormalMap ? getFlatNormalTexture() : getWhiteTexture();", " const imageTexture = game.getImageManager().getThreeTexture(resourceName);", - " const textures = isNormalMap ? layerNormalMaps : layerTextures;", - " let texture = textures.get(imageTexture);", + " const texturesByImage = isNormalMap ? layerNormalMaps : layerTextures;", + " let texturesByAnisotropy = texturesByImage.get(imageTexture);", + " if (!texturesByAnisotropy) {", + " texturesByAnisotropy = new Map();", + " texturesByImage.set(imageTexture, texturesByAnisotropy);", + " }", + " let texture = texturesByAnisotropy.get(anisotropy);", " if (!texture) {", " texture = imageTexture.clone();", " if (isNormalMap) texture.colorSpace = THREE.NoColorSpace;", @@ -41141,13 +41425,71 @@ " texture.wrapT = THREE.RepeatWrapping;", " texture.minFilter = THREE.LinearMipmapLinearFilter;", " texture.generateMipmaps = true;", - " texture.anisotropy = 4;", + " texture.anisotropy = anisotropy;", " texture.needsUpdate = true;", - " textures.set(imageTexture, texture);", + " texturesByAnisotropy.set(anisotropy, texture);", " }", " return texture;", "};", "", + "/** @type {Float32Array | null} */", + "let srgbToLinearValues = null;", + "/** @type {WeakMap} */", + "const linearAverageColors = new WeakMap();", + "/**", + " * @param {THREE.Texture} texture", + " * @returns {THREE.Vector3 | null} The average color of the texture, in the", + " * linear color space (like colors read by shaders), or null if the image", + " * can't be read: not loaded yet, or from another domain without CORS.", + " */", + "const getLinearAverageColor = (texture) => {", + " const image = texture.image;", + " if (!image) return null;", + " const cachedColor = linearAverageColors.get(image);", + " if (cachedColor) return cachedColor;", + " 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 null;", + " const size = 64;", + " let pixels;", + " try {", + " const canvas = document.createElement('canvas');", + " canvas.width = size;", + " canvas.height = size;", + " const context = canvas.getContext('2d');", + " // Downscaled with filtering, so that all pixels are counted.", + " context.imageSmoothingEnabled = true;", + " context.imageSmoothingQuality = 'high';", + " context.drawImage(image, 0, 0, size, size);", + " pixels = context.getImageData(0, 0, size, size).data;", + " } catch (error) {", + " return null;", + " }", + " if (!srgbToLinearValues) {", + " srgbToLinearValues = new Float32Array(256);", + " for (let value = 0; value < 256; value++) {", + " const srgb = value / 255;", + " srgbToLinearValues[value] = srgb <= 0.04045 ? srgb / 12.92 : Math.pow((srgb + 0.055) / 1.055, 2.4);", + " }", + " }", + " const isSrgb = texture.colorSpace === THREE.SRGBColorSpace;", + " let red = 0;", + " let green = 0;", + " let blue = 0;", + " for (let index = 0; index < pixels.length; index += 4) {", + " red += isSrgb ? srgbToLinearValues[pixels[index]] : pixels[index] / 255;", + " green += isSrgb ? srgbToLinearValues[pixels[index + 1]] : pixels[index + 1] / 255;", + " blue += isSrgb ? srgbToLinearValues[pixels[index + 2]] : pixels[index + 2] / 255;", + " }", + " const count = size * size;", + " const color = new THREE.Vector3(red / count, green / count, blue / count);", + " linearAverageColors.set(image, color);", + " return color;", + "};", + "", "/**", " * A texture of 4 bytes per sample of a grid of `size * size` samples (layer", " * weights, tint and wetness...), interpolated between samples.", @@ -41206,11 +41548,14 @@ " terrainLayerMap1: { value: getWhiteTexture() },", " terrainLayerMap2: { value: getWhiteTexture() },", " terrainLayerMap3: { value: getWhiteTexture() },", + " // Used by the height blending, in the linear color space.", + " terrainLayerAverageColors: {", + " value: [new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1)],", + " },", " terrainLayerNormalMap0: { value: getFlatNormalTexture() },", " terrainLayerNormalMap1: { value: getFlatNormalTexture() },", " terrainLayerNormalMap2: { value: getFlatNormalTexture() },", " terrainLayerNormalMap3: { value: getFlatNormalTexture() },", - " terrainHasNormalMaps: { value: 0 },", " terrainNormalMapStrength: { value: 1 },", " terrainTextureSize: { value: 256 },", " terrainHeightBlending: { value: 0 },", @@ -41219,6 +41564,7 @@ " terrainCliffCosines: { value: new THREE.Vector2() },", " terrainBrush: { value: new THREE.Vector4() },", " terrainColorMap: { value: getWhiteTexture() },", + " terrainNoiseMap: { value: getNoiseTexture() },", " // 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.", @@ -41238,8 +41584,12 @@ " ['#include ', '#include \\n' + fragmentShaderNormalCode],", " ], 'Terrain3D');", " };", - " this.material.customProgramCacheKey = () => (this.hasHoles ? 'Terrain3DWithHoles' : 'Terrain3D');", " this.hasHoles = false;", + " /** The defines of the features used by the terrain (see `updateMaterial`), without holes. */", + " this.featureDefines = [];", + " /** All the defines of the shader, separated by semicolons. */", + " this.shaderKey = '';", + " this.material.customProgramCacheKey = () => 'Terrain3D;' + this.shaderKey;", " // Without them, holes would cast shadows.", " this.depthMaterial = this._createHoleDepthMaterial(new THREE.MeshDepthMaterial());", " this.distanceMaterial = this._createHoleDepthMaterial(new THREE.MeshDistanceMaterial());", @@ -41276,11 +41626,28 @@ " setHasHoles(hasHoles) {", " if (hasHoles === this.hasHoles) return;", " this.hasHoles = hasHoles;", + " this._updateShaderDefines();", + " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", + " }", + "", + " /**", + " * Compile the shader only with the features used by the terrain. It is", + " * compiled again only when they change: properties only change in the", + " * editor, holes can be made by actions.", + " */", + " _updateShaderDefines() {", + " const defines = this.hasHoles ? [...this.featureDefines, 'TERRAIN_HOLES'] : this.featureDefines;", + " const shaderKey = defines.join(';');", + " if (shaderKey === this.shaderKey) return;", " // The material has its own defines (STANDARD).", - " if (hasHoles) this.material.defines.TERRAIN_HOLES = '';", - " else delete this.material.defines.TERRAIN_HOLES;", + " for (const name of this.shaderKey ? this.shaderKey.split(';') : []) {", + " delete this.material.defines[name];", + " }", + " for (const name of defines) {", + " this.material.defines[name] = '';", + " }", + " this.shaderKey = shaderKey;", " this.material.needsUpdate = true;", - " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", " }", "", " /** @param {THREE.Mesh} mesh */", @@ -41512,16 +41879,38 @@ " object._getLayer3NormalMap(),", " object._getLayer4NormalMap(),", " ];", + " const anisotropy = Math.max(parseInt(object._getAnisotropy(), 10) || DEFAULT_ANISOTROPY, 1);", + " const heightBlending = clamp01(object._getHeightBlending());", + " const textureVariation = clamp01(object._getTextureVariation());", + " const featureDefines = [];", " 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);", + " const layerTexture = getLayerTexture(game, textures[layer], false, anisotropy);", + " this.uniforms['terrainLayerMap' + layer].value = layerTexture;", + " this.uniforms['terrainLayerNormalMap' + layer].value = getLayerTexture(game, normalMaps[layer], true, anisotropy);", + " const averageColor = this.uniforms.terrainLayerAverageColors.value[layer];", + " if (textures[layer]) {", + " featureDefines.push('TERRAIN_LAYER_MAP_' + layer);", + " const linearAverageColor = heightBlending > 0 ? getLinearAverageColor(layerTexture) : null;", + " if (linearAverageColor) {", + " averageColor.copy(linearAverageColor);", + " } else if (heightBlending > 0) {", + " featureDefines.push('TERRAIN_LAYER_MIPMAP_AVERAGE_' + layer);", + " }", + " } else {", + " averageColor.set(1, 1, 1);", + " }", + " if (normalMaps[layer]) featureDefines.push('TERRAIN_LAYER_NORMAL_MAP_' + layer);", " }", - " this.uniforms.terrainHasNormalMaps.value = normalMaps.some((normalMap) => !!normalMap) ? 1 : 0;", + " if (normalMaps.some((normalMap) => !!normalMap)) featureDefines.push('TERRAIN_NORMAL_MAPS');", + " if (heightBlending > 0) featureDefines.push('TERRAIN_HEIGHT_BLENDING');", + " if (textureVariation > 0) featureDefines.push('TERRAIN_TEXTURE_VARIATION');", + " this.featureDefines = featureDefines;", + " this._updateShaderDefines();", " 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());", + " this.uniforms.terrainHeightBlending.value = heightBlending;", + " this.uniforms.terrainTextureVariation.value = textureVariation;", "", " const cliffLayer = parseInt(object._getCliffLayer(), 10);", " const cliffAngle = gdjs.toRad(object._getCliffAngle());", @@ -42439,40 +42828,21 @@ " return typeof scene.getRenderer3DWorldScale === 'function' ? scene.getRenderer3DWorldScale() : 1;", "};", "", + "/** @type {WeakMap} */", "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).", + " * @returns {THREE.Color} The average color of the texture (white when it can't be read).", " */", "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 linearAverageColor = getLinearAverageColor(texture);", + " if (!linearAverageColor) return white || (white = new THREE.Color(1, 1, 1));", + " let color = averageColors.get(linearAverageColor);", + " if (!color) {", + " color = new THREE.Color(linearAverageColor.x, linearAverageColor.y, linearAverageColor.z);", + " averageColors.set(linearAverageColor, color);", " }", - " 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;", "};", "", @@ -46086,6 +46456,37 @@ "group": "Rendering", "name": "ReceiveShadow" }, + { + "value": "4", + "type": "Choice", + "label": "Anisotropic filtering", + "description": "Keeps layer textures sharp where the ground is seen at a low angle (like from a car). Lower is faster on slow graphics cards, but blurs the ground in the distance.", + "group": "Rendering", + "choices": [ + { + "label": "None (fastest)", + "value": "1" + }, + { + "label": "2x", + "value": "2" + }, + { + "label": "4x", + "value": "4" + }, + { + "label": "8x", + "value": "8" + }, + { + "label": "16x (sharpest)", + "value": "16" + } + ], + "advanced": true, + "name": "Anisotropy" + }, { "value": "", "type": "String", @@ -46199,6 +46600,9 @@ }, { "propertyName": "ReceiveShadow" + }, + { + "propertyName": "Anisotropy" } ] }, diff --git a/examples/starting-first-person-shooter/theme-slots.json b/examples/starting-first-person-shooter/theme-slots.json index cef9a0271..fd5c0222f 100644 --- a/examples/starting-first-person-shooter/theme-slots.json +++ b/examples/starting-first-person-shooter/theme-slots.json @@ -1,12 +1,11 @@ { "version": 1, "objects": { - "scene:Game Scene/Ground": { - "front": "env.ground", - "left": "env.wall", - "right": "env.wall", - "top": "env.wall", - "bottom": "env.wall" + "scene:Game Scene/Terrain": { + "Layer1Texture": { + "slot": "terrain.ground", + "normalMap": "Layer1NormalMap" + } }, "scene:Game Scene/Obstacle": { "front": "env.wall", @@ -23,6 +22,9 @@ "effect:Game Scene//SkyBox": "sky.day" }, "ignoredObjects": [ - "scene:Game Scene/Player" + "scene:Game Scene/Player", + "object:Terrain3D::Terrain3D/AreaPlaceholder", + "object:Terrain3D::TerrainGrass/AreaPlaceholder", + "object:Terrain3D::ScatteredModels/AreaPlaceholder" ] } diff --git a/examples/starting-first-person-survival-crafting/starting-first-person-survival-crafting.json b/examples/starting-first-person-survival-crafting/starting-first-person-survival-crafting.json index 5337fed24..503f431ed 100644 --- a/examples/starting-first-person-survival-crafting/starting-first-person-survival-crafting.json +++ b/examples/starting-first-person-survival-crafting/starting-first-person-survival-crafting.json @@ -351,6 +351,9 @@ "objects": [ { "name": "Obstacle" + }, + { + "name": "Terrain" } ] }, @@ -2341,6 +2344,51 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Start with touch controls on touchscreens." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "SystemInfo::HasTouchScreen" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::SetChecked" + }, + "parameters": [ + "ControlsToggle", + "yes", + "" + ] + } + ] + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -2583,18 +2631,11 @@ "conditions": [ { "type": { - "value": "MouseButtonFromTextPressed" + "value": "HasAnyTouchOrMouseStarted" }, "parameters": [ - "", - "\"Left\"" + "" ] - }, - { - "type": { - "value": "BuiltinCommonInstructions::Once" - }, - "parameters": [] } ], "actions": [], @@ -2614,17 +2655,6 @@ "" ] }, - { - "type": { - "inverted": true, - "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" - }, - "parameters": [ - "JumpButton", - "MultitouchButton", - "" - ] - }, { "type": { "inverted": true, @@ -4846,6 +4876,51 @@ "// the test results.", "await harness.takeScreenshot('the inventory holding the log that was harvested');" ] + }, + { + "description": "Holding a log and looking down at the ground: the log is shown on the terrain, where the ray from the camera hits it.", + "name": "A held item is shown on the ground where the player looks", + "type": "gameplay", + "source": [ + "// Held items are shown, then placed, where the ray from the center of the", + "// camera hits the ground (the \"RaycastSurface\" group, which the terrain is part of).", + "await harness.goToScene('Game Scene');", + "harness.setTimeoutMs(60000);", + "await harness.stepFrames(6);", + "", + "// Arrange: a log in the selected inventory slot, as if it was harvested.", + "// The obstacles are taken out of the level, so that the ray can only hit the terrain.", + "for (const object of harness.getObjects('Obstacle')) harness.removeObject(object.id);", + "harness.setSceneVariable('Inventory[0].Name', 'Log');", + "harness.setSceneVariable('Inventory[0].Quantity', 1);", + "harness.setSceneVariable('SelectedSlot', 0);", + "harness.setSceneVariable('UpdateInventoryDisplay', true);", + "await harness.stepFrames(2);", + "// The level can have other logs lying around: the held one is the one marked as held.", + "const getHeldItem = () =>", + " harness.getObjects('Log').find((object) => {", + " const held = harness.getObjectVariable(object.id, 'Held');", + " return !!held && held.value === true;", + " });", + "harness.assert(!!getHeldItem(), 'The log of the selected slot is held.');", + "", + "// Look down at the ground in front of the player.", + "harness.getRuntimeObject(harness.getObjects('Player')[0].id).setRotationY(40);", + "await harness.stepFrames(2);", + "const heldItem = getHeldItem();", + "const terrain = harness.getRuntimeObject(harness.getObjects('Terrain')[0].id).__terrain3D;", + "const groundZ = terrain.getHeightAt(heldItem.x, heldItem.y);", + "console.log(", + " 'heldItem=' + Math.round(heldItem.x) + ';' + Math.round(heldItem.y) + ';' + heldItem.z.toFixed(1) +", + " ' hidden=' + heldItem.hidden + ' groundZ=' + groundZ.toFixed(1)", + ");", + "harness.assert(!heldItem.hidden, 'The held log is shown where the player looks at the ground.');", + "harness.assert(", + " Math.abs(heldItem.z - groundZ) < 3,", + " 'The held log is on the terrain (at Z ' + heldItem.z.toFixed(1) + ', the ground being at ' + groundZ.toFixed(1) + ').'", + ");", + "await harness.takeScreenshot('the held log shown on the ground');" + ] } ], "eventsFunctionsExtensions": [ @@ -13847,7 +13922,6 @@ { "author": "PANDAKO-GitHub", "category": "Input", - "dimension": "", "extensionNamespace": "", "fullName": "Mouse Pointer Lock", "gdevelopVersion": ">=5.5.222", @@ -13855,8 +13929,8 @@ "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMTksMTkgMjQsMTkgMjQsMjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSI2LDIzIDExLDIzIDExLDE4ICIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTI0LDE5LjRjLTAuNywwLjgtMS40LDEuNi0yLjIsMi40Yy03LDctMTUuMywxMC4yLTE4LjUsN3MtMC4xLTExLjUsNy0xOC41czE1LjMtMTAuMiwxOC41LTcNCgljMS40LDEuNCwxLjYsMy42LDAuOCw2LjMiLz4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xMSwyMi41Yy0wLjMtMC4yLTAuNS0wLjUtMC44LTAuOGMtNy03LTEwLjItMTUuMy03LTE4LjVzMTEuNS0wLjEsMTguNSw3czEwLjIsMTUuMyw3LDE4LjUNCgljLTEuNywxLjctNC44LDEuNi04LjQsMC4xIi8+DQo8L3N2Zz4NCg==", "name": "MousePointerLock", "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Virtual Reality/Virtual Reality_360_rotate_vr_movement.svg", - "shortDescription": "This behavior removes the limit on the distance the mouse can move and hides the cursor.", - "version": "0.3.0", + "shortDescription": "Lock and hide mouse pointer for unlimited movement (e.g., FPS mouse look).", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -13904,7 +13978,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -14152,16 +14226,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -14308,7 +14412,7 @@ "functionType": "Condition", "name": "isEmulatingPointerLock", "private": true, - "sentence": "The mouse pointer is lock is emulated", + "sentence": "The mouse pointer lock is emulated", "events": [ { "type": "BuiltinCommonInstructions::Standard", @@ -14344,6 +14448,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -14625,68 +14754,12 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "onFirstSceneLoaded" - }, - { - "functionName": "onScenePostEvents" - }, - { - "functionName": "RequestPointerLock" - }, - { - "functionName": "ExitPointerLock" - }, - { - "functionName": "isPointerLocked" - }, - { - "functionName": "isPointerActuallyLocked" - }, - { - "functionName": "isEmulatingPointerLock" - }, - { - "functionName": "IsMoving" - }, - { - "functionName": "MovementX" - }, - { - "functionName": "SetMovementX" - }, - { - "functionName": "MovementY" - }, - { - "functionName": "SetMovementY" - }, - { - "functionName": "TouchX" - }, - { - "functionName": "TouchY" - }, - { - "functionName": "TouchSpeedFactor" - }, - { - "functionName": "SetTouchSpeedFactor" - } - ] - }, "eventsBasedBehaviors": [ { "description": "Control camera rotations with a mouse.", "fullName": "First person camera mouse mapper", - "helpPath": "", - "iconUrl": "", "name": "FirstPersonPointerMapper", "objectType": "", - "previewIconUrl": "", "eventsFunctions": [ { "fullName": "", @@ -14706,6 +14779,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -14727,7 +14812,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { @@ -14961,7 +15046,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "HorizontalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetHorizontalSpeed", "sentence": "", "events": [ @@ -15042,7 +15126,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalSpeed", "sentence": "", "events": [ @@ -15123,7 +15206,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMin", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMin", "sentence": "", "events": [ @@ -15204,7 +15286,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMax", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMax", "sentence": "", "events": [ @@ -15285,7 +15366,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "OffsetZ", - "group": "First person camera mouse mapper configuration", "name": "SetOffsetZ", "sentence": "", "events": [ @@ -15322,57 +15402,6 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "doStepPreEvents" - }, - { - "folderName": "Layers and cameras", - "children": [ - { - "functionName": "LookFromObjectEyes" - } - ] - }, - { - "folderName": "First person camera mouse mapper configuration", - "children": [ - { - "functionName": "HorizontalSpeed" - }, - { - "functionName": "SetHorizontalSpeed" - }, - { - "functionName": "VerticalSpeed" - }, - { - "functionName": "SetVerticalSpeed" - }, - { - "functionName": "VerticalAngleMin" - }, - { - "functionName": "SetVerticalAngleMin" - }, - { - "functionName": "VerticalAngleMax" - }, - { - "functionName": "SetVerticalAngleMax" - }, - { - "functionName": "OffsetZ" - }, - { - "functionName": "SetOffsetZ" - } - ] - } - ] - }, "propertyDescriptors": [ { "value": "", @@ -15426,32 +15455,7 @@ "name": "ShouldLock" } ], - "propertiesFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "propertyName": "Object3D" - }, - { - "propertyName": "HorizontalSpeed" - }, - { - "propertyName": "VerticalSpeed" - }, - { - "propertyName": "VerticalAngleMin" - }, - { - "propertyName": "VerticalAngleMax" - }, - { - "propertyName": "OffsetZ" - }, - { - "propertyName": "ShouldLock" - } - ] - } + "sharedPropertyDescriptors": [] } ], "eventsBasedObjects": [] @@ -35558,7 +35562,7 @@ "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", + "version": "0.2.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.", "", @@ -36658,8 +36662,17 @@ "vTerrainLocalXY = position.xy;", "vTerrainLocalZ = position.z;", "vTerrainWorldXY = (modelMatrix * vec4(transformed, 1.0)).xy * terrainWorldScale;", - "vTerrainUp = normalize(transpose(inverse(mat3(modelMatrix))) * objectNormal).z;", + "// The world normal, from the camera space normal (computed by Three.js with a", + "// matrix inverted on the CPU), instead of inverting a matrix for each vertex.", + "vTerrainUp = inverseTransformDirection(transformedNormal, viewMatrix).z;", "`;", + "/** @param {(layer: number) => string} getLayerCode */", + "const forEachLayerCode = (getLayerCode) => [0, 1, 2, 3].map(getLayerCode).join('\\n');", + "", + "// Noise is read from a texture of random values, repeated every", + "// NOISE_MAP_SIZE units of noise: 1 texture read instead of 16 hashes.", + "const NOISE_MAP_SIZE = 256;", + "", "const fragmentShaderDeclarations = `", "uniform sampler2D terrainSplatMap;", "uniform vec2 terrainSplatUvScaleAndOffset;", @@ -36668,11 +36681,11 @@ "uniform sampler2D terrainLayerMap1;", "uniform sampler2D terrainLayerMap2;", "uniform sampler2D terrainLayerMap3;", + "uniform vec3 terrainLayerAverageColors[4];", "uniform sampler2D terrainLayerNormalMap0;", "uniform sampler2D terrainLayerNormalMap1;", "uniform sampler2D terrainLayerNormalMap2;", "uniform sampler2D terrainLayerNormalMap3;", - "uniform float terrainHasNormalMaps;", "uniform float terrainNormalMapStrength;", "uniform float terrainTextureSize;", "uniform float terrainHeightBlending;", @@ -36681,15 +36694,13 @@ "uniform vec2 terrainCliffCosines;", "uniform vec4 terrainBrush;", "uniform sampler2D terrainColorMap;", + "uniform sampler2D terrainNoiseMap;", "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. */", @@ -36701,15 +36712,16 @@ " copyBump.xy = transpose(terrainCopyRotation) * copyBump.xy;", " return mix(bump, copyBump, patches);", "}", - "float terrainNoise(vec2 p) {", + "/**", + " * 4 independent value noises (one by channel), from 0 to 1, smoothly varying", + " * between random values at integer positions.", + " */", + "vec4 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", - " );", + " vec2 f = p - i;", + " // The read position is moved so that the linear interpolation of the", + " // texture between 2 random values becomes a smooth one.", + " return texture2D(terrainNoiseMap, (i + f * f * (3.0 - 2.0 * f) + 0.5) / ${NOISE_MAP_SIZE}.0);", "}", "`;", "// Holes are samples without any painted layer: the interpolated sum of the", @@ -36727,15 +36739,11 @@ "varying vec2 vTerrainLocalXY;", "`;", "", + "// Layers without texture, normal map, height blending or texture variation", + "// are compiled out (see TerrainRenderer.updateMaterial).", "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)", - ");", + "vec4 terrainJitter = terrainNoise(vTerrainWorldXY / 24.0);", "// 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;", @@ -36746,48 +36754,61 @@ "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;", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "vec3 terrainTexture${layer} = texture2D(terrainLayerMap${layer}, terrainUv).rgb;", + "#else", + "vec3 terrainTexture${layer} = vec3(1.0);", + "#endif`", + ")}", "", "// 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);", - "}", + "#ifdef TERRAIN_TEXTURE_VARIATION", + "// 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).x);", + "terrainUv2 = terrainCopyRotation * terrainUv + vec2(0.31, 0.73);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "terrainTexture${layer} = mix(terrainTexture${layer}, texture2D(terrainLayerMap${layer}, terrainUv2).rgb, terrainPatches);", + "#endif`", + ")}", + "terrainColorVariation += terrainTextureVariation * 0.3 *", + " (terrainNoise(terrainUv * 0.11).y + 0.5 * terrainNoise(terrainUv * 0.37 + 3.1).z - 0.75);", + "#endif", "", "// 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);", - "}", + "#ifdef TERRAIN_HEIGHT_BLENDING", + "// Averages are computed once on the CPU, or read from the smallest mipmap", + "// when the image can't be read by the CPU.", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MIPMAP_AVERAGE_${layer}", + "vec3 terrainAverage${layer} = textureLod(terrainLayerMap${layer}, vec2(0.5), 16.0).rgb;", + "#else", + "vec3 terrainAverage${layer} = terrainLayerAverageColors[${layer}];", + "#endif`", + ")}", + "const vec3 terrainLuminance = vec3(0.299, 0.587, 0.114);", + "vec4 terrainHeights = vec4(", + " dot(terrainTexture0 - terrainAverage0, terrainLuminance),", + " dot(terrainTexture1 - terrainAverage1, terrainLuminance),", + " dot(terrainTexture2 - terrainAverage2, terrainLuminance),", + " dot(terrainTexture3 - terrainAverage3, 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);", + "#endif", "", "vec3 terrainColor = terrainColorVariation * (", " terrainWeights.x * terrainLayerColors[0] * terrainTexture0 +", @@ -36819,26 +36840,30 @@ "`;", "", "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;", + "#ifdef TERRAIN_NORMAL_MAPS", + "// Layers without normal map are flat.", + "vec3 terrainMapNormal = vec3(0.0);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_NORMAL_MAP_${layer}", + "terrainMapNormal += terrainWeights[${layer}] * terrainSampleNormal(terrainLayerNormalMap${layer}, terrainUv, terrainUv2, terrainPatches);", + "#else", + "terrainMapNormal.z += terrainWeights[${layer}];", + "#endif`", + ")}", + "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);", - " // 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);", "}", + "terrainTangent = normalize(terrainTangent);", + "vec3 terrainBitangent = cross(normal, terrainTangent);", + "normal = normalize(mat3(terrainTangent, terrainBitangent, normal) * terrainMapNormal);", + "#endif", "`;", "", "let whiteTexture = null;", @@ -36859,7 +36884,30 @@ " return flatNormalTexture;", "};", "", + "let noiseTexture = null;", + "/** Random values, in 4 independent channels, read by `terrainNoise` in the shader. */", + "const getNoiseTexture = () => {", + " if (!noiseTexture) {", + " const random = createRandom(1);", + " const bytes = new Uint8Array(NOISE_MAP_SIZE * NOISE_MAP_SIZE * 4);", + " for (let index = 0; index < bytes.length; index++) {", + " bytes[index] = Math.floor(random() * 256);", + " }", + " noiseTexture = new THREE.DataTexture(bytes, NOISE_MAP_SIZE, NOISE_MAP_SIZE);", + " noiseTexture.wrapS = THREE.RepeatWrapping;", + " noiseTexture.wrapT = THREE.RepeatWrapping;", + " // No mipmaps: the shader moves the read position between samples.", + " noiseTexture.magFilter = THREE.LinearFilter;", + " noiseTexture.minFilter = THREE.LinearFilter;", + " noiseTexture.needsUpdate = true;", + " }", + " return noiseTexture;", + "};", + "", + "const DEFAULT_ANISOTROPY = 4;", + "/** @type {WeakMap>} */", "const layerTextures = new WeakMap();", + "/** @type {WeakMap>} */", "const layerNormalMaps = new WeakMap();", "/**", " * Textures of the image manager have no mipmaps: they would shimmer when", @@ -36867,12 +36915,18 @@ " * @param {gdjs.RuntimeGame} game", " * @param {string} resourceName", " * @param {boolean} isNormalMap Normal maps are directions, not colors.", + " * @param {number} anisotropy", " */", - "const getLayerTexture = (game, resourceName, isNormalMap) => {", + "const getLayerTexture = (game, resourceName, isNormalMap, anisotropy) => {", " if (!resourceName) return isNormalMap ? getFlatNormalTexture() : getWhiteTexture();", " const imageTexture = game.getImageManager().getThreeTexture(resourceName);", - " const textures = isNormalMap ? layerNormalMaps : layerTextures;", - " let texture = textures.get(imageTexture);", + " const texturesByImage = isNormalMap ? layerNormalMaps : layerTextures;", + " let texturesByAnisotropy = texturesByImage.get(imageTexture);", + " if (!texturesByAnisotropy) {", + " texturesByAnisotropy = new Map();", + " texturesByImage.set(imageTexture, texturesByAnisotropy);", + " }", + " let texture = texturesByAnisotropy.get(anisotropy);", " if (!texture) {", " texture = imageTexture.clone();", " if (isNormalMap) texture.colorSpace = THREE.NoColorSpace;", @@ -36880,13 +36934,71 @@ " texture.wrapT = THREE.RepeatWrapping;", " texture.minFilter = THREE.LinearMipmapLinearFilter;", " texture.generateMipmaps = true;", - " texture.anisotropy = 4;", + " texture.anisotropy = anisotropy;", " texture.needsUpdate = true;", - " textures.set(imageTexture, texture);", + " texturesByAnisotropy.set(anisotropy, texture);", " }", " return texture;", "};", "", + "/** @type {Float32Array | null} */", + "let srgbToLinearValues = null;", + "/** @type {WeakMap} */", + "const linearAverageColors = new WeakMap();", + "/**", + " * @param {THREE.Texture} texture", + " * @returns {THREE.Vector3 | null} The average color of the texture, in the", + " * linear color space (like colors read by shaders), or null if the image", + " * can't be read: not loaded yet, or from another domain without CORS.", + " */", + "const getLinearAverageColor = (texture) => {", + " const image = texture.image;", + " if (!image) return null;", + " const cachedColor = linearAverageColors.get(image);", + " if (cachedColor) return cachedColor;", + " 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 null;", + " const size = 64;", + " let pixels;", + " try {", + " const canvas = document.createElement('canvas');", + " canvas.width = size;", + " canvas.height = size;", + " const context = canvas.getContext('2d');", + " // Downscaled with filtering, so that all pixels are counted.", + " context.imageSmoothingEnabled = true;", + " context.imageSmoothingQuality = 'high';", + " context.drawImage(image, 0, 0, size, size);", + " pixels = context.getImageData(0, 0, size, size).data;", + " } catch (error) {", + " return null;", + " }", + " if (!srgbToLinearValues) {", + " srgbToLinearValues = new Float32Array(256);", + " for (let value = 0; value < 256; value++) {", + " const srgb = value / 255;", + " srgbToLinearValues[value] = srgb <= 0.04045 ? srgb / 12.92 : Math.pow((srgb + 0.055) / 1.055, 2.4);", + " }", + " }", + " const isSrgb = texture.colorSpace === THREE.SRGBColorSpace;", + " let red = 0;", + " let green = 0;", + " let blue = 0;", + " for (let index = 0; index < pixels.length; index += 4) {", + " red += isSrgb ? srgbToLinearValues[pixels[index]] : pixels[index] / 255;", + " green += isSrgb ? srgbToLinearValues[pixels[index + 1]] : pixels[index + 1] / 255;", + " blue += isSrgb ? srgbToLinearValues[pixels[index + 2]] : pixels[index + 2] / 255;", + " }", + " const count = size * size;", + " const color = new THREE.Vector3(red / count, green / count, blue / count);", + " linearAverageColors.set(image, color);", + " return color;", + "};", + "", "/**", " * A texture of 4 bytes per sample of a grid of `size * size` samples (layer", " * weights, tint and wetness...), interpolated between samples.", @@ -36945,11 +37057,14 @@ " terrainLayerMap1: { value: getWhiteTexture() },", " terrainLayerMap2: { value: getWhiteTexture() },", " terrainLayerMap3: { value: getWhiteTexture() },", + " // Used by the height blending, in the linear color space.", + " terrainLayerAverageColors: {", + " value: [new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1)],", + " },", " terrainLayerNormalMap0: { value: getFlatNormalTexture() },", " terrainLayerNormalMap1: { value: getFlatNormalTexture() },", " terrainLayerNormalMap2: { value: getFlatNormalTexture() },", " terrainLayerNormalMap3: { value: getFlatNormalTexture() },", - " terrainHasNormalMaps: { value: 0 },", " terrainNormalMapStrength: { value: 1 },", " terrainTextureSize: { value: 256 },", " terrainHeightBlending: { value: 0 },", @@ -36958,6 +37073,7 @@ " terrainCliffCosines: { value: new THREE.Vector2() },", " terrainBrush: { value: new THREE.Vector4() },", " terrainColorMap: { value: getWhiteTexture() },", + " terrainNoiseMap: { value: getNoiseTexture() },", " // 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.", @@ -36977,8 +37093,12 @@ " ['#include ', '#include \\n' + fragmentShaderNormalCode],", " ], 'Terrain3D');", " };", - " this.material.customProgramCacheKey = () => (this.hasHoles ? 'Terrain3DWithHoles' : 'Terrain3D');", " this.hasHoles = false;", + " /** The defines of the features used by the terrain (see `updateMaterial`), without holes. */", + " this.featureDefines = [];", + " /** All the defines of the shader, separated by semicolons. */", + " this.shaderKey = '';", + " this.material.customProgramCacheKey = () => 'Terrain3D;' + this.shaderKey;", " // Without them, holes would cast shadows.", " this.depthMaterial = this._createHoleDepthMaterial(new THREE.MeshDepthMaterial());", " this.distanceMaterial = this._createHoleDepthMaterial(new THREE.MeshDistanceMaterial());", @@ -37015,11 +37135,28 @@ " setHasHoles(hasHoles) {", " if (hasHoles === this.hasHoles) return;", " this.hasHoles = hasHoles;", + " this._updateShaderDefines();", + " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", + " }", + "", + " /**", + " * Compile the shader only with the features used by the terrain. It is", + " * compiled again only when they change: properties only change in the", + " * editor, holes can be made by actions.", + " */", + " _updateShaderDefines() {", + " const defines = this.hasHoles ? [...this.featureDefines, 'TERRAIN_HOLES'] : this.featureDefines;", + " const shaderKey = defines.join(';');", + " if (shaderKey === this.shaderKey) return;", " // The material has its own defines (STANDARD).", - " if (hasHoles) this.material.defines.TERRAIN_HOLES = '';", - " else delete this.material.defines.TERRAIN_HOLES;", + " for (const name of this.shaderKey ? this.shaderKey.split(';') : []) {", + " delete this.material.defines[name];", + " }", + " for (const name of defines) {", + " this.material.defines[name] = '';", + " }", + " this.shaderKey = shaderKey;", " this.material.needsUpdate = true;", - " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", " }", "", " /** @param {THREE.Mesh} mesh */", @@ -37251,16 +37388,38 @@ " object._getLayer3NormalMap(),", " object._getLayer4NormalMap(),", " ];", + " const anisotropy = Math.max(parseInt(object._getAnisotropy(), 10) || DEFAULT_ANISOTROPY, 1);", + " const heightBlending = clamp01(object._getHeightBlending());", + " const textureVariation = clamp01(object._getTextureVariation());", + " const featureDefines = [];", " 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);", + " const layerTexture = getLayerTexture(game, textures[layer], false, anisotropy);", + " this.uniforms['terrainLayerMap' + layer].value = layerTexture;", + " this.uniforms['terrainLayerNormalMap' + layer].value = getLayerTexture(game, normalMaps[layer], true, anisotropy);", + " const averageColor = this.uniforms.terrainLayerAverageColors.value[layer];", + " if (textures[layer]) {", + " featureDefines.push('TERRAIN_LAYER_MAP_' + layer);", + " const linearAverageColor = heightBlending > 0 ? getLinearAverageColor(layerTexture) : null;", + " if (linearAverageColor) {", + " averageColor.copy(linearAverageColor);", + " } else if (heightBlending > 0) {", + " featureDefines.push('TERRAIN_LAYER_MIPMAP_AVERAGE_' + layer);", + " }", + " } else {", + " averageColor.set(1, 1, 1);", + " }", + " if (normalMaps[layer]) featureDefines.push('TERRAIN_LAYER_NORMAL_MAP_' + layer);", " }", - " this.uniforms.terrainHasNormalMaps.value = normalMaps.some((normalMap) => !!normalMap) ? 1 : 0;", + " if (normalMaps.some((normalMap) => !!normalMap)) featureDefines.push('TERRAIN_NORMAL_MAPS');", + " if (heightBlending > 0) featureDefines.push('TERRAIN_HEIGHT_BLENDING');", + " if (textureVariation > 0) featureDefines.push('TERRAIN_TEXTURE_VARIATION');", + " this.featureDefines = featureDefines;", + " this._updateShaderDefines();", " 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());", + " this.uniforms.terrainHeightBlending.value = heightBlending;", + " this.uniforms.terrainTextureVariation.value = textureVariation;", "", " const cliffLayer = parseInt(object._getCliffLayer(), 10);", " const cliffAngle = gdjs.toRad(object._getCliffAngle());", @@ -38178,40 +38337,21 @@ " return typeof scene.getRenderer3DWorldScale === 'function' ? scene.getRenderer3DWorldScale() : 1;", "};", "", + "/** @type {WeakMap} */", "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).", + " * @returns {THREE.Color} The average color of the texture (white when it can't be read).", " */", "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 linearAverageColor = getLinearAverageColor(texture);", + " if (!linearAverageColor) return white || (white = new THREE.Color(1, 1, 1));", + " let color = averageColors.get(linearAverageColor);", + " if (!color) {", + " color = new THREE.Color(linearAverageColor.x, linearAverageColor.y, linearAverageColor.z);", + " averageColors.set(linearAverageColor, color);", " }", - " 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;", "};", "", @@ -41825,6 +41965,37 @@ "group": "Rendering", "name": "ReceiveShadow" }, + { + "value": "4", + "type": "Choice", + "label": "Anisotropic filtering", + "description": "Keeps layer textures sharp where the ground is seen at a low angle (like from a car). Lower is faster on slow graphics cards, but blurs the ground in the distance.", + "group": "Rendering", + "choices": [ + { + "label": "None (fastest)", + "value": "1" + }, + { + "label": "2x", + "value": "2" + }, + { + "label": "4x", + "value": "4" + }, + { + "label": "8x", + "value": "8" + }, + { + "label": "16x (sharpest)", + "value": "16" + } + ], + "advanced": true, + "name": "Anisotropy" + }, { "value": "", "type": "String", @@ -41938,6 +42109,9 @@ }, { "propertyName": "ReceiveShadow" + }, + { + "propertyName": "Anisotropy" } ] }, diff --git a/examples/starting-first-person-survival-crafting/theme-slots.json b/examples/starting-first-person-survival-crafting/theme-slots.json index 95d95e90e..04281bc06 100644 --- a/examples/starting-first-person-survival-crafting/theme-slots.json +++ b/examples/starting-first-person-survival-crafting/theme-slots.json @@ -1,12 +1,11 @@ { "version": 1, "objects": { - "scene:Game Scene/Ground": { - "front": "env.ground", - "left": "env.wall", - "right": "env.wall", - "top": "env.wall", - "bottom": "env.wall" + "scene:Game Scene/Terrain": { + "Layer1Texture": { + "slot": "terrain.ground", + "normalMap": "Layer1NormalMap" + } }, "scene:Game Scene/Obstacle": { "front": "env.wall", @@ -24,6 +23,9 @@ "effect:Game Scene//SkyBox": "sky.day" }, "ignoredObjects": [ - "scene:Game Scene/Player" + "scene:Game Scene/Player", + "object:Terrain3D::Terrain3D/AreaPlaceholder", + "object:Terrain3D::TerrainGrass/AreaPlaceholder", + "object:Terrain3D::ScatteredModels/AreaPlaceholder" ] } diff --git a/examples/starting-first-person/starting-first-person.json b/examples/starting-first-person/starting-first-person.json index 09b576c93..82247f16d 100644 --- a/examples/starting-first-person/starting-first-person.json +++ b/examples/starting-first-person/starting-first-person.json @@ -1186,6 +1186,51 @@ "source": "", "type": "BuiltinCommonInstructions::Group", "events": [ + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Start with touch controls on touchscreens." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "SceneJustBegins" + }, + "parameters": [ + "" + ] + }, + { + "type": { + "value": "SystemInfo::HasTouchScreen" + }, + "parameters": [ + "" + ] + } + ], + "actions": [ + { + "type": { + "value": "SpriteToggleSwitch::SpriteToggleSwitch::SetChecked" + }, + "parameters": [ + "ControlsToggle", + "yes", + "" + ] + } + ] + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -1392,18 +1437,11 @@ "conditions": [ { "type": { - "value": "MouseButtonFromTextPressed" + "value": "HasAnyTouchOrMouseStarted" }, "parameters": [ - "", - "\"Left\"" + "" ] - }, - { - "type": { - "value": "BuiltinCommonInstructions::Once" - }, - "parameters": [] } ], "actions": [], @@ -1422,17 +1460,6 @@ "\"Left\"", "" ] - }, - { - "type": { - "inverted": true, - "value": "SpriteMultitouchJoystick::MultitouchButton::IsPressed" - }, - "parameters": [ - "JumpButton", - "MultitouchButton", - "" - ] } ], "actions": [ @@ -10840,7 +10867,6 @@ { "author": "PANDAKO-GitHub", "category": "Input", - "dimension": "", "extensionNamespace": "", "fullName": "Mouse Pointer Lock", "gdevelopVersion": ">=5.5.222", @@ -10848,8 +10874,8 @@ "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMy4wLjMsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iSWNvbnMiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB2aWV3Qm94PSIwIDAgMzIgMzIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDMyIDMyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojMDAwMDAwO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDoxMDt9DQo8L3N0eWxlPg0KPHBvbHlsaW5lIGNsYXNzPSJzdDAiIHBvaW50cz0iMTksMTkgMjQsMTkgMjQsMjQgIi8+DQo8cG9seWxpbmUgY2xhc3M9InN0MCIgcG9pbnRzPSI2LDIzIDExLDIzIDExLDE4ICIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTI0LDE5LjRjLTAuNywwLjgtMS40LDEuNi0yLjIsMi40Yy03LDctMTUuMywxMC4yLTE4LjUsN3MtMC4xLTExLjUsNy0xOC41czE1LjMtMTAuMiwxOC41LTcNCgljMS40LDEuNCwxLjYsMy42LDAuOCw2LjMiLz4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xMSwyMi41Yy0wLjMtMC4yLTAuNS0wLjUtMC44LTAuOGMtNy03LTEwLjItMTUuMy03LTE4LjVzMTEuNS0wLjEsMTguNSw3czEwLjIsMTUuMyw3LDE4LjUNCgljLTEuNywxLjctNC44LDEuNi04LjQsMC4xIi8+DQo8L3N2Zz4NCg==", "name": "MousePointerLock", "previewIconUrl": "https://resources.gdevelop-app.com/assets/Icons/Line Hero Pack/Master/SVG/Virtual Reality/Virtual Reality_360_rotate_vr_movement.svg", - "shortDescription": "This behavior removes the limit on the distance the mouse can move and hides the cursor.", - "version": "0.3.0", + "shortDescription": "Lock and hide mouse pointer for unlimited movement (e.g., FPS mouse look).", + "version": "0.3.2", "description": [ "This behavior removes the limit on the distance the mouse can move and hides the cursor.", "", @@ -10897,7 +10923,7 @@ { "name": "TouchSpeedFactor", "type": "number", - "value": 4 + "value": 2 } ], "eventsFunctions": [ @@ -11145,16 +11171,46 @@ ] }, { - "type": "BuiltinCommonInstructions::JsCode", - "inlineCode": [ - "const { handler } = gdjs._MousePointerLockExtension;\r", - "\r", - "handler.requestPointerLock();\r", - "" + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Fingers are followed instead of locking the pointer, because mobile browsers give tiny pointer movements." + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + "<", + "2" + ] + } ], - "parameterObjects": "", - "useStrict": true, - "eventsSheetExpanded": false + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const { handler } = gdjs._MousePointerLockExtension;\r", + "\r", + "handler.requestPointerLock();\r", + "" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": false + } + ] } ], "parameters": [], @@ -11301,7 +11357,7 @@ "functionType": "Condition", "name": "isEmulatingPointerLock", "private": true, - "sentence": "The mouse pointer is lock is emulated", + "sentence": "The mouse pointer lock is emulated", "events": [ { "type": "BuiltinCommonInstructions::Standard", @@ -11337,6 +11393,31 @@ ] } ] + }, + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "NumberVariable" + }, + "parameters": [ + "TouchId", + ">=", + "2" + ] + } + ], + "actions": [ + { + "type": { + "value": "SetReturnBoolean" + }, + "parameters": [ + "True" + ] + } + ] } ], "parameters": [], @@ -11618,68 +11699,12 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "onFirstSceneLoaded" - }, - { - "functionName": "onScenePostEvents" - }, - { - "functionName": "RequestPointerLock" - }, - { - "functionName": "ExitPointerLock" - }, - { - "functionName": "isPointerLocked" - }, - { - "functionName": "isPointerActuallyLocked" - }, - { - "functionName": "isEmulatingPointerLock" - }, - { - "functionName": "IsMoving" - }, - { - "functionName": "MovementX" - }, - { - "functionName": "SetMovementX" - }, - { - "functionName": "MovementY" - }, - { - "functionName": "SetMovementY" - }, - { - "functionName": "TouchX" - }, - { - "functionName": "TouchY" - }, - { - "functionName": "TouchSpeedFactor" - }, - { - "functionName": "SetTouchSpeedFactor" - } - ] - }, "eventsBasedBehaviors": [ { "description": "Control camera rotations with a mouse.", "fullName": "First person camera mouse mapper", - "helpPath": "", - "iconUrl": "", "name": "FirstPersonPointerMapper", "objectType": "", - "previewIconUrl": "", "eventsFunctions": [ { "fullName": "", @@ -11699,6 +11724,18 @@ }, "comment": "TODO It's probably a bad idea to rotate the object around Y." }, + { + "type": "BuiltinCommonInstructions::Comment", + "color": { + "b": 109, + "g": 230, + "r": 255, + "textB": 0, + "textG": 0, + "textR": 0 + }, + "comment": "Both rotations are relative to the screen height because the field of view is vertical. This way, the speed doesn't change with the screen ratio." + }, { "type": "BuiltinCommonInstructions::Standard", "conditions": [ @@ -11720,7 +11757,7 @@ "parameters": [ "Object", "+", - "MousePointerLock::MovementX() * HorizontalSpeed / SceneWindowWidth()" + "MousePointerLock::MovementX() * HorizontalSpeed / (SceneWindowHeight() * 16 / 9)" ] }, { @@ -11954,7 +11991,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "HorizontalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetHorizontalSpeed", "sentence": "", "events": [ @@ -12035,7 +12071,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalSpeed", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalSpeed", "sentence": "", "events": [ @@ -12116,7 +12151,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMin", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMin", "sentence": "", "events": [ @@ -12197,7 +12231,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "VerticalAngleMax", - "group": "First person camera mouse mapper configuration", "name": "SetVerticalAngleMax", "sentence": "", "events": [ @@ -12278,7 +12311,6 @@ "fullName": "", "functionType": "ActionWithOperator", "getterName": "OffsetZ", - "group": "First person camera mouse mapper configuration", "name": "SetOffsetZ", "sentence": "", "events": [ @@ -12315,57 +12347,6 @@ "objectGroups": [] } ], - "eventsFunctionsFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "functionName": "doStepPreEvents" - }, - { - "folderName": "Layers and cameras", - "children": [ - { - "functionName": "LookFromObjectEyes" - } - ] - }, - { - "folderName": "First person camera mouse mapper configuration", - "children": [ - { - "functionName": "HorizontalSpeed" - }, - { - "functionName": "SetHorizontalSpeed" - }, - { - "functionName": "VerticalSpeed" - }, - { - "functionName": "SetVerticalSpeed" - }, - { - "functionName": "VerticalAngleMin" - }, - { - "functionName": "SetVerticalAngleMin" - }, - { - "functionName": "VerticalAngleMax" - }, - { - "functionName": "SetVerticalAngleMax" - }, - { - "functionName": "OffsetZ" - }, - { - "functionName": "SetOffsetZ" - } - ] - } - ] - }, "propertyDescriptors": [ { "value": "", @@ -12419,32 +12400,7 @@ "name": "ShouldLock" } ], - "propertiesFolderStructure": { - "folderName": "__ROOT", - "children": [ - { - "propertyName": "Object3D" - }, - { - "propertyName": "HorizontalSpeed" - }, - { - "propertyName": "VerticalSpeed" - }, - { - "propertyName": "VerticalAngleMin" - }, - { - "propertyName": "VerticalAngleMax" - }, - { - "propertyName": "OffsetZ" - }, - { - "propertyName": "ShouldLock" - } - ] - } + "sharedPropertyDescriptors": [] } ], "eventsBasedObjects": [] @@ -23231,7 +23187,7 @@ "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", + "version": "0.2.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.", "", @@ -24331,8 +24287,17 @@ "vTerrainLocalXY = position.xy;", "vTerrainLocalZ = position.z;", "vTerrainWorldXY = (modelMatrix * vec4(transformed, 1.0)).xy * terrainWorldScale;", - "vTerrainUp = normalize(transpose(inverse(mat3(modelMatrix))) * objectNormal).z;", + "// The world normal, from the camera space normal (computed by Three.js with a", + "// matrix inverted on the CPU), instead of inverting a matrix for each vertex.", + "vTerrainUp = inverseTransformDirection(transformedNormal, viewMatrix).z;", "`;", + "/** @param {(layer: number) => string} getLayerCode */", + "const forEachLayerCode = (getLayerCode) => [0, 1, 2, 3].map(getLayerCode).join('\\n');", + "", + "// Noise is read from a texture of random values, repeated every", + "// NOISE_MAP_SIZE units of noise: 1 texture read instead of 16 hashes.", + "const NOISE_MAP_SIZE = 256;", + "", "const fragmentShaderDeclarations = `", "uniform sampler2D terrainSplatMap;", "uniform vec2 terrainSplatUvScaleAndOffset;", @@ -24341,11 +24306,11 @@ "uniform sampler2D terrainLayerMap1;", "uniform sampler2D terrainLayerMap2;", "uniform sampler2D terrainLayerMap3;", + "uniform vec3 terrainLayerAverageColors[4];", "uniform sampler2D terrainLayerNormalMap0;", "uniform sampler2D terrainLayerNormalMap1;", "uniform sampler2D terrainLayerNormalMap2;", "uniform sampler2D terrainLayerNormalMap3;", - "uniform float terrainHasNormalMaps;", "uniform float terrainNormalMapStrength;", "uniform float terrainTextureSize;", "uniform float terrainHeightBlending;", @@ -24354,15 +24319,13 @@ "uniform vec2 terrainCliffCosines;", "uniform vec4 terrainBrush;", "uniform sampler2D terrainColorMap;", + "uniform sampler2D terrainNoiseMap;", "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. */", @@ -24374,15 +24337,16 @@ " copyBump.xy = transpose(terrainCopyRotation) * copyBump.xy;", " return mix(bump, copyBump, patches);", "}", - "float terrainNoise(vec2 p) {", + "/**", + " * 4 independent value noises (one by channel), from 0 to 1, smoothly varying", + " * between random values at integer positions.", + " */", + "vec4 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", - " );", + " vec2 f = p - i;", + " // The read position is moved so that the linear interpolation of the", + " // texture between 2 random values becomes a smooth one.", + " return texture2D(terrainNoiseMap, (i + f * f * (3.0 - 2.0 * f) + 0.5) / ${NOISE_MAP_SIZE}.0);", "}", "`;", "// Holes are samples without any painted layer: the interpolated sum of the", @@ -24400,15 +24364,11 @@ "varying vec2 vTerrainLocalXY;", "`;", "", + "// Layers without texture, normal map, height blending or texture variation", + "// are compiled out (see TerrainRenderer.updateMaterial).", "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)", - ");", + "vec4 terrainJitter = terrainNoise(vTerrainWorldXY / 24.0);", "// 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;", @@ -24419,48 +24379,61 @@ "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;", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "vec3 terrainTexture${layer} = texture2D(terrainLayerMap${layer}, terrainUv).rgb;", + "#else", + "vec3 terrainTexture${layer} = vec3(1.0);", + "#endif`", + ")}", "", "// 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);", - "}", + "#ifdef TERRAIN_TEXTURE_VARIATION", + "// 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).x);", + "terrainUv2 = terrainCopyRotation * terrainUv + vec2(0.31, 0.73);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MAP_${layer}", + "terrainTexture${layer} = mix(terrainTexture${layer}, texture2D(terrainLayerMap${layer}, terrainUv2).rgb, terrainPatches);", + "#endif`", + ")}", + "terrainColorVariation += terrainTextureVariation * 0.3 *", + " (terrainNoise(terrainUv * 0.11).y + 0.5 * terrainNoise(terrainUv * 0.37 + 3.1).z - 0.75);", + "#endif", "", "// 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);", - "}", + "#ifdef TERRAIN_HEIGHT_BLENDING", + "// Averages are computed once on the CPU, or read from the smallest mipmap", + "// when the image can't be read by the CPU.", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_MIPMAP_AVERAGE_${layer}", + "vec3 terrainAverage${layer} = textureLod(terrainLayerMap${layer}, vec2(0.5), 16.0).rgb;", + "#else", + "vec3 terrainAverage${layer} = terrainLayerAverageColors[${layer}];", + "#endif`", + ")}", + "const vec3 terrainLuminance = vec3(0.299, 0.587, 0.114);", + "vec4 terrainHeights = vec4(", + " dot(terrainTexture0 - terrainAverage0, terrainLuminance),", + " dot(terrainTexture1 - terrainAverage1, terrainLuminance),", + " dot(terrainTexture2 - terrainAverage2, terrainLuminance),", + " dot(terrainTexture3 - terrainAverage3, 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);", + "#endif", "", "vec3 terrainColor = terrainColorVariation * (", " terrainWeights.x * terrainLayerColors[0] * terrainTexture0 +", @@ -24492,26 +24465,30 @@ "`;", "", "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;", + "#ifdef TERRAIN_NORMAL_MAPS", + "// Layers without normal map are flat.", + "vec3 terrainMapNormal = vec3(0.0);", + "${forEachLayerCode(", + " (layer) => `#ifdef TERRAIN_LAYER_NORMAL_MAP_${layer}", + "terrainMapNormal += terrainWeights[${layer}] * terrainSampleNormal(terrainLayerNormalMap${layer}, terrainUv, terrainUv2, terrainPatches);", + "#else", + "terrainMapNormal.z += terrainWeights[${layer}];", + "#endif`", + ")}", + "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);", - " // 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);", "}", + "terrainTangent = normalize(terrainTangent);", + "vec3 terrainBitangent = cross(normal, terrainTangent);", + "normal = normalize(mat3(terrainTangent, terrainBitangent, normal) * terrainMapNormal);", + "#endif", "`;", "", "let whiteTexture = null;", @@ -24532,7 +24509,30 @@ " return flatNormalTexture;", "};", "", + "let noiseTexture = null;", + "/** Random values, in 4 independent channels, read by `terrainNoise` in the shader. */", + "const getNoiseTexture = () => {", + " if (!noiseTexture) {", + " const random = createRandom(1);", + " const bytes = new Uint8Array(NOISE_MAP_SIZE * NOISE_MAP_SIZE * 4);", + " for (let index = 0; index < bytes.length; index++) {", + " bytes[index] = Math.floor(random() * 256);", + " }", + " noiseTexture = new THREE.DataTexture(bytes, NOISE_MAP_SIZE, NOISE_MAP_SIZE);", + " noiseTexture.wrapS = THREE.RepeatWrapping;", + " noiseTexture.wrapT = THREE.RepeatWrapping;", + " // No mipmaps: the shader moves the read position between samples.", + " noiseTexture.magFilter = THREE.LinearFilter;", + " noiseTexture.minFilter = THREE.LinearFilter;", + " noiseTexture.needsUpdate = true;", + " }", + " return noiseTexture;", + "};", + "", + "const DEFAULT_ANISOTROPY = 4;", + "/** @type {WeakMap>} */", "const layerTextures = new WeakMap();", + "/** @type {WeakMap>} */", "const layerNormalMaps = new WeakMap();", "/**", " * Textures of the image manager have no mipmaps: they would shimmer when", @@ -24540,12 +24540,18 @@ " * @param {gdjs.RuntimeGame} game", " * @param {string} resourceName", " * @param {boolean} isNormalMap Normal maps are directions, not colors.", + " * @param {number} anisotropy", " */", - "const getLayerTexture = (game, resourceName, isNormalMap) => {", + "const getLayerTexture = (game, resourceName, isNormalMap, anisotropy) => {", " if (!resourceName) return isNormalMap ? getFlatNormalTexture() : getWhiteTexture();", " const imageTexture = game.getImageManager().getThreeTexture(resourceName);", - " const textures = isNormalMap ? layerNormalMaps : layerTextures;", - " let texture = textures.get(imageTexture);", + " const texturesByImage = isNormalMap ? layerNormalMaps : layerTextures;", + " let texturesByAnisotropy = texturesByImage.get(imageTexture);", + " if (!texturesByAnisotropy) {", + " texturesByAnisotropy = new Map();", + " texturesByImage.set(imageTexture, texturesByAnisotropy);", + " }", + " let texture = texturesByAnisotropy.get(anisotropy);", " if (!texture) {", " texture = imageTexture.clone();", " if (isNormalMap) texture.colorSpace = THREE.NoColorSpace;", @@ -24553,13 +24559,71 @@ " texture.wrapT = THREE.RepeatWrapping;", " texture.minFilter = THREE.LinearMipmapLinearFilter;", " texture.generateMipmaps = true;", - " texture.anisotropy = 4;", + " texture.anisotropy = anisotropy;", " texture.needsUpdate = true;", - " textures.set(imageTexture, texture);", + " texturesByAnisotropy.set(anisotropy, texture);", " }", " return texture;", "};", "", + "/** @type {Float32Array | null} */", + "let srgbToLinearValues = null;", + "/** @type {WeakMap} */", + "const linearAverageColors = new WeakMap();", + "/**", + " * @param {THREE.Texture} texture", + " * @returns {THREE.Vector3 | null} The average color of the texture, in the", + " * linear color space (like colors read by shaders), or null if the image", + " * can't be read: not loaded yet, or from another domain without CORS.", + " */", + "const getLinearAverageColor = (texture) => {", + " const image = texture.image;", + " if (!image) return null;", + " const cachedColor = linearAverageColors.get(image);", + " if (cachedColor) return cachedColor;", + " 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 null;", + " const size = 64;", + " let pixels;", + " try {", + " const canvas = document.createElement('canvas');", + " canvas.width = size;", + " canvas.height = size;", + " const context = canvas.getContext('2d');", + " // Downscaled with filtering, so that all pixels are counted.", + " context.imageSmoothingEnabled = true;", + " context.imageSmoothingQuality = 'high';", + " context.drawImage(image, 0, 0, size, size);", + " pixels = context.getImageData(0, 0, size, size).data;", + " } catch (error) {", + " return null;", + " }", + " if (!srgbToLinearValues) {", + " srgbToLinearValues = new Float32Array(256);", + " for (let value = 0; value < 256; value++) {", + " const srgb = value / 255;", + " srgbToLinearValues[value] = srgb <= 0.04045 ? srgb / 12.92 : Math.pow((srgb + 0.055) / 1.055, 2.4);", + " }", + " }", + " const isSrgb = texture.colorSpace === THREE.SRGBColorSpace;", + " let red = 0;", + " let green = 0;", + " let blue = 0;", + " for (let index = 0; index < pixels.length; index += 4) {", + " red += isSrgb ? srgbToLinearValues[pixels[index]] : pixels[index] / 255;", + " green += isSrgb ? srgbToLinearValues[pixels[index + 1]] : pixels[index + 1] / 255;", + " blue += isSrgb ? srgbToLinearValues[pixels[index + 2]] : pixels[index + 2] / 255;", + " }", + " const count = size * size;", + " const color = new THREE.Vector3(red / count, green / count, blue / count);", + " linearAverageColors.set(image, color);", + " return color;", + "};", + "", "/**", " * A texture of 4 bytes per sample of a grid of `size * size` samples (layer", " * weights, tint and wetness...), interpolated between samples.", @@ -24618,11 +24682,14 @@ " terrainLayerMap1: { value: getWhiteTexture() },", " terrainLayerMap2: { value: getWhiteTexture() },", " terrainLayerMap3: { value: getWhiteTexture() },", + " // Used by the height blending, in the linear color space.", + " terrainLayerAverageColors: {", + " value: [new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1), new THREE.Vector3(1, 1, 1)],", + " },", " terrainLayerNormalMap0: { value: getFlatNormalTexture() },", " terrainLayerNormalMap1: { value: getFlatNormalTexture() },", " terrainLayerNormalMap2: { value: getFlatNormalTexture() },", " terrainLayerNormalMap3: { value: getFlatNormalTexture() },", - " terrainHasNormalMaps: { value: 0 },", " terrainNormalMapStrength: { value: 1 },", " terrainTextureSize: { value: 256 },", " terrainHeightBlending: { value: 0 },", @@ -24631,6 +24698,7 @@ " terrainCliffCosines: { value: new THREE.Vector2() },", " terrainBrush: { value: new THREE.Vector4() },", " terrainColorMap: { value: getWhiteTexture() },", + " terrainNoiseMap: { value: getNoiseTexture() },", " // 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.", @@ -24650,8 +24718,12 @@ " ['#include ', '#include \\n' + fragmentShaderNormalCode],", " ], 'Terrain3D');", " };", - " this.material.customProgramCacheKey = () => (this.hasHoles ? 'Terrain3DWithHoles' : 'Terrain3D');", " this.hasHoles = false;", + " /** The defines of the features used by the terrain (see `updateMaterial`), without holes. */", + " this.featureDefines = [];", + " /** All the defines of the shader, separated by semicolons. */", + " this.shaderKey = '';", + " this.material.customProgramCacheKey = () => 'Terrain3D;' + this.shaderKey;", " // Without them, holes would cast shadows.", " this.depthMaterial = this._createHoleDepthMaterial(new THREE.MeshDepthMaterial());", " this.distanceMaterial = this._createHoleDepthMaterial(new THREE.MeshDistanceMaterial());", @@ -24688,11 +24760,28 @@ " setHasHoles(hasHoles) {", " if (hasHoles === this.hasHoles) return;", " this.hasHoles = hasHoles;", + " this._updateShaderDefines();", + " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", + " }", + "", + " /**", + " * Compile the shader only with the features used by the terrain. It is", + " * compiled again only when they change: properties only change in the", + " * editor, holes can be made by actions.", + " */", + " _updateShaderDefines() {", + " const defines = this.hasHoles ? [...this.featureDefines, 'TERRAIN_HOLES'] : this.featureDefines;", + " const shaderKey = defines.join(';');", + " if (shaderKey === this.shaderKey) return;", " // The material has its own defines (STANDARD).", - " if (hasHoles) this.material.defines.TERRAIN_HOLES = '';", - " else delete this.material.defines.TERRAIN_HOLES;", + " for (const name of this.shaderKey ? this.shaderKey.split(';') : []) {", + " delete this.material.defines[name];", + " }", + " for (const name of defines) {", + " this.material.defines[name] = '';", + " }", + " this.shaderKey = shaderKey;", " this.material.needsUpdate = true;", - " for (const chunk of this.chunks) this._setChunkShadowMaterials(chunk.mesh);", " }", "", " /** @param {THREE.Mesh} mesh */", @@ -24924,16 +25013,38 @@ " object._getLayer3NormalMap(),", " object._getLayer4NormalMap(),", " ];", + " const anisotropy = Math.max(parseInt(object._getAnisotropy(), 10) || DEFAULT_ANISOTROPY, 1);", + " const heightBlending = clamp01(object._getHeightBlending());", + " const textureVariation = clamp01(object._getTextureVariation());", + " const featureDefines = [];", " 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);", + " const layerTexture = getLayerTexture(game, textures[layer], false, anisotropy);", + " this.uniforms['terrainLayerMap' + layer].value = layerTexture;", + " this.uniforms['terrainLayerNormalMap' + layer].value = getLayerTexture(game, normalMaps[layer], true, anisotropy);", + " const averageColor = this.uniforms.terrainLayerAverageColors.value[layer];", + " if (textures[layer]) {", + " featureDefines.push('TERRAIN_LAYER_MAP_' + layer);", + " const linearAverageColor = heightBlending > 0 ? getLinearAverageColor(layerTexture) : null;", + " if (linearAverageColor) {", + " averageColor.copy(linearAverageColor);", + " } else if (heightBlending > 0) {", + " featureDefines.push('TERRAIN_LAYER_MIPMAP_AVERAGE_' + layer);", + " }", + " } else {", + " averageColor.set(1, 1, 1);", + " }", + " if (normalMaps[layer]) featureDefines.push('TERRAIN_LAYER_NORMAL_MAP_' + layer);", " }", - " this.uniforms.terrainHasNormalMaps.value = normalMaps.some((normalMap) => !!normalMap) ? 1 : 0;", + " if (normalMaps.some((normalMap) => !!normalMap)) featureDefines.push('TERRAIN_NORMAL_MAPS');", + " if (heightBlending > 0) featureDefines.push('TERRAIN_HEIGHT_BLENDING');", + " if (textureVariation > 0) featureDefines.push('TERRAIN_TEXTURE_VARIATION');", + " this.featureDefines = featureDefines;", + " this._updateShaderDefines();", " 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());", + " this.uniforms.terrainHeightBlending.value = heightBlending;", + " this.uniforms.terrainTextureVariation.value = textureVariation;", "", " const cliffLayer = parseInt(object._getCliffLayer(), 10);", " const cliffAngle = gdjs.toRad(object._getCliffAngle());", @@ -25851,40 +25962,21 @@ " return typeof scene.getRenderer3DWorldScale === 'function' ? scene.getRenderer3DWorldScale() : 1;", "};", "", + "/** @type {WeakMap} */", "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).", + " * @returns {THREE.Color} The average color of the texture (white when it can't be read).", " */", "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 linearAverageColor = getLinearAverageColor(texture);", + " if (!linearAverageColor) return white || (white = new THREE.Color(1, 1, 1));", + " let color = averageColors.get(linearAverageColor);", + " if (!color) {", + " color = new THREE.Color(linearAverageColor.x, linearAverageColor.y, linearAverageColor.z);", + " averageColors.set(linearAverageColor, color);", " }", - " 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;", "};", "", @@ -29498,6 +29590,37 @@ "group": "Rendering", "name": "ReceiveShadow" }, + { + "value": "4", + "type": "Choice", + "label": "Anisotropic filtering", + "description": "Keeps layer textures sharp where the ground is seen at a low angle (like from a car). Lower is faster on slow graphics cards, but blurs the ground in the distance.", + "group": "Rendering", + "choices": [ + { + "label": "None (fastest)", + "value": "1" + }, + { + "label": "2x", + "value": "2" + }, + { + "label": "4x", + "value": "4" + }, + { + "label": "8x", + "value": "8" + }, + { + "label": "16x (sharpest)", + "value": "16" + } + ], + "advanced": true, + "name": "Anisotropy" + }, { "value": "", "type": "String", @@ -29611,6 +29734,9 @@ }, { "propertyName": "ReceiveShadow" + }, + { + "propertyName": "Anisotropy" } ] }, diff --git a/examples/starting-first-person/theme-slots.json b/examples/starting-first-person/theme-slots.json index fa1a9ca91..40e10a7ee 100644 --- a/examples/starting-first-person/theme-slots.json +++ b/examples/starting-first-person/theme-slots.json @@ -1,12 +1,11 @@ { "version": 1, "objects": { - "scene:Game Scene/Ground": { - "front": "env.ground", - "left": "env.wall", - "right": "env.wall", - "top": "env.wall", - "bottom": "env.wall" + "scene:Game Scene/Terrain": { + "Layer1Texture": { + "slot": "terrain.ground", + "normalMap": "Layer1NormalMap" + } }, "scene:Game Scene/Obstacle": { "front": "env.wall", @@ -21,6 +20,9 @@ "effect:Game Scene//SkyBox": "sky.day" }, "ignoredObjects": [ - "scene:Game Scene/Player" + "scene:Game Scene/Player", + "object:Terrain3D::Terrain3D/AreaPlaceholder", + "object:Terrain3D::TerrainGrass/AreaPlaceholder", + "object:Terrain3D::ScatteredModels/AreaPlaceholder" ] }