使用指南

May 15, 2026 · View on GitHub

本文档介绍 godot-java 的核心概念和进阶用法。

为什么选择 Java

Java vs GDScript

维度GDScriptJava
类型系统动态类型,运行时报错强类型,编译期检查
工具链Godot 编辑器IntelliJ IDEA、VS Code
生态Godot 专用Maven Central 海量库
性能解释执行Panama FFI 低开销原生调用
语言特性简单脚本Lambda、Stream、泛型、模块化

适用场景

适合使用 Java:

  • 有 Java / Android / 后端开发背景
  • 需要复用已有 Java 库
  • 项目规模较大,需要强类型和模块化架构
  • 需要 IDE 级别的重构和调试支持

适合使用 GDScript:

  • 编程初学者
  • 快速原型开发
  • 仅需简单游戏逻辑

核心概念

节点与继承

使用 @GodotClass 将 Java 类注册为 Godot 节点。Java 类必须继承对应的 Godot 父类:

import org.godot.annotation.GodotClass;
import org.godot.node.Node2D;

@GodotClass(name = "Player", parent = "CharacterBody2D")
public class Player extends Node2D {
}

常用父类:

父类Java 基类用途
Nodeorg.godot.node.Node基础节点
Node2Dorg.godot.node.Node2D2D 节点
Node3Dorg.godot.node.Node3D3D 节点
Controlorg.godot.node.ControlUI 节点
RefCountedorg.godot.core.RefCounted引用计数对象(默认)

虚方法

Godot 的虚方法在 Java 中通过 @Override 实现:

@Override
public void _ready() {
    // 节点加入场景树时调用(仅一次)
}

@Override
public void _process(double delta) {
    // 每帧调用,delta 为距上一帧的时间(秒)
}

@Override
public void _physicsProcess(double delta) {
    // 每物理帧调用(固定时间步长)
}

@Override
public void _enterTree() {
    // 节点进入场景树时调用
}

@Override
public void _exitTree() {
    // 节点离开场景树时调用
}

这些虚方法由 VirtualDispatch 类通过 Panama upcall stub 路由到 Java 对象。当 Godot 调用 get_virtual_func 查询虚方法时,返回对应的方法分发 stub;Godot 调用该 stub 时,通过 JavaObjectMap 查找对应的 Java 实例并调用其方法。

完整示例:角色组件

package com.example;

import org.godot.annotation.Export;
import org.godot.annotation.GodotClass;
import org.godot.annotation.GodotMethod;
import org.godot.annotation.Signal;
import org.godot.node.Node;
import org.godot.node.Node2D;
import org.godot.math.Vector2;

@GodotClass(name = "Player", parent = "Node2D")
public class Player extends Node2D {

    @Export
    private float speed = 300.0f;

    @Export
    private int maxHealth = 100;

    private int currentHealth;

    @Signal(name = "health_changed")
    private void onHealthChanged(int newHealth) {}

    @Signal(name = "died")
    private void onDied() {}

    @Override
    public void _ready() {
        currentHealth = maxHealth;
    }

    @Override
    public void _process(double delta) {
        // 每帧更新逻辑
        handleMovement(delta);
    }

    private void handleMovement(double delta) {
        // 调用 Godot 方法检查输入
        Object rightPressed = call("is_action_pressed", "move_right");
        Object leftPressed = call("is_action_pressed", "move_left");

        double dx = 0;
        if (rightPressed instanceof Boolean b && b) dx += speed;
        if (leftPressed instanceof Boolean b && b) dx -= speed;

        translate(dx * delta, 0);
    }

    @GodotMethod
    public void takeDamage(int amount) {
        currentHealth -= amount;
        emit("health_changed", currentHealth);
        if (currentHealth <= 0) {
            emit("died");
        }
    }

    @GodotMethod
    public int getHealth() {
        return currentHealth;
    }
}

方法调用

Java 调用 Godot 方法

使用 Godot.call() 调用任意 Godot 方法:

// 无参数
Object name = call("get_name");

// 带参数
call("set_position", new Vector2(100, 200));

// 获取返回值
Object result = call("is_visible");
if (result instanceof Boolean visible && visible) {
    // ...
}

方法调用流程:

  1. 通过 METHOD_HASHES 查找方法哈希值
  2. 调用 classdb_get_method_bind 获取方法绑定
  3. 将参数转换为 Variant
  4. 调用 object_method_bind_call 执行
  5. 将返回的 Variant 转换为 Java 对象

