Mit der Corona Game Engine können Sie plattformübergreifende Anwendungen und Spiele erstellen. Aber manchmal reicht die von ihnen bereitgestellte API nicht aus. In solchen Fällen gibt es Corona Native , mit dem Sie die Funktionalität mithilfe von nativem Code für jede Plattform erweitern können.
Dieser Artikel beschreibt die Verwendung von Java in Corona-Projekten für Android
Um zu verstehen, was in diesem Artikel passiert, benötigen Sie Grundkenntnisse in Java, Lua und der Corona-Engine
Erste Schritte
Corona und Android Studio müssen auf dem Computer installiert sein
Der Corona-Installationsordner enthält auch die Projektvorlage: Native \ Projektvorlage \ App. Kopieren Sie den gesamten Ordner und benennen Sie ihn in den Namen Ihres Projekts um.
Vorlagenanpassung
Hinweis: Ich habe den neuesten öffentlichen Build für Corona verwendet - 2017.3184 . In neuen Versionen kann sich die Vorlage ändern, und einige Vorbereitungen aus diesem Kapitel sind nicht mehr erforderlich.
Für Android benötigen wir 2 Ordner: Corona und Android
Löschen Sie Images.xcassets und LaunchScreen.storyboardc aus dem Corona- Ordner. Diese Ordner werden nicht benötigt. In der Datei main.lua löschen wir auch den gesamten Code - wir beginnen mit der Erstellung des Projekts von Grund auf neu. Wenn Sie ein vorhandenes Projekt verwenden möchten, ersetzen Sie alle Dateien im Corona-Ordner durch Ihre eigenen
Der Android- Ordner ist ein fertiges Projekt für Android Studio, wir müssen es öffnen. Die erste Nachricht aus dem Studio lautet "Gradle-Synchronisierung fehlgeschlagen". Build.gradle muss repariert werden:

Fügen Sie einen Link zu Repositorys in Buildscript hinzu, um die Situation zu beheben. Ich habe auch die Version im Klassenpfad 'com.android.tools.build:gradle' in eine neuere geändert.
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 }
Der nächste Schritt besteht darin, gradle-wrapper.properties zu ändern. Sie können es manuell ändern, indem Sie die Version von gradle in DistributionUrl ersetzen. Oder lassen Sie das Studio alles für Sie tun.

