适用于Android的Corona Native-在用Corona编写的游戏中使用自定义Java代码

Corona游戏引擎允许您创建跨平台的应用程序和游戏。 但是有时他们提供的API是不够的。 在这种情况下,可以使用Corona Native ,您可以使用每个平台的本机代码来扩展功能。


本文将讨论Java在Android的Corona项目中的使用


要了解本文中发生的事情,您需要Java,Lua和Corona引擎的基本知识


开始使用


必须在计算机上安装CoronaAndroid Studio


Corona安装文件夹还包含项目模板:Native \ Project Template \ App。 复制整个文件夹,然后将其重命名为项目的名称。


模板定制


注意:我使用了可用于Corona- 2017.3184的最新公共版本。 在新版本中,模板可能会更改,并且不再需要本章的某些准备。


对于Android,我们需要2个文件夹: Coronaandroid


Corona文件夹中删除Images.xcassetsLaunchScreen.storyboardc-我们不需要这些文件夹。 在main.lua文件中, 我们还删除了所有代码-我们将从头开始创建项目。 如果要使用现有项目,请用自己的项目替换Corona文件夹中的所有文件。


android文件夹是Android Studio的完成项目,我们需要打开它。 来自录音室的第一条消息将是“ Gradle sync failed”。 需要修复build.gradle:


建立gradle


要解决这种情况,请在buildscript中添加一个到存储库的链接。 我还将类路径'com.android.tools.build:gradle'中的版本更改为较新的版本。


Build.gradle代码
// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { dependencies { classpath 'com.android.tools.build:gradle:3.1.3' } repositories { jcenter() google() } } allprojects { repositories { jcenter() google() } } task clean(type: Delete) { delete rootProject.buildDir } 

下一步是更改gradle-wrapper.properties 。 您可以通过替换distributionUrl中的gradle版本来手动更改它。 或者让工作室为您做一切。


gradle-wrapper.properties


另外,您需要修复app模块的build.gradle :在cleanAssets中,您需要添加删除“ $ projectDir / build /中间体/ jniLibs” ,否则,您必须在每次启动前做一个干净的项目( 从此处开始)


现在同步已成功,只有很少的警告与过时的buildToolsVersion和配置中的旧语法有关。 纠正它们并不困难。


现在在工作室中,我们看到2个模块:应用程序和插件。 在继续之前,重命名应用程序(com.mycompany.app)和插件(plugin.library)。


在代码的进一步部分,该插件将称为plugin.habrExamplePlugin


该插件默认包含LuaLoader类-它将负责处理来自lua代码的调用。 已经有一些代码,但让我们清除一下。


LuaLoader代码
 package plugin.habrExamplePlugin; import com.naef.jnlua.JavaFunction; import com.naef.jnlua.LuaState; @SuppressWarnings({"WeakerAccess", "unused"}) public class LuaLoader implements JavaFunction { @Override public int invoke(LuaState luaState) { return 0; } } 

使用来自Lua代码的插件代码


Corona Native使用jnlua在Java和lua代码之间进行绑定。 LuaLoader实现了jnlua.JavaFunction接口,因此可以从lua代码中获取其invoke方法。 为了确保一切正常,将日志记录代码添加到LuaLoader.invoke并在main.lua中创建require插件


  @Override public int invoke(LuaState luaState) { Log.d("Corona native", "Lua Loader invoke called"); return 0; } 

 local habrPlugin = require("plugin.habrExamplePlugin") print("test:", habrPlugin) 

启动应用程序后,在日志中,我们将看到以下两行:


D / Corona本机:Lua Loader调用被称为
我/电晕:测试正确

因此,我们的应用程序下载了插件,并要求返回true。 现在,让我们尝试使用Java代码中的函数返回lua表。


要向模块添加功能,我们使用jnlua.NamedJavaFunction接口。 一个没有参数也没有返回值的简单函数的示例:


 class HelloHabrFunction implements NamedJavaFunction { @Override public String getName() { return "helloHabr"; } @Override public int invoke(LuaState L) { Log.d("Corona native", "Hello Habr!"); return 0; } } 

