Jundot Engine 文档

脚本 API 参考

Jundot Engine 以 Godot 的脚本 API 为基础。大部分 Godot 4.x 的节点、场景、GDScript 和 C# 用法都适用;Jundot 特有差异会在文档中单独标注。

ℹ️ 关于本页面

Jundot 基于 Godot 构建,Godot 官方文档仍然是学习节点、场景、脚本和 API 的主要参考。本文档只做常用速查和 Jundot 差异说明。

完整 Godot API 请参考 Godot 官方文档

概述

Jundot Engine 支持两种脚本语言:

语言 特点 适用场景
GDScript 简单易学、动态类型、与引擎深度集成 快速原型、游戏逻辑、UI
C# 静态类型、高性能、生态丰富 复杂系统、性能敏感、已有 C# 代码
基于 Godot API

常规脚本开发请优先参考 Godot 官方文档。如果 Jundot 添加了 AI、构建、发布或编辑器相关扩展,会在 Jundot 文档中补充。

节点系统

节点(Node)是 Godot/Jundot 的核心概念。所有游戏对象都是节点,节点可以嵌套组成场景树。

节点层级

Root (Node)
├── Player (CharacterBody2D)
│   ├── Sprite2D
│   ├── CollisionShape2D
│   └── AnimationPlayer
├── Enemy (CharacterBody2D)
├── Camera2D
└── UI (CanvasLayer)
    └── Control
        ├── Label
        └── Button

常用节点分类

基础节点

节点 说明 关键属性/方法
Node 所有节点的基类 _Ready(), _Process(delta), _PhysicsProcess(delta)
Node2D 2D 空间节点 position, rotation, scale, Translate(v)
Node3D 3D 空间节点 position, rotation, scale, basis
Control UI 控件基类 size, anchor, mouse_entered 信号

物理节点

节点 说明 适用场景
CharacterBody2D / 3D 角色控制器 玩家、敌人、NPC
RigidBody2D / 3D 刚体 受物理影响的物体
StaticBody2D / 3D 静态碰撞体 地形、墙壁
Area2D / 3D 触发区域 检测进入/离开
AnimatableBody2D / 3D 可动画的刚体 移动平台、门

视觉节点

节点 说明
Sprite2D 2D 精灵,显示单张纹理
AnimatedSprite2D 2D 帧动画精灵
Sprite3D 3D 空间中的 2D 精灵
MeshInstance3D 3D 网格实例
Label 文本标签
Line2D 2D 线条
Polygon2D 2D 多边形
CPUParticles2D / 3D CPU 粒子系统
GPUParticles2D / 3D GPU 粒子系统(性能更好)

UI 节点

节点 说明
CanvasLayer UI 层级(独立于世界坐标系)
Control 所有 UI 控件基类
Panel 面板容器
VBoxContainer / HBoxContainer 垂直/水平布局容器
GridContainer 网格布局
Button 按钮
LineEdit 单行输入框
TextEdit 多行文本框
ProgressBar 进度条
Slider 滑块
OptionButton 下拉选择
CheckBox 复选框
TabContainer 标签页
ScrollContainer 滚动容器

动画节点

节点 说明
AnimationPlayer 动画播放器(核心)
AnimationTree 动画状态机/混合树
AnimatedSprite2D 2D 帧动画
SpriteFrames 帧动画资源
Tween 补间动画(代码控制)

节点生命周期

每个节点都有以下生命周期方法,可以在脚本中重写:

方法 调用时机 用途
_EnterTree() 节点进入场景树时 初始化(早于 Ready)
_Ready() 节点及子节点都准备好时 初始化逻辑、获取节点引用
_Process(delta) 每帧调用 游戏逻辑、更新显示
_PhysicsProcess(delta) 固定帧率调用(物理帧) 物理相关逻辑、移动
_Input(event) 收到输入事件时 处理输入
_UnhandledInput(event) UI 未处理的输入 游戏操作输入
_ExitTree() 节点离开场景树时 清理资源、断开信号

生命周期示例(GDScript)

extends Node2D

var score = 0

func _ready():
    # 节点准备好时调用一次
    print("节点已就绪")
    # 获取子节点引用
    $Label.text = "分数: 0"

func _process(delta):
    # 每帧调用
    # delta 是距离上一帧的时间(秒)
    pass

func _physics_process(delta):
    # 固定帧率(默认 60 FPS)
    # 物理相关的逻辑放这里
    pass

