概要
Android StudioでGodot用のAndroid Libraryを作成してGDExtention化Godot Android plugins v2のおまかの作成手順をまとめました。
公式手順のURL
https://docs.godotengine.org/ja/4.x/tutorials/platform/android/android_plugin.html
手順
1. Android StudioでAndroid Libraryを準備
ライブラリー作成用プロジェクトを作成
Android Studioで File > New > New Projectからライブラリー作成用プロジェクトを作成

Name、Package nameなど情報入力(好きな名前で良い)

File > Project Structure > Modules を開いてNew Moduleを選択

Android Libraryを選択してModule name、Package nameなど情報を入力した上でFinishを押す

Godot Engine Libraryを追加する
アーカイブページからお使ったGodotのバージョンの「Download」を押す

Android StudioのExplorerをProject Filesモードに切り替え

ダウンロードしたGodot AAR libraryファイルをlibsにドラッグ&ドロップして「Refactor」を押す

File > Project Structure > Dependencies を開いてlibraryを選択してDeclared Dependenciesの下の「+」を押して「2 JAR/AAR Dependency」を選択

Step 1.に追加したAAR libraryのpathを入力
Step 2.にcompileOnlyを入力

追加後build.gradleのdependenciesに
compileOnly files('libs/godot-lib.4.5.1.stable.template_release.aar')
LibraryにGodotPluginの派生クラスを追加
ExplororをAndroidモードに切り替えてjava>package nameのフォルダに右クリックしてJava Classを追加

GodotPluginの派生クラスを作成
- classにextends GodotPluginを追加
- Constuctorを追加
- getPluginName()をoverrideしてクラス名をreturn
- getPluginGDExtensionLibrariesPathsをoverrideして追加予定のgdextension pathに設定
- (好みで良い)テスト用の関数を追加
package com.example.mylibrary;
import androidx.annotation.NonNull;
import org.godotengine.godot.Godot;
import org.godotengine.godot.plugin.GodotPlugin;
import java.util.Set;
import java.util.HashSet;
public class MyLibraryPlugin extends GodotPlugin {
private static MyLibraryPlugin instance;
public MyLibraryPlugin(Godot godot) {
super(godot);
instance = this;
}
public static MyLibraryPlugin getInstance() {
return instance;
}
@NonNull
@Override
public String getPluginName() {
return "MyLibraryPlugin";
}
@NonNull
@Override
public Set<String> getPluginGDExtensionLibrariesPaths() {
Set<String> paths = new HashSet<>();
paths.add("res://addons/mylibrary/mylibraryplugin.gdextension");
return paths;
}
public String testPlugin() {
return "Hello, Android Plugin World";
}
}
AndroidManifest.xmlにクラスを定義
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<meta-data
android:name="org.godotengine.plugin.v2.MyLibraryPlugin"
android:value="com.example.mylibrary.MyLibraryPlugin" />
</application>
</manifest>
Android Library AARファイルを出力テスト
Build > Assemble Module でビルドを実行

出力成功したら、Library>build>outputsにarrが出力される