Godot 调用 Java 方法

使用 @GodotMethod 暴露方法给 GDScript:

@GodotMethod
public void takeDamage(int amount) {
    health -= amount;
}

@GodotMethod("custom_name_in_gdscript")
public void myJavaMethod() {
    // GDScript 中以 "custom_name_in_gdscript" 调用
}

类型转换

Godot VariantJava 类型示例
NILnullnull
BOOLboolean / Booleantrue
INTlong / int42L
FLOATdouble / float3.14
STRINGString"hello"
VECTOR2Vector2new Vector2(1, 2)
VECTOR3Vector3new Vector3(1, 2, 3)
ARRAYGodotArray从 native 构造
DICTIONARYGodotDictionary从 native 构造
OBJECTGodot 子类包装 native 指针

类型转换由 VariantUtils.fromObject()VariantUtils.toObject() 自动完成。


属性与导出

@Export 注解

标记了 @Export 的字段会在 Godot 编辑器的属性检查器中显示,可在编辑器中直接修改:

@Export
private int speed = 300;

@Export
private float jumpVelocity = -400.0f;

@Export
private String playerName = "Hero";

支持的属性类型

  • 基本类型:intlongfloatdoubleboolean
  • 字符串:String

自定义属性名和提示

// 自定义属性名
@Export(propertyName = "move_speed")
private float speed = 300.0f;

// 带提示(range)
@Export(hint = "range:0,100,1")
private int health = 100;

信号

声明信号

@Signal(name = "health_changed")
private void onHealthChanged(int newHealth) {}

@Signal(name = "died")
private void onDied() {}

@Signal(name = "item_collected")
private void onItemCollected(String itemType, int value) {}

发射信号

emit("health_changed", currentHealth);
emit("died");
emit("item_collected", "coin", 100);

连接信号

在 GDScript 中:

player.health_changed.connect(_on_health_changed)

在 Java 中:

// 在 _ready() 中连接
connect("pressed", callable, 0);

连接标志

标志说明
CONNECT_DEFERRED1延迟调用
CONNECT_PERSIST2节点移动时保持连接
CONNECT_ONE_SHOT4触发一次后自动断开

从 GDScript 迁移

基本语法对照

GDScriptJava说明
var x = 10@Export private int x = 10;使用 @Export 可在编辑器可见
const MAX = 100private static final int MAX = 100;Java 常量
onready var node = $Child_ready() 中初始化onready 关键字
extends Node2D@GodotClass(...) extends Node2D注解 + 继承

方法命名对照

GDScriptJava
_ready()_ready()
_process(delta)_process(double delta)
_physics_process(delta)_physicsProcess(double delta)
move_and_slide()call("move_and_slide")
get_node("path")getNode("path")
get_children()getChildren()

信号对照

# GDScript
signal health_changed(new_health)
emit_signal("health_changed", health)
// Java
@Signal(name = "health_changed")
public void healthChanged(int newHealth) {}

emit("health_changed", health);

关键差异

  • Java 使用静态类型,所有变量需要声明类型
  • onready 关键字,需在 _ready() 中显式初始化节点引用
  • 方法名在 Godot 侧保持原样(snake_case),Java 侧使用 camelCase
  • 节点引用通过 getNode("Path") 获取,建议在 _ready() 中缓存

资源管理

原生指针

Godot 类持有原生 Godot 对象的指针(nativeObject 字段)。通过 getPtr() 获取,isValid() 检查有效性。

实例生命周期

  1. Godot 调用 create_instance_func(由 InstanceCallbacks 处理)
  2. Java 创建实例,通过反射设置 nativeObject 字段
  3. 实例注册到 JavaObjectMap(nativePtr -> Java 对象映射)
  4. 虚方法通过 VirtualDispatch -> JavaObjectMap 查找实例并分发
  5. Godot 调用 free_instance_func 时,从 JavaObjectMap 移除并调用 onFreed()

内存注意事项

  • RefCounted 对象由 Godot 引用计数管理
  • 节点由场景树管理生命周期
  • 持久 Arena(Bridge.ARENA)用于注册阶段的 upcall stub 和静态数据,生命周期与 JVM 相同
  • 运行时调用路径(Godot.call()GodotArray 操作等)使用 scoped arena,通过 Java 25 ScopedValue 自动管理,每次调用结束后释放临时 Variant 和缓冲区
  • 不要在节点被释放后继续使用(isValid() 返回 false)