From 83766aaff449d87e5421fdaa4ce734ef12dbcbc7 Mon Sep 17 00:00:00 2001 From: Artsiom Trubchyk Date: Sun, 28 Jan 2024 20:05:59 +0300 Subject: [PATCH] Add `mraid` Lua module --- .defignore | 2 + README.md | 2 +- example/{ => assets/fonts}/vera_large.font | 0 example/main.script | 23 ++- example/play_now.gui | 14 +- playable_ad/build.settings | 2 + playable_ad/gulpfile.js | 4 +- playable_ad/lib/web/library_mraid.js | 19 +++ .../manifests/web/engine_template.html | 91 +++++------ playable_ad/src/ext.cpp | 145 ++++++++++++++++++ 10 files changed, 234 insertions(+), 68 deletions(-) rename example/{ => assets/fonts}/vera_large.font (100%) create mode 100644 playable_ad/build.settings create mode 100644 playable_ad/lib/web/library_mraid.js diff --git a/.defignore b/.defignore index 5aefad0..4069238 100644 --- a/.defignore +++ b/.defignore @@ -1,2 +1,4 @@ /playable_ad/build /playable_ad/node_modules +/playable_ad/package.json +/playable_ad/package-lock.json diff --git a/README.md b/README.md index ee49dd1..cac738b 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ npm install --global gulp-cli ## Usage -Copy the `playable_ad` folder into the root of your project + the `.defignore` file. Then, in the command line: +Copy the `playable_ad` folder into the root of your project and the `.defignore` file. Then, in the command line: ``` cd your_project_folder diff --git a/example/vera_large.font b/example/assets/fonts/vera_large.font similarity index 100% rename from example/vera_large.font rename to example/assets/fonts/vera_large.font diff --git a/example/main.script b/example/main.script index 6dccf33..32c91b8 100644 --- a/example/main.script +++ b/example/main.script @@ -16,18 +16,10 @@ local function toggle_music(self, enabled) end end -local function window_callback(self, event, data) - if event == window.WINDOW_EVENT_FOCUS_LOST then - print("window.WINDOW_EVENT_FOCUS_LOST") - toggle_music(self, false) - elseif event == window.WINDOW_EVENT_FOCUS_GAINED then - print("window.WINDOW_EVENT_FOCUS_GAINED") - toggle_music(self, true) - elseif event == window.WINDOW_EVENT_ICONIFIED then - print("window.WINDOW_EVENT_ICONIFIED") - elseif event == window.WINDOW_EVENT_DEICONIFIED then - print("window.WINDOW_EVENT_DEICONIFIED") - elseif event == window.WINDOW_EVENT_RESIZED then +local function mraid_callback(self, event, data) + if event == mraid.EVENT_VIEWABLE_CHANGE then + print("mraid.EVENT_VIEWABLE_CHANGE:", data.viewable) + toggle_music(self, data.viewable) end end @@ -36,7 +28,10 @@ end -- function init(self) - window.set_listener(window_callback) + -- If `mraid` is available, the game is ran in the webview of Unity Ads or other mraid-based ad system. + if mraid then + mraid.set_listener(mraid_callback) + end toggle_music(self, true) go.set("/music", "euler.z", 20) @@ -53,4 +48,4 @@ function init(self) end function on_input(self, action_id, action) -end \ No newline at end of file +end diff --git a/example/play_now.gui b/example/play_now.gui index db8d5dd..bfa9512 100644 --- a/example/play_now.gui +++ b/example/play_now.gui @@ -1,7 +1,7 @@ script: "/example/play_now.gui_script" fonts { name: "vera_large" - font: "/example/vera_large.font" + font: "/example/assets/fonts/vera_large.font" } textures { name: "main" @@ -33,8 +33,8 @@ nodes { w: 1.0 } size { - x: 329.0 - y: 93.0 + x: 200.0 + y: 100.0 z: 0.0 w: 1.0 } @@ -66,6 +66,10 @@ nodes { alpha: 1.0 template_node_child: false size_mode: SIZE_MODE_AUTO + custom_type: 0 + enabled: true + visible: true + material: "" } nodes { position { @@ -129,6 +133,10 @@ nodes { template_node_child: false text_leading: 1.0 text_tracking: 0.0 + custom_type: 0 + enabled: true + visible: true + material: "" } material: "/builtins/materials/gui.material" adjust_reference: ADJUST_REFERENCE_PARENT diff --git a/playable_ad/build.settings b/playable_ad/build.settings new file mode 100644 index 0000000..0d4fcac --- /dev/null +++ b/playable_ad/build.settings @@ -0,0 +1,2 @@ +[playable_ad] +enabled = 1 diff --git a/playable_ad/gulpfile.js b/playable_ad/gulpfile.js index fbbb14e..55ff9fc 100644 --- a/playable_ad/gulpfile.js +++ b/playable_ad/gulpfile.js @@ -10,6 +10,7 @@ const projectDir = ".."; const buildDir = "build"; const archiveDir = "archive"; const bundleJsWebPath = buildDir + "/output_js-web"; +const buildSettingsPath = "build.settings"; const bobJarVersionInfoUrl = "https://d.defold.com/stable/info.json"; let bobJarVersionInfo = null; // filled by fetchBobVersionInfo() @@ -240,8 +241,9 @@ function buildGame(cb) { "--build-server", options["build-server"], "--variant", options.variant, "--texture-compression", options["texture-compression"], + "--settings", playableAdDir + "/" + buildSettingsPath, ], - (options.settings ? ["--settings", options.settings] : []), + (options.settings ? [options.settings] : []), [ "--bundle-output", playableAdDir + "/" + bundleJsWebPath, "--platform", "js-web", diff --git a/playable_ad/lib/web/library_mraid.js b/playable_ad/lib/web/library_mraid.js new file mode 100644 index 0000000..454d623 --- /dev/null +++ b/playable_ad/lib/web/library_mraid.js @@ -0,0 +1,19 @@ +var LibraryDefMraid = { + $DefMraid: { + viewableChangeFunc: null, + + onViewableChange: function (viewable) { + if (DefMraid.viewableChangeFunc) { + {{{ makeDynCall('vi', 'DefMraid.viewableChangeFunc') }}}(viewable); + } + }, + }, + + DefMraid_SetCallback: function (cbfun) { + DefMraid.viewableChangeFunc = cbfun; + return 1; + }, +}; + +autoAddDeps(LibraryDefMraid, '$DefMraid'); +mergeInto(LibraryManager.library, LibraryDefMraid); diff --git a/playable_ad/manifests/web/engine_template.html b/playable_ad/manifests/web/engine_template.html index 98f07c2..f3151e0 100644 --- a/playable_ad/manifests/web/engine_template.html +++ b/playable_ad/manifests/web/engine_template.html @@ -1,5 +1,6 @@ + {{#playable_ad.enabled}} + {{/playable_ad.enabled}} + {{#playable_ad.enabled}}
@@ -264,67 +267,57 @@ // The main point is that mraid is injected by their webview and we can just use the `mraid.*` methods. // If `mraid` is available, then it's Unity Ads or other mraid-based ad system. if (window.mraid) { - // Defold does not currently have a public API to hook the game startup. - // Therefore, we override this function to figure that: - const old_preloadAndCallMain = Module._preloadAndCallMain; - Module._preloadAndCallMain = function () { - if (!Module._archiveLoaded) { - old_preloadAndCallMain(); - return; - } + (function() { + // Defold does not currently have a public API to hook the game startup. + // Therefore, we override this function to figure that: + var old_preloadAndCallMain = Module._preloadAndCallMain; + Module._preloadAndCallMain = function () { + if (!Module._archiveLoaded) { + old_preloadAndCallMain(); + return; + } - // Wait for the SDK to become ready - if (mraid.getState() === "loading") { - mraid.addEventListener("ready", onSdkReady); - } else { - onSdkReady(); - } - }; + // Wait for the SDK to become ready + if (mraid.getState() === "loading") { + mraid.addEventListener("ready", onSdkReady); + } else { + onSdkReady(); + } + }; - let started = false; - let paused = false; - function startGame() { - console.log("mraid support: start game."); - if (!started) { - started = true; - old_preloadAndCallMain(); - } else { - if (paused) { - paused = false; - GLFW.onFocusChanged(1); + var started = false; + function startGame() { + if (!started) { + started = true; + console.log("mraid support: start game."); + old_preloadAndCallMain(); } } - } - function pauseGame() { - console.log("mraid support: pause game."); - if (!paused) { - paused = true; - GLFW.onFocusChanged(0); - } - } + function viewableChangeHandler(viewable) { + if (viewable) { + startGame(); + } - function viewableChangeHandler(viewable) { - // start/pause/resume gameplay, stop/play sounds - if(viewable) { - startGame(); - } else { - pauseGame(); + if (typeof DefMraid !== 'undefined') { + DefMraid.onViewableChange(viewable); + } } - } - function onSdkReady() { - console.log("mraid support: SDK is ready."); - mraid.addEventListener("viewableChange", viewableChangeHandler); + function onSdkReady() { + console.log("mraid support: SDK is ready."); + mraid.addEventListener("viewableChange", viewableChangeHandler); - // Wait for the ad to become viewable for the first time - if (mraid.isViewable()) { - startGame(); + // Wait for the ad to become viewable for the first time + if (mraid.isViewable()) { + startGame(); + } } - } - console.log("mraid support: hook installed."); + console.log("mraid support: hook installed."); + })(); } + {{/playable_ad.enabled}} diff --git a/playable_ad/src/ext.cpp b/playable_ad/src/ext.cpp index e718416..98dd6d0 100644 --- a/playable_ad/src/ext.cpp +++ b/playable_ad/src/ext.cpp @@ -1,13 +1,158 @@ #include +#if defined(DM_PLATFORM_HTML5) +#include +#include + +typedef void (*DefMraid_OnViewableChange)(const int viewable); +extern "C" void DefMraid_SetCallback(DefMraid_OnViewableChange callback); + +static dmScript::LuaCallbackInfo* m_ListenerCallback = 0; + +enum MraidEvent +{ + EVENT_VIEWABLE_CHANGE = 0, +}; + +static void Mraid_OnViewableChange(const int viewable) +{ + if (!m_ListenerCallback) { + return; + } + + lua_State* L = dmScript::GetCallbackLuaContext(m_ListenerCallback); + DM_LUA_STACK_CHECK(L, 0); + + if (!dmScript::SetupCallback(m_ListenerCallback)) + { + return; + } + + lua_pushnumber(L, EVENT_VIEWABLE_CHANGE); + + lua_newtable(L); + lua_pushstring(L, "viewable"); + lua_pushboolean(L, viewable == 1); + lua_rawset(L, -3); + + dmScript::PCall(L, 3, 0); // self + # user arguments + + dmScript::TeardownCallback(m_ListenerCallback); +} + +/*# sets a mraid event listener + * Sets a mraid event listener. + * + * @name mraid.set_listener + * + * @param callback [type:function(self, event, data)|nil] A callback which receives info about mraid events. Pass an empty function or `nil` if you no longer wish to receive callbacks. + * + * `self` + * : [type:object] The calling script + * + * `event` + * : [type:constant] The type of event. Can be one of these: + * + * - `mraid.EVENT_VIEWABLE_CHANGE` + * - `mraid.EVENT_VIEWABLE_CHANGE` + * + * `data` + * : [type:table] The callback value `data` is a table which currently holds these values: + * + * - [type:boolean] `viewable`. + * + * @examples + * + * ```lua + * local function mraid_callback(self, event, data) + * if event == mraid.EVENT_VIEWABLE_CHANGE then + * print("mraid.EVENT_VIEWABLE_CHANGE:", data.viewable) + * end + * end + * + * function init(self) + * if mraid then + * mraid.set_listener(mraid_callback) + * end + * end + * ``` + */ +static int Mraid_SetListener(lua_State* L) +{ + luaL_checkany(L, 1); + + if (lua_isnil(L, 1)) + { + if (m_ListenerCallback) + dmScript::DestroyCallback(m_ListenerCallback); + m_ListenerCallback = 0; + return 0; + } + + if (m_ListenerCallback) + dmScript::DestroyCallback(m_ListenerCallback); + m_ListenerCallback = dmScript::CreateCallback(L, 1); + + if (!dmScript::IsCallbackValid(m_ListenerCallback)) + return luaL_error(L, "Failed to create callback"); + + return 0; +} + +// Functions exposed to Lua +static const luaL_reg Mraid_methods[] = { + { "set_listener", Mraid_SetListener }, + /* Sentinel: */ + { NULL, NULL } +}; + +static void Mraid_LuaInit(lua_State* L) +{ + const int has_mraid = EM_ASM_INT( + return window.mraid ? 1 : 0; + ); + if (!has_mraid) { + return; + } + + int top = lua_gettop(L); + + // Register lua names + luaL_register(L, "mraid", Mraid_methods); + +#define SETCONSTANT(name) \ + lua_pushnumber(L, (lua_Number) name); \ + lua_setfield(L, -2, #name);\ + + SETCONSTANT(EVENT_VIEWABLE_CHANGE) + +#undef SETCONSTANT + + lua_pop(L, 1); + assert(top == lua_gettop(L)); +} +#endif + static dmExtension::Result InitializeExt(dmExtension::Params* params) { +#if defined(DM_PLATFORM_HTML5) + Mraid_LuaInit(params->m_L); + DefMraid_SetCallback(Mraid_OnViewableChange); +#endif + return dmExtension::RESULT_OK; } static dmExtension::Result FinalizeExt(dmExtension::Params* params) { +#if defined(DM_PLATFORM_HTML5) + if (m_ListenerCallback) + dmScript::DestroyCallback(m_ListenerCallback); + m_ListenerCallback = 0; + DefMraid_SetCallback(0); +#endif + return dmExtension::RESULT_OK; }