2. Godot用環境の構築
GDExtension構成の準備
公式手順を参照して作成ください
https://docs.godotengine.org/en/stable/tutorials/scripting/gdextension/gdextension_c_example.html
GDExtension構成をAndroid Libraryに入れます。
ファイル構成
GodotAndroidPlugin/
├── .gitignore
├── extension_api.json ← "godot --dump-extension-api" で出力
├── build.gradle ← Android Studioから生成された
├── gradle.properties ← Android Studioから生成された
├── gradlew ← Android Studioから生成された
├── gradle/ ← Android Studioから生成された
│ ├── libs.versions.toml
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── local.properties ← Android Studioから生成された
├── settings.gradle ← Android Studioから生成された
├── godot-cpp/ ← https://github.com/godotengine/godot-cppからClone
│
└── mylibrary/
├── SConstruct ← 公式サンプルを参照して作成
├── libs/ ← 前の手順で配置した
│ └── godot-lib.4.5.1.stable.template_release.aar
├── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── assets/
│ │ └── addons/
│ │ └── MyLibraryPlugin/
│ │ └── mylibraryplugin.gdextension ← GDExtension 設定ファイル
│ ├── cpp/ ← GDExtensionの構成部分
│ │ ├── PluginHandler.cpp ← Godotで使うc++クラス(Plugin名と被らないように)
│ │ ├── PluginHandler.h
│ │ ├── register_types.cpp ← Godotで使う登録用c++クラス
│ │ ├── register_types.h
│ │ └── platform/
│ │ ├── android/
│ │ │ ├── MyLibraryPluginAndroid.cpp ← Android bridgeクラス
│ │ └── ios/
│ │ └── MyLibraryPluginIOS.mm ← iOS bridgeクラス
│ ├── java/
│ │ └── com/
│ │ └── example/
│ │ └── mylibrary/
│ │ └── MyLibraryPlugin.java
│ └── jniLibs/
│ └── arm64-v8a/
│ ├── libmylibrary.android.template_debug.arm64.so ← この後生成
│ └── libmylibrary.android.template_release.arm64.so ← この後生成
└── addons/
└── mylibrary/
├── export_mylibrary.gd ← Godot用の出力処理用
├── plugin.cfg ← Godot用のPlugin設定
├── mylibraryplugin.gdextension ← Godot用のGDExtension設定
└── bin/
├──libmylibrary.android.template_debug.arm64.so ← GDExtensionの手順で出力した
├──libmylibrary.ios.template_debug.xcframework ← GDExtensionの手順で出力した
├──mylibrary-debug.aar ← Android Studioで出力した
└──mylibrary-release.aar ← Android Studioで出力した
GDExtension plugin設定 サンプル
基本設定はGodotでProject > Project Settings > Plugins > Create New Pluginから作成できます。
mylibraryplugin.gdextensionを作成
[configuration]
entry_symbol = "mylibrary_library_init"
compatibility_minimum = "4.2"
android_aar_plugin = true
reloadable = true
[libraries]
ios.debug = "res://addons/mylibrary/bin/libmylibrary.ios.template_debug.xcframework"
ios.release = "res://addons/mylibrary/bin/libmylibrary.ios.template_release.xcframework"
android.debug.arm64 = "res://addons/mylibrary/bin/libmylibrary.android.template_debug.arm64.so"
android.release.arm64 = "res://addons/mylibrary/bin/libmylibrary.android.template_release.arm64.so"
[dependencies]
ios.debug = {
"res://bin/libgodot-cpp.ios.template_debug.xcframework": ""
}
ios.release = {
"res://bin/libgodot-cpp.ios.template_release.xcframework": ""
}
export_mylibrary.gd
@tool
extends EditorPlugin
# A class member to hold the editor export plugin during its lifecycle.
var export_handler : MyLibraryPluginExportHandler
func _enter_tree():
# Initialization of the plugin goes here.
export_handler = MyLibraryPluginExportHandler.new()
add_export_plugin(export_handler)
func _exit_tree():
# Clean-up of the plugin goes here.
remove_export_plugin(export_handler)
export_handler = null
class MyLibraryPluginExportHandler extends EditorExportPlugin:
# Plugin's name.
var _plugin_name = "MyLibraryPlugin"
# Path to the plugin directory relative to the addons folder
var _plugin_base_path = "res://addons/mylibrary"
# Android plugin (AAR) paths
var _aar_files = []
func _init():
# Initialize list of AAR files bundled with the plugin
# Adjust these paths based on where your AAR files are located after building
_aar_files = [
_plugin_base_path + "/bin/debug/mylibrary-debug.aar",
_plugin_base_path + "/bin/release/mylibrary-release.aar"
]
# Specifies which platform is supported by the plugin.
func _supports_platform(platform):
if platform is EditorExportPlatformAndroid:
return true
if platform is EditorExportPlatformIOS:
return true
return false
# Return the plugin's name.
func _get_name():
return _plugin_name
# Return the paths of the plugin's AAR binaries relative to the 'addons' directory.
func _get_android_libraries(platform, debug):
if debug:
# Return debug AAR
if _aar_files.size() > 0:
return PackedStringArray([_aar_files[0]])
else:
# Return release AAR
if _aar_files.size() > 1:
return PackedStringArray([_aar_files[1]])
return PackedStringArray()
plugin.cfg
[plugin]
name="MyLibraryPlugin"
description="A native Android GDExtension plugin for Godot 4.2+"
author="Your Organization"
version="0.1.0"
script="export_mylibrary.gd"
3. Bridgeクラス サンプル(src/main/cpp内)
PluginHandler.cpp
#include "PluginHandler.h"
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
#include <godot_cpp/classes/display_server.hpp>
#ifdef __APPLE__
extern "C" {
#if TARGET_OS_IOS || TARGET_OS_SIMULATOR
const char* test_plugin_native();
#endif
}
#endif
#ifdef __ANDROID__
extern "C" {
const char* test_plugin_native();
}
#endif
using namespace godot;
void PluginHandler::_bind_methods() {
ClassDB::bind_method(D_METHOD("test_plugin"), &PluginHandler::test_plugin);
}
PluginHandler::PluginHandler() {
}
PluginHandler::~PluginHandler() {
}
String PluginHandler::test_plugin() {
return String(test_plugin_native());
}
platform/android/MyLibraryPluginAndroid.cpp
#ifdef __ANDROID__
#include <jni.h>
#include <android/log.h>
#include <string>
#include <pthread.h>
#include <godot_cpp/variant/string.hpp>
using namespace godot;
// Global JNI references
static JavaVM* g_jvm = nullptr;
static jclass g_myplugin_class = nullptr;
static jobject g_myplugin_instance = nullptr;
static pthread_mutex_t g_jni_mutex = PTHREAD_MUTEX_INITIALIZER;
// Method IDs (cached for performance)
static jmethodID g_method_getInstance = nullptr;
static jmethodID g_method_testPlugin = nullptr;
// Helper function to get JNIEnv for the current thread
static JNIEnv* get_jni_env() {
if (!g_jvm) {
return nullptr;
}
JNIEnv* env = nullptr;
int status = g_jvm->GetEnv((void**)&env, JNI_VERSION_1_6);
return env;
}
// Initialize JNI - call this once at startup
extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
g_jvm = vm;
JNIEnv* env = nullptr;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
// Find MyLibraryPlugin class
jclass localClass = env->FindClass("com/example/mylibrary/MyLibraryPlugin");
if (!localClass) {
return JNI_ERR;
}
// Create global reference
g_myplugin_class = (jclass)env->NewGlobalRef(localClass);
env->DeleteLocalRef(localClass);
if (!g_myplugin_class) {
return JNI_ERR;
}
// Cache method IDs
g_method_getInstance = env->GetStaticMethodID(g_myplugin_class, "getInstance", "()Lcom/example/mylibrary/MyLibraryPlugin;");
g_method_testPlugin = env->GetMethodID(g_myplugin_class, "testPlugin", "()Ljava/lang/String;");
// Verify all method IDs were cached
if (!g_method_getInstance || !g_method_testPlugin) {
env->ExceptionDescribe();
env->ExceptionClear();
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
JNIEnv* env = nullptr;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) == JNI_OK) {
if (g_myplugin_instance) {
env->DeleteGlobalRef(g_myplugin_instance);
g_myplugin_instance = nullptr;
}
if (g_myplugin_class) {
env->DeleteGlobalRef(g_myplugin_class);
g_myplugin_class = nullptr;
}
}
pthread_mutex_destroy(&g_jni_mutex);
g_jvm = nullptr;
}
// Helper to get MyLibraryPlugin instance with retry
static jobject get_myplugin_instance(JNIEnv* env) {
if (!env) {
return nullptr;
}
if (!g_myplugin_instance && g_method_getInstance && g_myplugin_class) {
pthread_mutex_lock(&g_jni_mutex);
if (!g_myplugin_instance) {
jobject localInstance = env->CallStaticObjectMethod(g_myplugin_class, g_method_getInstance);
// Check for exceptions
if (env->ExceptionCheck()) {
env->ExceptionDescribe();
env->ExceptionClear();
pthread_mutex_unlock(&g_jni_mutex);
return nullptr;
}
if (!localInstance) {
pthread_mutex_unlock(&g_jni_mutex);
return nullptr;
}
g_myplugin_instance = env->NewGlobalRef(localInstance);
env->DeleteLocalRef(localInstance);
if (!g_myplugin_instance) {
pthread_mutex_unlock(&g_jni_mutex);
return nullptr;
}
}
pthread_mutex_unlock(&g_jni_mutex);
}
return g_myplugin_instance;
}
// Static buffer to hold the result string
static char g_result_buffer[256] = {0};
extern "C" {
const char* test_plugin_native();
}
// Implementation
const char* test_plugin_native() {
JNIEnv* env = get_jni_env();
if (!env) {
strcpy(g_result_buffer, "Test failed: No JNIEnv");
return g_result_buffer;
}
jobject instance = get_myplugin_instance(env);
if (!instance || !g_method_testPlugin) {
strcpy(g_result_buffer, "Test failed: No instance or method ID");
return g_result_buffer;
}
jstring result = (jstring)env->CallObjectMethod(instance, g_method_testPlugin);
// Convert jstring to C string
const char* utf8_chars = env->GetStringUTFChars(result, nullptr);
strncpy(g_result_buffer, utf8_chars, sizeof(g_result_buffer) - 1);
g_result_buffer[sizeof(g_result_buffer) - 1] = '\0';
env->ReleaseStringUTFChars(result, utf8_chars);
env->DeleteLocalRef(result);
return g_result_buffer;
}
#endif // __ANDROID__
4. Godotに導入
addonsフォルダををコピーしてGodotプロジェクトのフォルダに配置

Project > Project Settings > PluginsでMyLibraryPluginを有効化
Godotテスト環境を設置
SceneにLabelを作成


作成後LabelをLabelTestにリネーム(位置、文字設定はお好みで)
extends Node2D
@onready
var label : Label = $/root/Node2D/LabelTest
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
test_plugin()
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
pass
func test_plugin() -> void:
# Check if PluginHandler class is available
if not ClassDB.class_exists("PluginHandler"):
printerr("PluginHandler class not found!")
return
# Create PluginHandler instance
var my_library_plugin : PluginHandler = PluginHandler.new()
if my_library_plugin:
label.text = my_library_plugin.test_plugin()
else:
label.text = "Failed to create PluginHandler instance."
APKを出力してデバイスで確認
成功したら、javaから取得した文字がLabelに表示できます。

終わり
Godot Android Plugin v2 GDExtensionの構成は複雑ですが、c++メソッドを通してプラットフォーム限定のメソッドが使えるようになります。
以上です。