func _exit_tree():
    # 节点被移除时调用
    print("节点已移除")

场景管理

场景基础操作

# 切换场景
get_tree().change_scene_to_file("res://scenes/Level2.tscn")

# 重新加载当前场景
get_tree().reload_current_scene()

# 获取当前场景根节点
var current_scene = get_tree().current_scene

# 退出游戏
get_tree().quit()

# 暂停游戏
get_tree().paused = true

# 恢复游戏
get_tree().paused = false

# 获取根节点
var root = get_tree().root

# 获取自动加载的单例
var game_manager = GameManager

实例化场景

# 加载场景资源
var enemy_scene = preload("res://enemy.tscn")

func spawn_enemy(position: Vector2):
    # 实例化
    var enemy = enemy_scene.instantiate()
    # 设置位置
    enemy.position = position
    # 添加到场景树
    add_child(enemy)
    # 或者添加到指定父节点
    # get_node("Enemies").add_child(enemy)

func spawn_multiple():
    for i in range(10):
        var enemy = enemy_scene.instantiate()
        enemy.position = Vector2(i * 64, 100)
        add_child(enemy)

节点操作

获取节点

# 获取直接子节点(推荐)
var player = $Player
var sprite = $Player/Sprite2D

# 用字符串路径
var player = get_node("Player")
var sprite = get_node("Player/Sprite2D")

# 泛型方式(C# 风格,GDScript 也支持)
var player = get_node<CharacterBody2D>("Player")

# 获取不到时返回 null(不报错)
var hp = get_node_or_null("HealthBar")
if hp:
    hp.value = 100

# 获取父节点
var parent = get_parent()

# 获取根节点
var root = get_tree().root

添加/删除节点

# 添加子节点
var node = Node2D.new()
add_child(node)

# 添加到指定索引
add_child(node, true)  # 保持现有层级

# 移除节点(会在下一帧删除)
node.queue_free()

# 立即删除(不推荐)
node.free()

# 从父节点移除但不删除
remove_child(node)

# 重新设置父节点
node.reparent(new_parent)

遍历节点

# 遍历直接子节点
for child in get_children():
    print(child.name)

# 递归遍历所有子节点
for child in get_children():
    _process_node(child)

# 按类型查找
var enemies = get_tree().get_nodes_in_group("enemy")

# 查找第一个匹配的节点
var player = get_tree().get_first_node_in_group("player")

信号系统

信号(Signal)是 Godot 的观察者模式实现,用于节点间的松耦合通信。

基础用法

# 连接信号(代码方式)
func _ready():
    # 按钮点击
    $Button.pressed.connect(_on_button_pressed)
    
    # 带参数的信号
    $Player.health_changed.connect(_on_health_changed)
    
    # Area 进入检测
    $Area2D.body_entered.connect(_on_body_entered)

# 信号处理函数
func _on_button_pressed():
    print("按钮被点击了")

func _on_health_changed(new_health: int):
    print("生命值变为: ", new_health)

func _on_body_entered(body: Node):
    print("有物体进入: ", body.name)

自定义信号

# 定义信号
signal game_over(score)
signal player_died
signal health_changed(new_value, old_value)

# 发出信号
func die():
    game_over.emit(score)
    player_died.emit()

func set_health(value):
    var old = health
    health = value
    health_changed.emit(value, old)

断开和一次性

# 断开信号
$Button.pressed.disconnect(_on_button_pressed)

# 检查是否已连接
if $Button.pressed.is_connected(_on_button_pressed):
    print("已连接")

# 等待信号(异步)
await $Timer.timeout
print("计时器到了")

# 等待自定义信号
await player.died
print("玩家死亡")

输入系统

输入映射

推荐使用输入映射(项目设置 → 输入映射),而不是直接检查按键:

# 检查持续按下
if Input.is_action_pressed("move_left"):
    velocity.x = -SPEED

if Input.is_action_pressed("move_right"):
    velocity.x = SPEED

# 检查刚按下
if Input.is_action_just_pressed("jump") and is_on_floor():
    velocity.y = -JUMP_FORCE

# 检查刚松开
if Input.is_action_just_released("jump") and velocity.y < -200:
    velocity.y = -200

# 获取轴输入(-1 到 1)
var dir = Input.get_axis("move_left", "move_right")
velocity.x = dir * SPEED

