5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Godot】JavaサポートしたGDExtension Android Plugin v2を作ってみました

Last updated at Posted at 2025-12-19

概要

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からライブラリー作成用プロジェクトを作成
image_1.png

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

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

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

Library追加後不要な「app」を削除
image_5.png

Godot Engine Libraryを追加する

アーカイブページからお使ったGodotのバージョンの「Download」を押す
image(6).png

次のページで「Show all downloads」
image(7).png

下にある「AAR library」でダウンロード可能
image(8).png

Android StudioのExplorerをProject Filesモードに切り替え
image(9).png

追加したLibraryの中に「libs」フォルダを作成
image(10).png

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

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

Step 1.に追加したAAR libraryのpathを入力
Step 2.にcompileOnlyを入力
image(13).png

追加後build.gradleのdependenciesに
compileOnly files('libs/godot-lib.4.5.1.stable.template_release.aar')

追加されるのを確認できる
image(14).png

LibraryにGodotPluginの派生クラスを追加

ExplororをAndroidモードに切り替えてjava>package nameのフォルダに右クリックしてJava Classを追加
image(15).png

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にクラスを定義

  • android:nameは”org.godotengine.plugin.v2.”は必須
  • android:valueはpackage name + クラス名
    image(16).png
<?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 でビルドを実行
image(17).png

出力成功したら、Library>build>outputsにarrが出力される
image(18).png

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プロジェクトのフォルダに配置
image(19).png

Project > Project Settings > PluginsでMyLibraryPluginを有効化

image(20).png

Godotテスト環境を設置

2D Nodeを作成
image(21).png

SceneにLabelを作成
image(22).png
image(23).png
作成後LabelをLabelTestにリネーム(位置、文字設定はお好みで)

作成したSceneにScriptを作成
image(24).png
image(25).png

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に表示できます。
image(26).png

終わり

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

以上です。

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?