Außerdem müssen Sie build.gradle für das App-Modul reparieren: In cleanAssets müssen Sie die Löschzeile "$ projectDir / build / intermediates / jniLibs" hinzufügen , ohne die Sie vor jedem Start ein sauberes Projekt durchführen müssen ( von hier aus ).
Nachdem die Synchronisierung erfolgreich war, gibt es nur noch wenige Warnungen in Bezug auf die veraltete buildToolsVersion und die alte Syntax in der Konfiguration. Sie zu korrigieren ist nicht schwierig.
Jetzt im Studio sehen wir 2 Module: App und Plugin. Benennen Sie die Anwendung (com.mycompany.app) und das Plugin (plugin.library) um, bevor Sie fortfahren.
Weiter im Code heißt das Plugin plugin.habrExamplePlugin
Das Plugin enthält standardmäßig die LuaLoader-Klasse - es ist für die Verarbeitung von Anrufen aus Lua-Code verantwortlich. Es gibt bereits Code, aber lassen Sie uns ihn löschen.
LuaLoader-Code 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; } }
Verwenden des Plugin-Codes aus dem Lua-Code
Corona Native verwendet jnlua zum Binden zwischen Java- und Lua-Code. LuaLoader implementiert die Schnittstelle jnlua.JavaFunction, sodass die Aufrufmethode im Lua-Code verfügbar ist. Um sicherzustellen, dass alles in Ordnung ist, fügen Sie den Protokollierungscode zu LuaLoader.invoke hinzu und erstellen Sie das erforderliche Plugin in 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)
Nach dem Start der Anwendung werden in den Protokollen die folgenden zwei Zeilen angezeigt:
D / Corona native: Lua Loader Aufruf aufgerufen
Ich / Corona: Test wahr
Daher hat unsere Anwendung das Plugin heruntergeladen und erfordert eine Rückgabe von true. Versuchen wir nun, eine Lua-Tabelle mit Funktionen aus Java-Code zurückzugeben.
Um dem Modul Funktionen hinzuzufügen, verwenden wir die Schnittstelle jnlua.NamedJavaFunction. Ein Beispiel für eine einfache Funktion ohne Argumente und ohne Rückgabewert:
class HelloHabrFunction implements NamedJavaFunction { @Override public String getName() { return "helloHabr"; } @Override public int invoke(LuaState L) { Log.d("Corona native", "Hello Habr!"); return 0; } }
Um unsere neue Funktion in lua zu registrieren, verwenden wir die Methode 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);
Dieser Code bedarf einer zusätzlichen Erläuterung:
LuaState, ein Parameter der Aufrufmethode, stellt im Wesentlichen einen Wrapper über die virtuelle Lua-Maschine dar (bitte korrigieren Sie mich, wenn ich etwas falsch mache). Für diejenigen, die mit der Verwendung von Lua-Code aus C vertraut sind, entspricht LuaState dem Zeiger lua_State in C.
Für diejenigen, die in den Dschungel der Arbeit mit Lua eintauchen möchten, empfehle ich, das Handbuch zu lesen , beginnend mit The Application Program Interface
Wenn also invoke aufgerufen wird, erhalten wir LuaState. Es hat einen Stapel, der Parameter enthält, die vom Lua-Code an unsere Funktion übergeben werden. In diesem Fall ist dies der Name des Moduls, da LuaLoader ausgeführt wird, wenn der Aufruf erforderlich ist ("plugin.habrExamplePlugin").
Die vom Aufruf zurückgegebene Anzahl gibt die Anzahl der Variablen vom Stapel an, die an den Lua-Code zurückgegeben werden. Im Fall von require hat diese Zahl keine Auswirkung, aber wir werden dieses Wissen später nutzen, indem wir eine Funktion erstellen, die mehrere Werte zurückgibt
Hinzufügen von Feldern zum Modul
Zusätzlich zu den Funktionen können wir dem Modul auch zusätzliche Felder hinzufügen, z. B. die Version:
luaState.register(libName, luaFunctions);
In diesem Fall haben wir den Index -2 verwendet, um anzuzeigen, dass das Feld für unser Modul festgelegt werden muss. Ein negativer Index bedeutet, dass die Zählung am Ende des Stapels beginnt. -1 zeigt auf die Zeichenfolge "0.1.2" (in lua beginnen die Indizes mit eins).
Um den Stapel nicht zu verstopfen, empfehle ich nach dem Festlegen des Felds, luaState.pop (1) aufzurufen - wirft 1 Element vom Stapel.
Vollständiger LuaLoader-Code @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; } }
Funktionsbeispiele
Ein Beispiel für eine Funktion, die mehrere Zeichenfolgen verwendet und diese über den Zeichenfolgengenerator verkettetImplementierung:
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){
Verwendung in Lua:
local joinedString = habrPlugin.stringJoin("this", " ", "was", " ", "concated", " ", "by", " ", "Java", "!", " ", "some", " ", "number", " : ", 42); print(joinedString)
Beispiel für die Rückgabe mehrerer WerteKlasse SumFunction implementiert NamedJavaFunction {
Überschreiben
public String getName () {
return "sum";
}}
@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 - Verwenden von Java-Klassen direkt in Lua
Die jnlua-Bibliothek verfügt über eine spezielle JavaReflector-Klasse, die für die Erstellung einer lua-Tabelle aus einem Java-Objekt verantwortlich ist. Auf diese Weise können Sie Klassen in Java schreiben und sie für die zukünftige Verwendung dem Lua-Code übergeben.
Dies zu tun ist ganz einfach:
Klassenbeispiel
@SuppressWarnings({"unused"}) public class Calculator { public int sum(int number1, int number2){ return number1 + number2; } public static int someStaticMethod(){ return 4; } }
Hinzufügen einer Instanz dieser Klasse zu unserem Modul
luaState.pushJavaObject(new Calculator()); luaState.setField(-2, "calc"); luaState.pop(1);
Verwendung in 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())
Beachten Sie den Doppelpunkt im Klassenmethodenaufruf. Für statische Methoden müssen Sie auch einen Doppelpunkt verwenden.
Dann bemerkte ich eine interessante Funktion des Reflektors: Wenn wir nur eine Instanz der Klasse an lua übergeben, ist der Aufruf der statischen Methode über getClass () möglich. Nach einem Aufruf über getClass () werden jedoch nachfolgende Aufrufe für das Objekt selbst ausgelöst:
print("call method of java object", calc:sum(3,4))
Mit getClass () können wir auch neue Objekte direkt in lua erstellen:
local newInstance = calc:getClass():new()
Leider konnte ich Calculator.class aufgrund von "java.lang.IllegalArgumentException: illegaler Typ" in setField nicht im Modulfeld speichern .
Erstellen und Aufrufen von Lua-Funktionen im laufenden Betrieb
Dieser Abschnitt wurde angezeigt, weil die Krone nicht die Möglichkeit bietet, über ihre API direkt in Java auf Funktionen zuzugreifen. Mit jnlua.LuaState können Sie jedoch beliebigen Lua-Code laden und ausführen:
class CreateDisplayTextFunction implements NamedJavaFunction{
Denken Sie daran, die Funktion über LuaLoader.invoke zu registrieren, ähnlich wie in den vorherigen Beispielen
Der Anruf in Lua:
habrPlugin.createText("Hello Habr!")
Fazit
Somit kann Ihre Android-Anwendung alle nativen Funktionen der Plattform nutzen. Der einzige Nachteil dieser Lösung besteht darin, dass Sie nicht mehr Corona Simulator verwenden können, was die Entwicklung verlangsamt (ein Neustart des Simulators erfolgt fast sofort, im Gegensatz zum Debuggen auf einem Emulator oder Gerät, für das Build + Install erforderlich ist).
Nützliche Links
Vollständiger Code auf Github verfügbar
Corona Native Dokumentation
3) Eines der Jnlua-Repositories . Hat mir geholfen, den Zweck einiger Funktionen zu verstehen.