# 获取向量
var move_dir = Input.get_vector("left", "right", "up", "down")
velocity = move_dir * SPEED

鼠标输入

# 鼠标位置(相对于当前节点)
var mouse_pos = get_local_mouse_position()

# 全局鼠标位置
var mouse_global = get_global_mouse_position()

# 鼠标按钮
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
    print("左键按下")

if Input.is_action_just_pressed("click"):
    shoot()

# 鼠标滚轮
func _input(event):
    if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_WHEEL_UP:
        zoom_in()

键盘输入

# 直接检查按键(不推荐,优先用输入映射)
if Input.is_key_pressed(KEY_A):
    pass

# 文本输入
func _input(event):
    if event is InputEventKey and event.pressed:
        print("按下了: ", event.keycode)

向量与数学

Vector2(2D 向量)

# 创建向量
var pos = Vector2(100, 200)
var pos2 = Vector2.ZERO  # (0, 0)
var up = Vector2.UP      # (0, -1)
var right = Vector2.RIGHT  # (1, 0)

# 基本运算
var result = a + b
result = a - b
result = a * 2.0
result = a / 2.0

# 常用属性
pos.x = 100
pos.y = 200
pos.length()         # 长度
pos.normalized()     # 单位向量
pos.distance_to(b)   # 到另一点的距离
pos.angle()          # 角度(弧度)
pos.angle_to(b)      # 与另一向量的夹角
pos.dot(b)           # 点积
pos.cross(b)         # 叉积
pos.rotated(angle)   # 旋转
pos.move_toward(target, delta)  # 向目标移动

# 插值
var between = a.lerp(b, 0.5)  # 中间点

Vector3(3D 向量)

var pos = Vector3(1, 2, 3)
var zero = Vector3.ZERO
var up = Vector3.UP
var forward = Vector3.FORWARD

# 方法与 Vector2 类似
pos.length()
pos.normalized()
pos.distance_to(other)
pos.dot(other)
pos.cross(other)
pos.lerp(other, t)

Tween 动画

用代码创建补间动画:

# 基本用法
var tween = create_tween()
tween.tween_property(self, "position:x", 200, 0.5)
tween.tween_property(self, "modulate:a", 0, 0.3)

# 设置缓动效果
tween.set_ease(Tween.EASE_OUT)
tween.set_trans(Tween.TRANS_CUBIC)

# 循环
tween.set_loops()

# 回调
tween.tween_callback(_on_complete)
tween.finished.connect(_on_all_done)

# 延迟
tween.tween_interval(1.0)  # 等待 1 秒

# 并行(同时执行)
tween.parallel().tween_property(...)
tween.parallel().tween_property(...)

# 完整示例
func fade_out_and_remove():
    var tween = create_tween()
    tween.tween_property(self, "modulate:a", 0, 0.3)
    tween.tween_callback(queue_free)

func punch_scale():
    var tween = create_tween()
    tween.tween_property(self, "scale", Vector2(1.2, 1.2), 0.1)
    tween.tween_property(self, "scale", Vector2(1, 1), 0.2)
    tween.set_trans(Tween.TRANS_BACK)

缓动类型

Ease(缓动方向) Trans(过渡曲线)
EASE_IN — 慢入 TRANS_LINEAR — 线性
EASE_OUT — 慢出 TRANS_SINE — 正弦
EASE_IN_OUT — 慢入慢出 TRANS_QUINT — 五次
EASE_OUT_IN — 快入快出 TRANS_QUART — 四次
TRANS_EXPO — 指数
TRANS_ELASTIC — 弹性
TRANS_BACK — 回退
TRANS_BOUNCE — 弹跳

计时器

# 用 Timer 节点
var timer = Timer.new()
timer.wait_time = 2.0
timer.timeout.connect(_on_timeout)
add_child(timer)
timer.start()

func _on_timeout():
    print("2 秒到了")

# 一次性(简单场景)
func start_countdown():
    var timer = get_tree().create_timer(3.0)
    timer.timeout.connect(_on_done)

# 异步等待
async_func do_something_after_delay():
    await get_tree().create_timer(1.5).timeout
    print("1.5 秒后")

# 重复触发
func _ready():
    var timer = Timer.new()
    timer.wait_time = 0.5
    timer.timeout.connect(_on_tick)
    add_child(timer)
    timer.start()

# 停止计时器
timer.stop()
timer.time_left = 5.0  # 重置时间

