本指南介绍如何在运行 HTML5 构建时在 JavaScript 和 Lua 代码之间进行交互。
HTML5 构建仍处于测试阶段。本指南中的内容可能会有所更改。
HTML5 提供了对各种 API(包括内置 API 和第三方集成)的极其丰富的访问权限。在 Lua 中为所有这些 API 创建绑定是不可能的。这就是 Solar2D 提供一种极其简单的方式在 Lua 和 JavaScript 代码之间桥接的原因。
当尝试从 Lua 中 require 一个模块时,可以利用 HTML5 构建中包含的 JavaScript 模块加载器。它会在项目根目录中查找扩展名为 .js 的文件。
例如
local demo_plugin = require "demo_plugin"
在 Web 浏览器中运行时,也会查找 demo_plugin.js 作为模块加载。请注意,模拟器无法执行 JavaScript 插件。
为了加载模块,该文件必须定义一个与模块名称相同的全局对象,在本例中为 demo_plugin。以下是 demo_plugin.js 的示例内容
demo_plugin = {
init: function() {
console.log( "Init was called" );
}
}
您可以在文件的正文中执行其他 JS 代码,但建议将所有初始化操作移到函数中,例如 init。
请注意,如果文件包含语法错误,则在 HTML5 构建的运行时期间会产生运行时错误。如果您看到 ERROR: module "demo_plugin" not found 错误弹出窗口,请从浏览器的开发者工具中检查 JavaScript 控制台。如果它有类似 Module file is loaded, but object 'demo_plugin' is not found! 的警告,则表示模块包含语法错误,因此未能创建全局模块对象。
开发总是出错的插件可能会很麻烦,因为 JS 代码在模拟器中不起作用。为此,我们建议创建一个 Lua 包装器。它应该检查 JavaScript 是否可以执行,如果可以则执行。
为此,您需要创建两个文件:demo_plugin.lua
if system.getInfo("platform") == 'html5' then
return require "demo_plugin_js"
else
local lib = {}
setmetatable( lib, {__index = function( t, k )
return function()
print( "WARNING: Placeholder is called for " .. k )
end
end} )
return lib
end
这个简单的 Lua 包装器将为所有请求返回一个虚拟函数,除非它在 html5 平台上运行。在这种情况下,它将返回包含在 demo_plugin_js.js 文件中的真实插件
demo_plugin_js = {
init: function() {
console.log( "Init was called" );
}
}
请注意,模块的对象也必须重命名为 demo_plugin_js。
我们试图使 Lua 和 JavaScript 之间的桥接尽可能无缝。我们的桥接只在一个方向上工作:您可以从 Lua 调用 JS 模块对象的方法或访问其属性。
为了演示这一点,让我们向 demo_plugin_js.js 添加一些属性
demo_plugin_js = {
init: function() {
console.log( "Init was called" );
},
someInt: 42,
someString: "Hello World!",
log: function( p ) {
console.log( "Log called", p, this.someInt, this.someString );
}
}
以下是我们如何从 main.lua 访问它们
local demo = require "demo_plugin" demo.init() print( "Values:", demo.someInt, demo.someString ) demo.someInt = -1 demo.someString = "Hi!" demo.log( 42 )
现在将其构建为 HTML5。确保打开 /index-debug.html 以查看 Lua 的 print 指令的输出。它隐藏在默认索引文件中。您应该看到一切按预期工作。方法正在被调用,打印的值正在显示,并且分配的属性也能正常工作。作为参数传递、分配给属性或由方法返回的值将被复制并进行简单的类型转换,以便在 JS 和 Lua 环境之间传递。如果类型无法复制或透明地转换,则会被省略。
我们已经看到从 Lua 调用 JavaScript 函数非常简单。但通常需要反过来 - 在某些 JavaScript 异步操作完成后调用 Lua 的回调函数。这很棘手,因为 JavaScript 没有提供任何与垃圾回收器交互的机制。更简单的值类型(如字符串、数字或事件表)可以在 Lua 和 JavaScript 之间复制,因此内存泄漏不是问题,因为副本在每一侧都单独处理。另一方面,函数必须与其环境保持连接才能执行预期任务。尽管如此,Solar2D 提供了两种方法,允许将 Lua 函数传递给 Javascript
在 main.lua 中
local function callbackFunction( message )
print( message )
end
local demo = require "demo_plugin"
demo.callback = callbackFunction
demo.execute()
在 demo_plugin_js.js 中
demo_plugin_js = {
execute: function() {
this.callback( "Hello World" );
}
}
这将按预期工作,并将“Hello World”打印到 index-debug.html 控制台。如果 demo.callback 是从 Lua 分配的,则 JS 模块加载器将负责内存管理。如果您想从 JavaScript 向 this.callback 分配某些内容,请确保事先将其*释放*。下一节将详细介绍这一点。
前一种方法虽然简单,但从 Lua 的角度来看却不是很优雅或地道。在 Lua 中,我们通常将函数作为参数传递。这会导致与其他语言的接口桥接出现问题,因为我们必须“持有”传递的函数。当使用 Lua C API 时,我们必须手动告诉垃圾回收器正在使用特定函数,并且不应将其作为垃圾回收。
JS 模块加载器中实现了类似的方法。当函数从 Lua 传递到 JavaScript 时,它会被转换为*引用*,您可以选择“持有”它并创建一个可调用的 JavaScript 函数。当不再需要它时,您应该释放它以防止内存泄漏。
以下是处理传递给 JavaScript 的函数的所有 API
LuaIsFunction 返回 boolean 值。如果传递的值表示有效的 Lua 函数*引用*,则为 true。LuaCreateFunction 将 Lua 函数*引用*转换为可调用的 JavaScript 函数并返回它。LuaReleaseFunction 释放对底层 Lua 函数的*引用*。调用已释放的函数将导致无操作(不执行任何操作)。操作方式应如下:接收 Lua 函数*引用*作为参数。从此*引用*创建一个 JavaScript 函数包装器。调用包装器函数,并在不再需要时释放它。
让我们用一个例子来演示它是如何工作的:main.lua
local function callbackFunction( message )
print( message )
end
local demo = require "demo_plugin"
demo.execute( callbackFunction )
demo_plugin_js.js:
demo_plugin_js = {
execute: function( callbackReference ) {
if( LuaIsFunction( callbackReference ) ) {
var f = LuaCreateFunction( callbackReference );
f( "Hello World" );
LuaReleaseFunction( f );
}
}
}
这种方法也可以用于异步调用。在这种情况下,请确保在接收参数后立即使用 LuaCreateFunction。所有函数引用在退出传递给它们的方法后立即释放
demo_plugin_js.js:
demo_plugin_js = {
execute: function( callbackReference ) {
if( LuaIsFunction( callbackReference ) ) {
var f = LuaCreateFunction( callbackReference );
setTimeout( function() {
f( "Hello World" );
LuaReleaseFunction( f );
}, 1000 )
}
}
}
要查看真实插件的示例,请查看我们在 GitHub 上提供的 VK Direct Games 插件。让我们回顾一些值得注意的部分。
init()函数 init() 最有趣。它分为 2 个部分。首先 - 它记住回调函数以将事件分派给
LuaReleaseFunction( this.callback );
if ( LuaIsFunction( callback ) ) {
this.callback = LuaCreateFunction( callback );
} else {
this.callback = function(){};
}
第一行释放现有的回调函数(如果有)。然后,它从引用创建新的回调函数,并将其分配给 callback 属性。如果没有设置新的回调函数,我们只使用空函数作为回调函数,以防止运行时错误和过度检查。
第二部分
if ( this.init_internal ) {
this.init_internal();
this.init_internal = null;
}
它调用 init_internal 方法,然后将其设置为 null,这样它就不会再次被调用。这是确保 init_internal 只被调用一次的简单方法。
init_internal()VK Direct Games 是一个第三方集成。我们必须使用外部 JavaScript 库来使用它。该库在 init_internal() 方法中加载和初始化。让我们来看看
init_internal: function()
{
var script = document.createElement('script');
script.setAttribute('src', 'https://vk.com/js/api/mobile_sdk.js');
script.setAttribute('type', 'text/javascript');
script.setAttribute('charset', 'utf-8');
script.onerror = function()
{
// ...
};
script.onload = function()
{
// ... initOK = ... initFail = ...
VK.init(initOK, initFail, '5.60');
};
document.head.appendChild(script);
}
在这段代码中,我们以编程方式创建一个 <script/> 元素并将其附加到我们的 HTML 页面。我们还设置了 onerror 和 onload 处理程序,我们在其中分派消息。此外,我们在脚本加载时调用 VK.init 以初始化 VK API。查看原始代码以获取更多详细信息。
遵循现有的 插件提交指南。要添加 HTML5 JS 插件,请将您的 JS 和 Lua 文件与 metadata.lua 一起放在与其他平台插件相邻的 web 文件夹中。
示例 metadata.lua
local metadata = {
plugin =
{
format = 'js',
},
}
return metadata
使用 VK Direct Games 插件 作为示例。
如果您有任何问题或想法,请随时加入论坛或 Discord上的社区讨论。