Corona Native para Android: uso de código Java personalizado en un juego escrito en Corona

El motor de juegos Corona te permite crear aplicaciones y juegos multiplataforma. Pero a veces la API que proporcionan no es suficiente. Para tales casos, existe Corona Native , que le permite ampliar la funcionalidad utilizando código nativo para cada plataforma.


Este artículo discutirá el uso de Java en proyectos Corona para Android


Para comprender lo que sucede en el artículo, necesita conocimientos básicos de Java, Lua y el motor Corona.


Empezando


Corona y Android Studio deben estar instalados en la computadora


La carpeta de instalación de Corona también contiene la plantilla del proyecto: Native \ Project Template \ App. Copie la carpeta completa y cámbiele el nombre al nombre de su proyecto.


Personalización de plantillas


Nota: Usé la última versión pública disponible para Corona: 2017.3184 . En las nuevas versiones, la plantilla puede cambiar y ya no se necesitarán algunos preparativos de este capítulo.


Para Android necesitamos 2 carpetas dentro: Corona y Android


De la carpeta Corona , elimine Images.xcassets y LaunchScreen.storyboardc ; no necesitamos estas carpetas. En el archivo main.lua , también eliminamos todo el código; comenzaremos a crear el proyecto desde cero. Si desea utilizar un proyecto existente, reemplace todos los archivos de la carpeta Corona con los suyos.


La carpeta de Android es un proyecto terminado para Android Studio, necesitamos abrirla. El primer mensaje del estudio será "Error de sincronización de Gradle". Necesito arreglar build.gradle:


construir gradle


Para solucionar la situación, agregue un enlace a los repositorios en buildscript. También cambié la versión en classpath 'com.android.tools.build:gradle' a una más nueva.


Build.gradle code
// 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 } 

El siguiente paso es cambiar gradle-wrapper.properties . Puede cambiarlo manualmente reemplazando la versión de gradle en distributionUrl . O deja que el estudio haga todo por ti.


gradle-wrapper.properties


Además, debe corregir build.gradle para el módulo de la aplicación: en cleanAssets debe agregar la línea de eliminación "$ projectDir / build / intermediates / jniLibs" , sin la cual tendrá que hacer un proyecto limpio antes de cada inicio ( tomado de aquí )


Ahora que la sincronización es exitosa, solo hay algunas advertencias relacionadas con la versión obsoleta buildToolsVersion y la sintaxis anterior en la configuración. Corregirlos no es difícil.


Ahora en el estudio vemos 2 módulos: aplicación y complemento. Cambie el nombre de la aplicación (com.mycompany.app) y el complemento (plugin.library) antes de continuar.


Además en el código, el complemento se llamará plugin.habrExamplePlugin


El complemento contiene la clase LuaLoader de manera predeterminada: será responsable de manejar las llamadas desde el código lua. Ya hay un código, pero eliminémoslo.


Código 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; } } 

Usando el código del complemento del código lua


Corona Native usa jnlua para el enlace entre java y el código lua. LuaLoader implementa la interfaz jnlua.JavaFunction, por lo que su método de invocación está disponible desde el código lua. Para asegurarse de que todo esté en orden, agregue el código de registro a LuaLoader.invoke y realice el complemento require en main.lua


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

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

Una vez iniciada la aplicación, entre los registros veremos las siguientes 2 líneas:


D / Corona nativo: invocación de Lua Loader llamada
I / Corona: prueba verdadera

Por lo tanto, nuestra aplicación descargó el complemento y requiere devoluciones verdaderas. Ahora intentemos devolver una tabla lua con funciones del código Java.


Para agregar funciones al módulo, usamos la interfaz jnlua.NamedJavaFunction. Un ejemplo de una función simple sin argumentos y sin valor de retorno:


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

Para registrar nuestra nueva función en lua, utilizamos el método 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; } 

Este código requiere una explicación adicional:


LuaState, un parámetro del método invoke, representa esencialmente un contenedor sobre la máquina virtual Lua (corríjame si lo equivoco). Para aquellos familiarizados con el uso del código lua de C, LuaState es el mismo que el puntero lua_State en C.


Para aquellos que desean profundizar en la jungla de trabajar con lua, les recomiendo leer el manual, comenzando con la interfaz del programa de aplicación


Entonces, cuando se invoca invoke, obtenemos LuaState. Tiene una pila que contiene parámetros pasados ​​a nuestra función desde el código lua. En este caso, este es el nombre del módulo, ya que LuaLoader se ejecuta cuando se requiere una llamada ("plugin.habrExamplePlugin").


El número devuelto por invoke muestra el número de variables de la pila que se devolverán al código lua. En el caso de require, este número no tiene efecto, pero usaremos este conocimiento más adelante creando una función que devuelva varios valores


Agregar campos al módulo


Además de las funciones, también podemos agregar campos adicionales al módulo, por ejemplo, la versión:


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

En este caso, usamos el índice -2 para indicar que el campo necesita ser configurado para nuestro módulo. Un índice negativo significa que el conteo comienza al final de la pila. -1 apuntará a la cadena "0.1.2" (en lua, los índices comienzan con uno).


Para no obstruir la pila, después de configurar el campo, recomiendo llamar a luaState.pop (1): arroja 1 elemento de la pila.


Código completo de 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; } } 

Ejemplos de funciones


Un ejemplo de una función que toma varias cadenas y las concatena a través del generador de cadenas

Implementación


 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; } } 

Uso en lua:


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

Ejemplo de devolución de valores múltiples

La clase SumFunction implementa NamedJavaFunction {
Anular
public String getName () {
devolver "suma";
}


 @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; } 

}


Reflexión de Java: uso de clases de Java directamente en lua


La biblioteca jnlua tiene una clase especial JavaReflector que se encarga de crear una tabla lua a partir de un objeto java. Por lo tanto, puede escribir clases en java y darlas al código lua para uso futuro.


Hacer esto es bastante simple:


Ejemplo de clase


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

Agregar una instancia de esta clase a nuestro módulo


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

Uso en 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()) 

Tenga en cuenta los dos puntos en la llamada al método de clase. Para métodos estáticos, también debe usar dos puntos.


Entonces noté una característica interesante del reflector: si pasamos solo una instancia de la clase a lua, entonces es posible llamar a su método estático a través de getClass (). Pero después de una llamada a través de getClass (), se activarán llamadas posteriores en el objeto en sí:


 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         

Además, usando getClass (), podemos crear nuevos objetos directamente en lua:


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

Desafortunadamente, no pude guardar Calculator.class en el campo del módulo debido a "java.lang.IllegalArgumentException: tipo ilegal" dentro de setField .


Crear y llamar a funciones lua sobre la marcha


Esta sección apareció porque la corona no proporciona la capacidad de acceder a las funciones desde su API directamente en Java. Pero jnlua.LuaState le permite cargar y ejecutar código lua arbitrario:


 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; } } 

Recuerde registrar la función a través de LuaLoader.invoke, similar a los ejemplos anteriores


La llamada en lua:


 habrPlugin.createText("Hello Habr!") 

Conclusión


Por lo tanto, su aplicación de Android puede usar todas las capacidades nativas de la plataforma. El único inconveniente de esta solución es que pierde la oportunidad de usar Corona Simulator, que ralentiza el desarrollo (reiniciar el simulador es casi instantáneo, a diferencia de la depuración en un emulador o dispositivo que requiere compilación + instalación)


Enlaces utiles


  1. Código completo disponible en github


  2. Documentación nativa de Corona



3) Uno de los repositorios jnlua . Me ayudó a comprender el propósito de algunas funciones.

Source: https://habr.com/ru/post/es416079/


All Articles