组系统

组(Group)是给节点打标签的机制,方便批量操作。

# 添加到组
add_to_group("enemy")
add_to_group("boss")

# 从组移除
remove_from_group("temp")

# 检查是否在组里
if is_in_group("player"):
    print("我是玩家")

# 获取组里所有节点
var enemies = get_tree().get_nodes_in_group("enemy")

# 获取第一个
var player = get_tree().get_first_node_in_group("player")

# 给组里所有节点调用方法
get_tree().call_group("enemy", "take_damage", 10)

# 通知组
get_tree().notify_group("enemy", NOTIFICATION_GAME_START)

自动加载(单例)

在项目设置 → 自动加载中注册的脚本,可以全局访问:

# 假设注册了 GameManager
# 任何地方都可以直接调用
GameManager.score += 100
GameManager.save_game()

# 常见用途
# - 游戏状态管理
# - 存档系统
# - 全局事件总线
# - 音频管理器
# - 场景切换管理器

资源系统

# 加载资源
var texture = load("res://icon.png")
var scene = load("res://scene.tscn")

# 预加载(编译时加载,更快)
var player_scene = preload("res://player.tscn")

# 动态加载
var dynamic_resource = load(path)
if dynamic_resource == null:
    print("加载失败: ", path)

# 保存资源
var config = ConfigFile.new()
config.set_value("player", "score", 100)
config.save("user://save.cfg")

# 用户数据路径
var save_path = "user://savegame.dat"

音频

# AudioStreamPlayer
$Music.stream = load("res://music.ogg")
$Music.play()
$Music.stop()
$Music.volume_db = -10  # 音量(分贝)
$Music.pitch_scale = 1.0  # 音调

# AudioStreamPlayer2D(有位置的音效)
$Sound2D.position = Vector2(100, 200)
$Sound2D.play()

# 常用方法
player.play()
player.stop()
player.pause()
player.seek(2.0)  # 跳到第 2 秒

# 播放一次性音效
func play_sfx(sound):
    var sfx = AudioStreamPlayer.new()
    sfx.stream = sound
    add_child(sfx)
    sfx.finished.connect(sfx.queue_free)
    sfx.play()

文件读写

# 读取文件
var file = FileAccess.open("res://data.txt", FileAccess.READ)
if file:
    var content = file.get_as_text()
    file.close()

# 写入文件
var file = FileAccess.open("user://save.txt", FileAccess.WRITE)
if file:
    file.store_string("hello")
    file.close()

# JSON 读写
# 保存
var data = {"score": 100, "name": "player"}
var json_str = JSON.stringify(data)
FileAccess.write_string("user://save.json", json_str)

# 读取
var json_str = FileAccess.get_file_as_string("user://save.json")
var data = JSON.parse_string(json_str)

C# API 参考

使用 C# 时,API 命名略有不同(PascalCase 风格):

using Godot;

public partial class Player : CharacterBody2D
{
    [Export] public float Speed = 200.0f;
    [Export] public float JumpForce = -400.0f;

    public override void _Ready()
    {
        // 节点准备好时
        var sprite = GetNode<Sprite2D>("Sprite2D");
    }

    public override void _PhysicsProcess(double delta)
    {
        float dir = Input.GetAxis("move_left", "move_right");
        Velocity = new Vector2(dir * Speed, Velocity.Y);

        if (Input.IsActionJustPressed("jump") && IsOnFloor())
        {
            Velocity = new Vector2(Velocity.X, JumpForce);
        }

        MoveAndSlide();
    }

    // 信号定义
    [Signal]
    public delegate void DiedEventHandler();

    public void Die()
    {
        EmitSignal(SignalName.Died);
        QueueFree();
    }
}

常用 API 速查表

功能 GDScript C#
获取节点 $NodeName / get_node() GetNode<T>("NodeName")
获取子节点 get_children() GetChildren()
添加子节点 add_child(node) AddChild(node)
删除节点 queue_free() QueueFree()
连接信号 signal.connect(func) Signal += Handler
发出信号 signal.emit() EmitSignal()
场景切换 get_tree().change_scene_to_file() GetTree().ChangeSceneToFile()
创建 Tween create_tween() CreateTween()
等待时间 await get_tree().create_timer(t).timeout await ToSignal(GetTree().CreateTimer(t), ...)
退出游戏 get_tree().quit() GetTree().Quit()

更多资源