diff --git a/.github/workflows/godot-android-raw-wire.yml b/.github/workflows/godot-android-raw-wire.yml new file mode 100644 index 00000000..70806290 --- /dev/null +++ b/.github/workflows/godot-android-raw-wire.yml @@ -0,0 +1,213 @@ +name: Godot Android raw-wire POC + +on: + push: + branches: + - feature/godot-android-raw-wire-poc + pull_request: + branches: + - upstream/android-oboe + workflow_dispatch: + +permissions: + contents: read + +jobs: + raw-wire-poc: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v5 + + - uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: "17" + + - uses: android-actions/setup-android@v3 + + - name: Install Android SDK components + run: | + yes | sdkmanager --licenses >/dev/null + sdkmanager \ + "platforms;android-36" \ + "build-tools;35.0.1" \ + "ndk;27.0.12077973" \ + "cmake;3.22.1" + + - uses: gradle/actions/setup-gradle@v4 + with: + gradle-version: "8.13" + + - name: Build AMY AAR + run: bash godot/android-hello-world/prepare.sh + + - name: Verify AMY AAR contents + run: | + AAR=godot/android-hello-world/addons/amy_android/amy-service-debug.aar + test -s "$AAR" + unzip -l "$AAR" | tee /tmp/amy-aar.txt + grep -q 'jni/arm64-v8a/libamy_android.so' /tmp/amy-aar.txt + grep -q 'jni/arm64-v8a/libamy_android_client.so' /tmp/amy-aar.txt + grep -q 'jni/x86_64/libamy_android.so' /tmp/amy-aar.txt + grep -q 'jni/x86_64/libamy_android_client.so' /tmp/amy-aar.txt + unzip -p "$AAR" classes.jar > /tmp/amy-classes.jar + unzip -l /tmp/amy-classes.jar | tee /tmp/amy-classes.txt + grep -q 'org/amy/audio/AmyService.class' /tmp/amy-classes.txt + grep -q 'org/amy/audio/AmyClient.class' /tmp/amy-classes.txt + grep -q 'org/amy/audio/AmyAutoStartProvider.class' /tmp/amy-classes.txt + + - name: Install Godot 4.7.2 and Android export templates + run: | + curl -L --fail --retry 3 \ + -o /tmp/godot.zip \ + https://github.com/godotengine/godot/releases/download/4.7.2-stable/Godot_v4.7.2-stable_linux.x86_64.zip + unzip -q /tmp/godot.zip -d /tmp/godot + GODOT_BIN="$(find /tmp/godot -maxdepth 1 -type f -name 'Godot*' | head -n1)" + chmod +x "$GODOT_BIN" + sudo cp "$GODOT_BIN" /usr/local/bin/godot + + curl -L --fail --retry 3 \ + -o /tmp/godot-templates.tpz \ + https://github.com/godotengine/godot/releases/download/4.7.2-stable/Godot_v4.7.2-stable_export_templates.tpz + rm -rf /tmp/godot-templates + mkdir -p /tmp/godot-templates + unzip -q /tmp/godot-templates.tpz -d /tmp/godot-templates + mkdir -p "$HOME/.local/share/godot/export_templates/4.7.2.stable" + cp -a /tmp/godot-templates/templates/. \ + "$HOME/.local/share/godot/export_templates/4.7.2.stable/" + godot --version + + - name: Import Godot project + run: godot --headless --path godot/android-hello-world --import --quit + + - name: Export ARM64 phone APK + run: | + mkdir -p godot/android-hello-world/build + godot --headless \ + --path godot/android-hello-world \ + --install-android-build-template \ + --export-debug "Android ARM64" build/amy-godot-raw-wire-arm64.apk + test -s godot/android-hello-world/build/amy-godot-raw-wire-arm64.apk + + - name: Export x86_64 emulator APK + run: | + godot --headless \ + --path godot/android-hello-world \ + --export-debug "Android CI x86_64" build/amy-godot-raw-wire-x86_64.apk + test -s godot/android-hello-world/build/amy-godot-raw-wire-x86_64.apk + + - name: Verify APK packaging + run: | + ARM=godot/android-hello-world/build/amy-godot-raw-wire-arm64.apk + X86=godot/android-hello-world/build/amy-godot-raw-wire-x86_64.apk + + unzip -l "$ARM" | tee /tmp/arm-apk.txt + grep -q 'lib/arm64-v8a/libgodot_android.so' /tmp/arm-apk.txt + grep -q 'lib/arm64-v8a/libamy_android.so' /tmp/arm-apk.txt + grep -q 'lib/arm64-v8a/libamy_android_client.so' /tmp/arm-apk.txt + ! grep -q 'lib/x86_64/' /tmp/arm-apk.txt + + unzip -l "$X86" | tee /tmp/x86-apk.txt + grep -q 'lib/x86_64/libgodot_android.so' /tmp/x86-apk.txt + grep -q 'lib/x86_64/libamy_android.so' /tmp/x86-apk.txt + grep -q 'lib/x86_64/libamy_android_client.so' /tmp/x86-apk.txt + ! grep -q 'lib/arm64-v8a/' /tmp/x86-apk.txt + + for apk in "$ARM" "$X86"; do + for dex in $(zipinfo -1 "$apk" | grep -E '^classes([0-9]+)?\.dex$'); do + unzip -p "$apk" "$dex" + done | strings > /tmp/classes.txt + grep -q 'Lorg/amy/audio/AmyService;' /tmp/classes.txt + grep -q 'Lorg/amy/audio/AmyClient;' /tmp/classes.txt + grep -q 'Lorg/amy/audio/AmyAutoStartProvider;' /tmp/classes.txt + done + + - name: Upload ARM64 APK + uses: actions/upload-artifact@v4 + with: + name: amy-godot-raw-wire-arm64-apk + path: godot/android-hello-world/build/amy-godot-raw-wire-arm64.apk + if-no-files-found: error + + - name: Enable KVM for Android emulator + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \ + | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Run Godot -> amy.sock -> AMY smoke test + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 35 + arch: x86_64 + profile: pixel_2 + disable-animations: true + emulator-options: -no-window -gpu swiftshader_indirect -no-snapshot -no-boot-anim + script: | + set -e + adb uninstall org.amy.godothello >/dev/null 2>&1 || true + adb install godot/android-hello-world/build/amy-godot-raw-wire-x86_64.apk + + # Arm the already-existing AMY integration-test audio capture before + # Android creates the application process/provider. This is test-only; + # Godot itself does not start or control AmyService. + adb shell run-as org.amy.godothello mkdir -p files + adb shell run-as org.amy.godothello touch files/amy-audio-capture.enable + + adb logcat -c + adb shell am start -W -n org.amy.godothello/com.godot.game.GodotAppLauncher + sleep 12 + + adb logcat -d > /tmp/amy-godot.log + adb shell ps -A > /tmp/amy-processes.txt + adb shell run-as org.amy.godothello ls -la files > /tmp/amy-files.txt + + echo '===== GODOT/AMY LOG =====' + grep -E 'AmyService|AmyAndroid|AmyAudioCapture|Godot (connected|wire:|raw-wire)' /tmp/amy-godot.log || true + echo '===== PROCESSES =====' + grep 'org.amy.godothello' /tmp/amy-processes.txt || true + echo '===== FILES =====' + cat /tmp/amy-files.txt + + grep -q 'AMY listening on private socket' /tmp/amy-godot.log + grep -q 'AMY/Oboe started:' /tmp/amy-godot.log + grep -q 'Godot connected to amy.sock' /tmp/amy-godot.log + grep -q 'Godot wire: v0w0V10.0Z' /tmp/amy-godot.log + test "$(grep -Ec 'Godot wire: v0n(60|62|64|65|67|69|71|72)l1Z' /tmp/amy-godot.log)" -eq 8 + grep -q 'Godot wire: v0n60l1Z' /tmp/amy-godot.log + grep -q 'Godot wire: v0n72l1Z' /tmp/amy-godot.log + grep -q 'Godot raw-wire C scale complete' /tmp/amy-godot.log + ! grep -q 'Godot raw-wire error:' /tmp/amy-godot.log + grep -q 'org.amy.godothello:amy' /tmp/amy-processes.txt + grep -q 'amy.sock' /tmp/amy-files.txt + grep -q 'amy-render.wav' /tmp/amy-files.txt + grep -q 'amy-oboe.wav' /tmp/amy-files.txt + + mkdir -p android/godot-audio-capture + adb exec-out run-as org.amy.godothello cat files/amy-render.wav > android/godot-audio-capture/amy-render.wav + adb exec-out run-as org.amy.godothello cat files/amy-oboe.wav > android/godot-audio-capture/amy-oboe.wav + adb exec-out run-as org.amy.godothello cat files/amy-audio-levels.txt > android/godot-audio-capture/amy-audio-levels.txt + test -s android/godot-audio-capture/amy-render.wav + test -s android/godot-audio-capture/amy-oboe.wav + test -s android/godot-audio-capture/amy-audio-levels.txt + + - name: Analyze audio produced by Godot wire commands + run: | + cat android/godot-audio-capture/amy-audio-levels.txt + python3 tests/check_android_audio_capture.py \ + android/godot-audio-capture/amy-render.wav \ + android/godot-audio-capture/amy-oboe.wav + + - name: Upload diagnostics and captured audio + if: always() + uses: actions/upload-artifact@v4 + with: + name: amy-godot-raw-wire-diagnostics + path: | + /tmp/amy-godot.log + /tmp/amy-processes.txt + /tmp/amy-files.txt + android/godot-audio-capture/ + if-no-files-found: warn diff --git a/android/amy-service/src/main/AndroidManifest.xml b/android/amy-service/src/main/AndroidManifest.xml index cde4251b..682656e0 100644 --- a/android/amy-service/src/main/AndroidManifest.xml +++ b/android/amy-service/src/main/AndroidManifest.xml @@ -1,6 +1,12 @@ + + :-O3;-Wall;-Wextra;-Wno-unused-parameter> ) +target_compile_options(amy_android_client PRIVATE + -O3 -Wall -Wextra -Wno-unused-parameter +) + target_compile_features(amy_android PRIVATE c_std_11 cxx_std_17) +target_compile_features(amy_android_client PRIVATE cxx_std_17) target_link_libraries(amy_android PRIVATE oboe::oboe diff --git a/android/amy-service/src/main/cpp/amy_android_client.cpp b/android/amy-service/src/main/cpp/amy_android_client.cpp new file mode 100644 index 00000000..67cdfff1 --- /dev/null +++ b/android/amy-service/src/main/cpp/amy_android_client.cpp @@ -0,0 +1,73 @@ +#include + +#include +#include +#include + +#include +#include +#include + +namespace { + +int connect_socket(const char *path) { + if (path == nullptr || path[0] == '\0') return -EINVAL; + + sockaddr_un addr{}; + const size_t path_len = std::strlen(path); + if (path_len >= sizeof(addr.sun_path)) return -ENAMETOOLONG; + + addr.sun_family = AF_UNIX; + std::memcpy(addr.sun_path, path, path_len + 1u); + + const int fd = socket(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); + if (fd < 0) return -errno; + + if (connect(fd, reinterpret_cast(&addr), sizeof(addr)) < 0) { + const int saved = errno; + close(fd); + return -saved; + } + return fd; +} + +int send_wire(int fd, JNIEnv *env, jstring wire) { + if (fd < 0) return -ENOTCONN; + if (wire == nullptr) return -EINVAL; + + const jsize len = env->GetStringUTFLength(wire); + if (len <= 0) return -EINVAL; + + const char *bytes = env->GetStringUTFChars(wire, nullptr); + if (bytes == nullptr) return -ENOMEM; + + const ssize_t sent = send(fd, bytes, static_cast(len), MSG_NOSIGNAL); + const int saved = sent < 0 ? errno : 0; + env->ReleaseStringUTFChars(wire, bytes); + + if (sent < 0) return -saved; + if (sent != len) return -EIO; + return 0; +} + +} // namespace + +extern "C" JNIEXPORT jint JNICALL +Java_org_amy_audio_AmyClient_nativeConnect(JNIEnv *env, jclass, jstring socketPath) { + if (socketPath == nullptr) return -EINVAL; + const char *path = env->GetStringUTFChars(socketPath, nullptr); + if (path == nullptr) return -ENOMEM; + const int result = connect_socket(path); + env->ReleaseStringUTFChars(socketPath, path); + return result; +} + +extern "C" JNIEXPORT jint JNICALL +Java_org_amy_audio_AmyClient_nativeSend(JNIEnv *env, jclass, jint fd, jstring wire) { + return send_wire(fd, env, wire); +} + +extern "C" JNIEXPORT void JNICALL +Java_org_amy_audio_AmyClient_nativeClose(JNIEnv *, jclass, jint fd) { + if (fd >= 0) close(fd); +} diff --git a/android/amy-service/src/main/java/org/amy/audio/AmyAutoStartProvider.java b/android/amy-service/src/main/java/org/amy/audio/AmyAutoStartProvider.java new file mode 100644 index 00000000..979c99d6 --- /dev/null +++ b/android/amy-service/src/main/java/org/amy/audio/AmyAutoStartProvider.java @@ -0,0 +1,36 @@ +package org.amy.audio; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; + +import java.io.File; + +/** + * Android lifecycle hook for starting the separate :amy process. + * Godot never starts or stops AmyService; it only connects to amy.sock. + */ +public final class AmyAutoStartProvider extends ContentProvider { + @Override + public boolean onCreate() { + Context context = getContext(); + if (context == null) return false; + + File socket = new File(context.getFilesDir(), AmyService.DEFAULT_SOCKET_NAME); + Intent intent = new Intent(context, AmyService.class); + intent.putExtra(AmyService.EXTRA_SOCKET_PATH, socket.getAbsolutePath()); + context.startService(intent); + return true; + } + + @Override public Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { return null; } + @Override public String getType(Uri uri) { return null; } + @Override public Uri insert(Uri uri, ContentValues values) { return null; } + @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } + @Override public int update(Uri uri, ContentValues values, String selection, + String[] selectionArgs) { return 0; } +} diff --git a/android/amy-service/src/main/java/org/amy/audio/AmyClient.java b/android/amy-service/src/main/java/org/amy/audio/AmyClient.java new file mode 100644 index 00000000..9b173df8 --- /dev/null +++ b/android/amy-service/src/main/java/org/amy/audio/AmyClient.java @@ -0,0 +1,58 @@ +package org.amy.audio; + +import android.content.Context; + +import java.io.File; + +/** Minimal client for the private AMY SOCK_SEQPACKET socket. */ +public final class AmyClient { + private static final int EINVAL = 22; + private static final int ENOTCONN = 107; + private static int nativeFd = -1; + + static { + System.loadLibrary("amy_android_client"); + } + + private AmyClient() {} + + private static String socketPath(Context context) { + if (context == null) return ""; + return new File(context.getFilesDir(), AmyService.DEFAULT_SOCKET_NAME) + .getAbsolutePath(); + } + + /** Connect once to filesDir/amy.sock. Returns 0 or negative errno. */ + public static synchronized int connect(Context context) { + if (context == null) return -EINVAL; + closeLocked(); + int fd = nativeConnect(socketPath(context)); + if (fd < 0) return fd; + nativeFd = fd; + return 0; + } + + /** Send exactly one AMY wire command as one SOCK_SEQPACKET packet. */ + public static synchronized int sendWire(String wire) { + if (nativeFd < 0) return -ENOTCONN; + if (wire == null || wire.isEmpty()) return -EINVAL; + int result = nativeSend(nativeFd, wire); + if (result < 0) closeLocked(); + return result; + } + + public static synchronized void close() { + closeLocked(); + } + + private static void closeLocked() { + if (nativeFd >= 0) { + nativeClose(nativeFd); + nativeFd = -1; + } + } + + private static native int nativeConnect(String socketPath); + private static native int nativeSend(int fd, String wire); + private static native void nativeClose(int fd); +} diff --git a/godot/android-hello-world/addons/amy_android/export_plugin.gd b/godot/android-hello-world/addons/amy_android/export_plugin.gd new file mode 100644 index 00000000..3e59091e --- /dev/null +++ b/godot/android-hello-world/addons/amy_android/export_plugin.gd @@ -0,0 +1,25 @@ +@tool +extends EditorPlugin + +var _export_plugin: EditorExportPlugin + +func _enter_tree() -> void: + _export_plugin = AmyAndroidExportPlugin.new() + add_export_plugin(_export_plugin) + +func _exit_tree() -> void: + if _export_plugin != null: + remove_export_plugin(_export_plugin) + _export_plugin = null + +class AmyAndroidExportPlugin extends EditorExportPlugin: + func _supports_platform(platform: EditorExportPlatform) -> bool: + return platform is EditorExportPlatformAndroid + + func _get_android_libraries(_platform: EditorExportPlatform, debug: bool) -> PackedStringArray: + if debug: + return PackedStringArray(["amy_android/amy-service-debug.aar"]) + return PackedStringArray(["amy_android/amy-service-release.aar"]) + + func _get_name() -> String: + return "AMY Android service" diff --git a/godot/android-hello-world/addons/amy_android/plugin.cfg b/godot/android-hello-world/addons/amy_android/plugin.cfg new file mode 100644 index 00000000..e00be010 --- /dev/null +++ b/godot/android-hello-world/addons/amy_android/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="AMY Android Service Export" +description="Packages the AMY Android service AAR into the Godot APK." +author="AMY" +version="1.0" +script="export_plugin.gd" diff --git a/godot/android-hello-world/export_presets.cfg b/godot/android-hello-world/export_presets.cfg new file mode 100644 index 00000000..8b8e8844 --- /dev/null +++ b/godot/android-hello-world/export_presets.cfg @@ -0,0 +1,93 @@ +[preset.0] + +name="Android ARM64" +platform="Android" +runnable=true +advanced_options=false +dedicated_server=false +custom_features="" +export_filter="all_resources" +include_filter="" +exclude_filter="" +export_path="build/amy-godot-raw-wire-arm64.apk" +patches=PackedStringArray() +encryption_include_filters="" +encryption_exclude_filters="" +seed=0 +encrypt_pck=false +encrypt_directory=false +script_export_mode=2 + +[preset.0.options] + +custom_template/debug="" +custom_template/release="" +gradle_build/use_gradle_build=true +gradle_build/gradle_build_directory="" +gradle_build/android_source_template="" +gradle_build/compress_native_libraries=false +gradle_build/export_format=0 +gradle_build/min_sdk="26" +gradle_build/target_sdk="36" +architectures/armeabi-v7a=false +architectures/arm64-v8a=true +architectures/x86=false +architectures/x86_64=false +version/code=1 +version/name="1.0" +package/unique_name="org.amy.godothello" +package/name="AMY Godot Raw Wire POC" +package/signed=true +package/app_category=2 +package/retain_data_on_uninstall=false +package/exclude_from_recents=false +package/show_in_android_tv=false +package/show_in_app_library=true +package/show_as_launcher_app=true + +[preset.1] + +name="Android CI x86_64" +platform="Android" +runnable=false +advanced_options=false +dedicated_server=false +custom_features="" +export_filter="all_resources" +include_filter="" +exclude_filter="" +export_path="build/amy-godot-raw-wire-x86_64.apk" +patches=PackedStringArray() +encryption_include_filters="" +encryption_exclude_filters="" +seed=0 +encrypt_pck=false +encrypt_directory=false +script_export_mode=2 + +[preset.1.options] + +custom_template/debug="" +custom_template/release="" +gradle_build/use_gradle_build=true +gradle_build/gradle_build_directory="" +gradle_build/android_source_template="" +gradle_build/compress_native_libraries=false +gradle_build/export_format=0 +gradle_build/min_sdk="26" +gradle_build/target_sdk="36" +architectures/armeabi-v7a=false +architectures/arm64-v8a=false +architectures/x86=false +architectures/x86_64=true +version/code=1 +version/name="1.0" +package/unique_name="org.amy.godothello" +package/name="AMY Godot Raw Wire POC" +package/signed=true +package/app_category=2 +package/retain_data_on_uninstall=false +package/exclude_from_recents=false +package/show_in_android_tv=false +package/show_in_app_library=true +package/show_as_launcher_app=true diff --git a/godot/android-hello-world/main.gd b/godot/android-hello-world/main.gd new file mode 100644 index 00000000..71e08ea2 --- /dev/null +++ b/godot/android-hello-world/main.gd @@ -0,0 +1,128 @@ +extends Control + +var _status: Label +var _play_button: Button +# Deliberately leave these as Variant. Typing AmyClient as Object makes +# GDScript bind connect() to Object.connect(signal, callable) at parse time +# instead of dispatching to the wrapped Java static method. +var _amy_client +var _android_context + +func _ready() -> void: + _build_ui() + + if OS.get_name() != "Android": + _fail("Android export required") + return + + var runtime = Engine.get_singleton("AndroidRuntime") + if runtime == null: + _fail("AndroidRuntime unavailable") + return + + _android_context = runtime.getApplicationContext() + if _android_context == null: + _fail("Android application context unavailable") + return + + _amy_client = JavaClassWrapper.wrap("org.amy.audio.AmyClient") + if _amy_client == null: + _fail("AmyClient class unavailable") + return + if not _amy_client.has_java_method("connect") or not _amy_client.has_java_method("sendWire"): + _fail("AmyClient methods unavailable") + return + + _status.text = "Connecting to amy.sock..." + for _attempt in range(200): + var rc: int = int(_amy_client.connect(_android_context)) + var exception = JavaClassWrapper.get_exception() + if exception != null: + _fail("AmyClient.connect exception: %s" % str(exception)) + return + if rc == 0: + print("Godot connected to amy.sock") + _status.text = "Connected to AMY" + _play_button.disabled = false + await _play_scale() + return + await get_tree().create_timer(0.05).timeout + + _fail("Could not connect to amy.sock") + +func _exit_tree() -> void: + if _amy_client != null: + _amy_client.close() + +func _build_ui() -> void: + var center := CenterContainer.new() + center.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + add_child(center) + + var column := VBoxContainer.new() + column.custom_minimum_size = Vector2(680, 0) + column.alignment = BoxContainer.ALIGNMENT_CENTER + center.add_child(column) + + var title := Label.new() + title.text = "AMY + Godot raw-wire proof of concept" + title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + title.add_theme_font_size_override("font_size", 28) + column.add_child(title) + + _status = Label.new() + _status.text = "Initializing..." + _status.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + _status.add_theme_font_size_override("font_size", 20) + column.add_child(_status) + + _play_button = Button.new() + _play_button.text = "Play C major scale" + _play_button.disabled = true + _play_button.pressed.connect(_on_play_pressed) + column.add_child(_play_button) + +func _on_play_pressed() -> void: + await _play_scale() + +func _send_wire(wire: String) -> bool: + var rc: int = int(_amy_client.sendWire(wire)) + var exception = JavaClassWrapper.get_exception() + if exception != null: + _fail("AmyClient.sendWire exception: %s" % str(exception)) + return false + if rc != 0: + _fail("amy.sock send failed: %d" % rc) + return false + print("Godot wire: %s" % wire) + return true + +func _play_scale() -> void: + _play_button.disabled = true + _status.text = "Playing C major scale..." + + # Stage 1 deliberately uses the exact literal AMY wire commands from the + # already-working Android hello-world. No Amy.gd/API translation is involved. + if not _send_wire("v0w0V10.0Z"): + return + await get_tree().create_timer(0.03).timeout + + for note in [60, 62, 64, 65, 67, 69, 71, 72]: + if not _send_wire("v0n%dl1Z" % note): + return + await get_tree().create_timer(0.35).timeout + if not _send_wire("v0l0Z"): + return + await get_tree().create_timer(0.08).timeout + + _status.text = "C scale complete" + _play_button.disabled = false + print("Godot raw-wire C scale complete") + +func _fail(message: String) -> void: + push_error(message) + print("Godot raw-wire error: %s" % message) + if _status != null: + _status.text = message + if _play_button != null: + _play_button.disabled = true diff --git a/godot/android-hello-world/main.tscn b/godot/android-hello-world/main.tscn new file mode 100644 index 00000000..23751ec5 --- /dev/null +++ b/godot/android-hello-world/main.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource path="res://main.gd" type="Script" id="1_main"] + +[node name="AmyGodotAndroidHello" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_main") diff --git a/godot/android-hello-world/prepare.sh b/godot/android-hello-world/prepare.sh new file mode 100644 index 00000000..3bf83619 --- /dev/null +++ b/godot/android-hello-world/prepare.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT="$(cd "${HERE}/../.." && pwd)" +GRADLE_BIN="${GRADLE_BIN:-gradle}" + +( + cd "${ROOT}/android" + "${GRADLE_BIN}" :amy-service:assembleDebug +) + +ADDON="${HERE}/addons/amy_android" +mkdir -p "${ADDON}" +cp "${ROOT}/android/amy-service/build/outputs/aar/amy-service-debug.aar" \ + "${ADDON}/amy-service-debug.aar" + +printf 'Prepared AMY service AAR for Godot in %s\n' "${ADDON}" diff --git a/godot/android-hello-world/project.godot b/godot/android-hello-world/project.godot new file mode 100644 index 00000000..647bdbad --- /dev/null +++ b/godot/android-hello-world/project.godot @@ -0,0 +1,20 @@ +; Engine configuration file. +config_version=5 + +[application] +config/name="AMY Godot Raw Wire POC" +run/main_scene="res://main.tscn" + +[display] +window/size/viewport_width=720 +window/size/viewport_height=1280 +window/size/window_width_override=360 +window/size/window_height_override=640 + +[editor_plugins] +enabled=PackedStringArray("res://addons/amy_android/plugin.cfg") + +[rendering] +renderer/rendering_method="gl_compatibility" +renderer/rendering_method.mobile="gl_compatibility" +textures/vram_compression/import_etc2_astc=true