diff --git a/extensions/community/YouTubePlayables.json b/extensions/community/YouTubePlayables.json new file mode 100644 index 000000000..82dec9e87 --- /dev/null +++ b/extensions/community/YouTubePlayables.json @@ -0,0 +1,666 @@ +{ + "author": "", + "category": "Third-party", + "extensionNamespace": "", + "fullName": "YouTube Playables SDK", + "gdevelopVersion": "", + "helpPath": "https://developers.google.com/youtube/gaming/playables", + "iconUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0ibWRpLXlvdXR1YmUiIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTAsMTVMMTUuMTksMTJMMTAsOVYxNU0yMS41Niw3LjE3QzIxLjY5LDcuNjQgMjEuNzgsOC4yNyAyMS44NCw5LjA3QzIxLjkxLDkuODcgMjEuOTQsMTAuNTYgMjEuOTQsMTEuMTZMMjIsMTJDMjIsMTQuMTkgMjEuODQsMTUuOCAyMS41NiwxNi44M0MyMS4zMSwxNy43MyAyMC43MywxOC4zMSAxOS44MywxOC41NkMxOS4zNiwxOC42OSAxOC41LDE4Ljc4IDE3LjE4LDE4Ljg0QzE1Ljg4LDE4LjkxIDE0LjY5LDE4Ljk0IDEzLjU5LDE4Ljk0TDEyLDE5QzcuODEsMTkgNS4yLDE4Ljg0IDQuMTcsMTguNTZDMy4yNywxOC4zMSAyLjY5LDE3LjczIDIuNDQsMTYuODNDMi4zMSwxNi4zNiAyLjIyLDE1LjczIDIuMTYsMTQuOTNDMi4wOSwxNC4xMyAyLjA2LDEzLjQ0IDIuMDYsMTIuODRMMiwxMkMyLDkuODEgMi4xNiw4LjIgMi40NCw3LjE3QzIuNjksNi4yNyAzLjI3LDUuNjkgNC4xNyw1LjQ0QzQuNjQsNS4zMSA1LjUsNS4yMiA2LjgyLDUuMTZDOC4xMiw1LjA5IDkuMzEsNS4wNiAxMC40MSw1LjA2TDEyLDVDMTYuMTksNSAxOC44LDUuMTYgMTkuODMsNS40NEMyMC43Myw1LjY5IDIxLjMxLDYuMjcgMjEuNTYsNy4xN1oiIC8+PC9zdmc+", + "name": "YouTubePlayables", + "previewIconUrl": "https://asset-resources.gdevelop.io/public-resources/Icons/9ae52130760954736a653fb090dfec5ed9a8ffc0d881f27616ad3a6192dd602c_youtube.svg", + "shortDescription": "Playables are interactive games and experiences published to YouTube.", + "version": "0.0.1", + "description": [ + "> To test the game properly before uploading to YouTube, please use the SDK Test Suite available at https://developers.google.com/youtube/gaming/playables/certification/sdktestsuite", + "", + "Feautres:", + "", + "- Save progress to YouTube servers.", + "- Load progress from YouTube servers.", + "- Submit high-score.", + "- Show interstitial ads. (experimental)", + "- Open a YouTube video.", + "- Check the SDK version with expression `YouTubePlayables::SdkVersion()`.", + "", + "Requirements:", + "", + "- Use action \"The game is ready\" when the initial screen is completely ready.", + "- Use condition \"The game audio is enabled\" to un/mute the game.", + "- Use conditions \"The game is paused\" and \"The game is resumed\" to pause/resume the game.", + "- More info: https://developers.google.com/youtube/gaming/playables/certification/requirements" + ], + "tags": [ + "save", + "load", + "leaderboard", + "SDK", + "YouTube", + "api", + "Playables" + ], + "authorIds": [ + "lj8txSYs8QPfahnyD8MFO4XvMv02" + ], + "dependencies": [], + "globalVariables": [], + "sceneVariables": [], + "eventsFunctions": [ + { + "fullName": "", + "functionType": "Action", + "name": "onFirstSceneLoaded", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "const script = document.createElement(\"script\");", + "script.src = \"https://www.youtube.com/game_api/v1\";", + "", + "script.onload = () => {", + " gdjs._YTGame = window.ytgame;", + " gdjs._YTGame._status = {}; // A proxy for event listeners. ", + " ", + " if (gdjs._YTGame.IN_PLAYABLES_ENV) {", + " // Notify YouTube that the game has begun showing frames. (As soon as possible)", + " gdjs._YTGame.game.firstFrameReady();", + "", + " // Audio event listener.", + " gdjs._YTGame._status.isAudioEnabled = gdjs._YTGame.system.isAudioEnabled();", + " gdjs._YTGame.system.onAudioEnabledChange((isAudioEnabled) => {", + " gdjs._YTGame._status.isAudioEnabled = isAudioEnabled;", + " });", + "", + " // Pause event listener", + " gdjs._YTGame._status.onPause = false;", + " gdjs._YTGame.system.onPause(() => {", + " gdjs._YTGame._status.onPause = true;", + " gdjs._YTGame._status.onResume = false;", + " });", + "", + " // Resume event listener", + " gdjs._YTGame._status.onResume = true;", + " gdjs._YTGame.system.onResume(() => {", + " gdjs._YTGame._status.onPause = false;", + " gdjs._YTGame._status.onResume = true;", + " });", + " } else {", + " gdjs._YTGame._status.isAudioEnabled = true;", + " gdjs._YTGame._status.onPause = false;", + " gdjs._YTGame._status.onResume = true;", + " }", + "};", + "", + "document.head.prepend(script);" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Check if the YouTube Playables SDK is ready to be used.", + "fullName": "Playables SDK is ready", + "functionType": "Condition", + "name": "IsSdkReady", + "sentence": "YouTube Playables SDK is ready", + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "eventsFunctionContext.returnValue = gdjs._YTGame;", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Returns YouTube Playables SDK current version.", + "fullName": "SDK version", + "functionType": "StringExpression", + "name": "SdkVersion", + "sentence": "", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "YouTubePlayables::IsSdkReady" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "eventsFunctionContext.returnValue = gdjs._YTGame.SDK_VERSION;", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ] + } + ], + "expressionType": { + "type": "string" + }, + "parameters": [], + "objectGroups": [] + }, + { + "description": "Notifies YouTube that the game is ready for players to interact with.\nYou MUST trigger this when the initial interface is completely ready.", + "fullName": "The game is ready", + "functionType": "Action", + "name": "GameReady", + "sentence": "The game is ready", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "YouTubePlayables::IsSdkReady" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "gdjs._YTGame.game.gameReady();", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Check if the game is running within the Playables environment.", + "fullName": "In Playables environment", + "functionType": "Condition", + "name": "InPlayablesEnvironment", + "sentence": "The game is running within the YouTube Playables environment", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "YouTubePlayables::IsSdkReady" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "eventsFunctionContext.returnValue = gdjs._YTGame.IN_PLAYABLES_ENV;", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Check if the player has enabled the game audio through YouTube UI.\nThis is a mandatory requirement and the game must be muted on demand.", + "fullName": "The game audio must be enabled", + "functionType": "Condition", + "name": "IsAudioEnabled", + "sentence": "The game audio must be enabled", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "YouTubePlayables::IsSdkReady" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "eventsFunctionContext.returnValue = gdjs._YTGame._status.isAudioEnabled;", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "async": true, + "description": "Requests an interstitial ad to be shown.\nSubject to changes without notice, and makes no guarantees about whether the ad will be shown.", + "fullName": "Requests an interstitial ad (experimental)", + "functionType": "Action", + "name": "RequestInterstitialAd", + "sentence": "Request an interstitial ad to be shown (store result state in _PARAM1_)", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "YouTubePlayables::IsSdkReady" + }, + "parameters": [ + "", + "" + ] + }, + { + "type": { + "value": "YouTubePlayables::InPlayablesEnvironment" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "gdjs._YTGame.ads.requestInterstitialAd()\r", + " .then(() => {\r", + " eventsFunctionContext.getArgument(\"Status\").setString(\"ok\");\r", + " eventsFunctionContext.task.resolve();\r", + " })\r", + " .catch((error) => {\r", + " eventsFunctionContext.getArgument(\"Status\").setString(\"error\");\r", + " eventsFunctionContext.task.resolve();\r", + " });" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ] + } + ], + "parameters": [ + { + "description": "Callback variable with state (ok or error)", + "name": "Status", + "type": "variable" + } + ], + "objectGroups": [] + }, + { + "description": "Check if the player has paused the game through YouTube UI.\nThis is a mandatory requirement and the game must be paused on demand.", + "fullName": "The game is paused", + "functionType": "Condition", + "name": "OnPause", + "sentence": "The game must be paused", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "YouTubePlayables::IsSdkReady" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "eventsFunctionContext.returnValue = gdjs._YTGame._status.onPause;", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "description": "Check if the player has resumed the game through YouTube UI.\nThis is a mandatory requirement and the game must be resumed on demand.", + "fullName": "The game is resumed", + "functionType": "Condition", + "name": "OnResume", + "sentence": "The game must be resumed", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "YouTubePlayables::IsSdkReady" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": "eventsFunctionContext.returnValue = gdjs._YTGame._status.onResume;", + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ] + } + ], + "parameters": [], + "objectGroups": [] + }, + { + "async": true, + "description": "Saves game data to YouTube in the form of a serialized string.", + "fullName": "Save data", + "functionType": "Action", + "name": "SaveData", + "sentence": "Save _PARAM1_ to YouTube (store result state in _PARAM2_)", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "YouTubePlayables::IsSdkReady" + }, + "parameters": [ + "", + "" + ] + }, + { + "type": { + "value": "YouTubePlayables::InPlayablesEnvironment" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "gdjs._YTGame.game.saveData(eventsFunctionContext.getArgument(\"Data\"))", + " .then(() => {", + " eventsFunctionContext.getArgument(\"Status\").setString(\"ok\");", + " eventsFunctionContext.task.resolve();", + " })", + " .catch((error) => {", + " eventsFunctionContext.getArgument(\"Status\").setString(\"error\");", + " eventsFunctionContext.task.resolve();", + " });" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ] + } + ], + "parameters": [ + { + "description": "Data to save", + "longDescription": "YouTube accepts text only. Use `ToJSON()` to stringify and store a whole variable.", + "name": "Data", + "type": "string" + }, + { + "description": "Callback variable with state (ok or error)", + "name": "Status", + "type": "variable" + } + ], + "objectGroups": [] + }, + { + "async": true, + "description": "Loads game data from YouTube in the form of a serialized string.", + "fullName": "Load data", + "functionType": "Action", + "name": "LoadData", + "sentence": "Load_PARAM1_ from YouTube (store result state in _PARAM2_)", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "YouTubePlayables::IsSdkReady" + }, + "parameters": [ + "", + "" + ] + }, + { + "type": { + "value": "YouTubePlayables::InPlayablesEnvironment" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "gdjs._YTGame.game.loadData()", + " .then((data) => {", + " eventsFunctionContext.getArgument(\"Data\").setString(data);", + " eventsFunctionContext.getArgument(\"Status\").setString(\"ok\");", + " eventsFunctionContext.task.resolve();", + " })", + " .catch((error) => {", + " eventsFunctionContext.getArgument(\"Status\").setString(\"error\");", + " eventsFunctionContext.task.resolve();", + " });" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ] + } + ], + "parameters": [ + { + "description": "Variable to store data", + "longDescription": "YouTube will return text. If you have saved a stringified variable, use the \"Convert JSON to a variable\" action.", + "name": "Data", + "type": "variable" + }, + { + "description": "Callback variable with state (ok or error)", + "name": "Status", + "type": "variable" + } + ], + "objectGroups": [] + }, + { + "async": true, + "description": "Sends a score to YouTube.\nThe score should represent one dimension of progress within the game. If there are multiple dimensions, you must choose one dimension to be consistent. Scores will be sorted and the highest score will be displayed in YouTube UI so any in-game high score UI should align with what is being sent through this action.", + "fullName": "Send score", + "functionType": "Action", + "name": "SendScore", + "sentence": "Send score _PARAM1_ to YouTube (store result state in _PARAM2_)", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "YouTubePlayables::IsSdkReady" + }, + "parameters": [ + "", + "" + ] + }, + { + "type": { + "value": "YouTubePlayables::InPlayablesEnvironment" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "gdjs._YTGame.engagement.sendScore({ value: eventsFunctionContext.getArgument(\"Score\") })", + " .then(() => {", + " eventsFunctionContext.getArgument(\"Status\").setString(\"ok\");", + " eventsFunctionContext.task.resolve();", + " })", + " .catch((error) => {", + " eventsFunctionContext.getArgument(\"Status\").setString(\"error\");", + " eventsFunctionContext.task.resolve();", + " });" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ] + } + ], + "parameters": [ + { + "description": "Score", + "name": "Score", + "type": "expression" + }, + { + "description": "Callback variable with state (ok or error)", + "name": "Status", + "type": "variable" + } + ], + "objectGroups": [] + }, + { + "async": true, + "description": "Requests YouTube to open content corresponding to the provided video ID.\nGenerally, this will open the video in a new tab on web and in the miniplayer on mobile.", + "fullName": "Open a YouTube video", + "functionType": "Action", + "name": "OpenYTContent", + "sentence": "Open the YouTube video with ID _PARAM1_ (store result state in _PARAM2_)", + "events": [ + { + "type": "BuiltinCommonInstructions::Standard", + "conditions": [ + { + "type": { + "value": "YouTubePlayables::IsSdkReady" + }, + "parameters": [ + "", + "" + ] + }, + { + "type": { + "value": "YouTubePlayables::InPlayablesEnvironment" + }, + "parameters": [ + "", + "" + ] + } + ], + "actions": [], + "events": [ + { + "type": "BuiltinCommonInstructions::JsCode", + "inlineCode": [ + "gdjs._YTGame.engagement.openYTContent({ id: eventsFunctionContext.getArgument(\"VideoID\") })", + " .then(() => {", + " eventsFunctionContext.getArgument(\"Status\").setString(\"ok\");", + " eventsFunctionContext.task.resolve();", + " })", + " .catch((error) => {", + " eventsFunctionContext.getArgument(\"Status\").setString(\"error\");", + " eventsFunctionContext.task.resolve();", + " });" + ], + "parameterObjects": "", + "useStrict": true, + "eventsSheetExpanded": true + } + ] + } + ], + "parameters": [ + { + "description": "Video ID", + "name": "VideoID", + "type": "string" + }, + { + "description": "Callback variable with state (ok or error)", + "name": "Status", + "type": "variable" + } + ], + "objectGroups": [] + } + ], + "eventsBasedBehaviors": [], + "eventsBasedObjects": [] +} \ No newline at end of file