要在lua中注册我们的新函数,我们使用LuaState.register方法:


 public class LuaLoader implements JavaFunction { @Override public int invoke(LuaState luaState) { Log.d("Corona native", "Lua Loader invoke called"); String libName = luaState.toString(1); //      (  require) NamedJavaFunction[] luaFunctions = new NamedJavaFunction[]{ new HelloHabrFunction(), //     }; luaState.register(libName, luaFunctions); //   ,     //  1        lua . //     require      , require     return 1; } 

此代码需要其他说明:


LuaState是invoke方法的参数,本质上表示Lua虚拟机上的包装器(如果我输入错了,请纠正我)。 对于那些熟悉使用C语言编写的lua代码的人来说,LuaState与C语言中的lua_State指针相同。


对于那些想深入研究lua的人,我建议您阅读《应用程序接口》一书中的手册。


因此,当调用invoke时,我们得到LuaState。 它具有一个堆栈,其中包含从lua代码传递给我们的函数的参数。 在这种情况下,这是模块的名称,因为在调用require(“ plugin.habrExamplePlugin”)时执行LuaLoader。


invoke返回的数字显示了堆栈中将返回到lua代码的变量数。 在require的情况下,该数字无效,但是稍后我们将通过创建返回几个值的函数来使用此知识。


向模块添加字段


除了功能,我们还可以向模块添加其他字段,例如版本:


  luaState.register(libName, luaFunctions); //   ,       luaState.pushString("0.1.2"); //     luaState.setField(-2, "version"); //   version   . 

在这种情况下,我们使用-2索引指示需要为我们的模块设置字段。 负索引表示计数从堆栈末尾开始。 -1将指向字符串“ 0.1.2”(在lua中,索引以1开头)。


为了不阻塞堆栈,设置字段后,建议调用luaState.pop(1)-从堆栈中抛出1个元素。


完整的LuaLoader代码
 @SuppressWarnings({"WeakerAccess", "unused"}) public class LuaLoader implements JavaFunction { @Override public int invoke(LuaState luaState) { Log.d("Corona native", "Lua Loader invoke called"); String libName = luaState.toString(1); //      (  require) NamedJavaFunction[] luaFunctions = new NamedJavaFunction[]{ new HelloHabrFunction(), //     }; luaState.register(libName, luaFunctions); //   ,     luaState.register(libName, luaFunctions); //   ,       luaState.pushString("0.1.2"); //     luaState.setField(-2, "version"); //   version   . //  1        lua . //     require      , require     return 0; } } 

功能实例


一个函数示例,该函数采用多个字符串并通过“字符串”构建器将它们连接起来

实现方式:


 class StringJoinFunction implements NamedJavaFunction{ @Override public String getName() { return "stringJoin"; } @Override public int invoke(LuaState luaState) { int currentStackIndex = 1; StringBuilder stringBuilder = new StringBuilder(); while (!luaState.isNone(currentStackIndex)){ String str = luaState.toString(currentStackIndex); if (str != null){ //toString  null  non-string  non-number,  stringBuilder.append(str); } currentStackIndex++; } luaState.pushString(stringBuilder.toString()); return 1; } } 

在lua中的用法:


 local joinedString = habrPlugin.stringJoin("this", " ", "was", " ", "concated", " ", "by", " ", "Java", "!", " ", "some", " ", "number", " : ", 42); print(joinedString) 

返回多个值的示例

SumFunction类实现NamedJavaFunction {
覆写
公共字符串getName(){
返回“和”;
}


 @Override public int invoke(LuaState luaState) { if (!luaState.isNumber(1) || !luaState.isNumber(2)){ luaState.pushNil(); luaState.pushString("Arguments should be numbers!"); return 2; } int firstNumber = luaState.toInteger(1); int secondNumber = luaState.toInteger(1); luaState.pushInteger(firstNumber + secondNumber); return 1; } 

}


Java Reflection-在lua中直接使用Java类


jnlua库具有一个特殊的JavaReflector类,该类负责根据Java对象创建lua表。 因此,您可以使用Java编写类,并将其提供给lua代码以备将来使用。


要做到这一点很简单:


类的例子


 @SuppressWarnings({"unused"}) public class Calculator { public int sum(int number1, int number2){ return number1 + number2; } public static int someStaticMethod(){ return 4; } } 

将此类的实例添加到我们的模块中


  luaState.pushJavaObject(new Calculator()); luaState.setField(-2, "calc"); luaState.pop(1); 

在Lua中的用法:


 local calc = habrPlugin.calc print("call method of java object", calc:sum(3,4)) print("call static method of java object", calc:getClass():someStaticMethod()) 

注意类方法调用中的冒号。 对于静态方法,还必须使用冒号。


然后,我注意到了反射器的一个有趣的功能:如果我们仅将类的实例传递给lua,则可以通过getClass()调用其静态方法。 但是在通过getClass()调用之后,后续调用将在对象本身上触发:


 print("call method of java object", calc:sum(3,4)) -- ok print("exception here", calc:someStaticMethod()) --   "com.naef.jnlua.LuaRuntimeException: no method of class plugin.habrExamplePlugin.Calculator matches 'someStaticMethod()'" print("call static method of java object", calc:getClass():someStaticMethod()) -- ok print("hmm", calc:someStaticMethod()) --    getClass         

另外,使用getClass(),我们可以直接在lua中创建新对象:


 local newInstance = calc:getClass():new() 

不幸的是,由于setField内部存在“ java.lang.IllegalArgumentException:非法类型” ,因此我无法在模块字段中保存Calculator.class。


动态创建和调用lua函数


之所以出现此部分,是因为Crown无法提供直接使用Java从其api访问功能的功能。 但是jnlua.LuaState允许您加载和执行任意lua代码:


 class CreateDisplayTextFunction implements NamedJavaFunction{ //    API  private static String code = "local text = ...;" + "return display.newText({" + "text = text," + "x = 160," + "y = 200," + "});"; @Override public String getName() { return "createText"; } @Override public int invoke(LuaState luaState) { luaState.load(code,"CreateDisplayTextFunction code"); //    ,     luaState.pushValue(1); //        luaState.call(1, 1); //   ,      1 ,    1 return 1; } } 

切记通过LuaLoader.invoke注册该函数,类似于前面的示例


lua中的呼叫:


 habrPlugin.createText("Hello Habr!") 

结论


因此,您的android应用程序可以使用平台的所有本机功能。 该解决方案的唯一缺点是您失去了使用Corona Simulator的能力,这会减慢开发速度(重新启动模拟器几乎是瞬间的,这与在需要构建+安装的模拟器或设备上进行调试不同)


有用的链接


  1. 完整代码可在github上找到


  2. Corona本机文档



3) jnlua存储库之一 。 帮助我了解某些功能的目的。

Source: https://habr.com/ru/post/zh-CN416079/


All Articles