强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Godot 3 GDScript 教程 / GDScript 基础语法

GDScript 基础语法

GDScript 概述

GDScript 是 Godot 引擎的内置脚本语言,语法受 Python 启发,专为游戏开发优化。它与 Godot 的节点系统深度集成,语法简洁、学习曲线平缓。

GDScript vs Python

特性GDScriptPython
缩进语法✅ 使用 Tab✅ 使用空格
类型系统可选类型注解可选类型注解
继承extendsclass MyClass(Base)
构造函数_init()__init__()
空值nullNone
字典{}{}
数组[][]
字符串格式化"Hello %s" % namef"Hello {name}"
运行环境Godot 引擎CPython 解释器

脚本创建与附加

创建脚本的三种方式

  1. 菜单创建:选中节点 → Inspector 底部 → “Attach Script”
  2. 快捷键:选中节点 → 按 Ctrl+Shift+N
  3. 文件系统:在 FileSystem 面板右键 → New → GDScript

脚本结构模板

extends Node  # 继承自哪个节点类型

# 类变量
var health: int = 100
var speed: float = 200.0

# 生命周期函数
func _ready() -> void:
    pass  # 节点进入场景树时调用

func _process(delta: float) -> void:
    pass  # 每帧调用

func _physics_process(delta: float) -> void:
    pass  # 每个物理帧调用

⚠️ 注意:每个 .gd 文件只能有一个 extends 声明,且必须在文件开头。

变量声明

var 关键字

# 基本变量声明
var health = 100                  # 类型推断为 int
var name = "Player"               # 类型推断为 String
var position = Vector2(100, 200)  # 类型推断为 Vector2

# 带类型注解的声明(推荐)
var health: int = 100
var speed: float = 250.0
var is_alive: bool = true
var player_name: String = "Hero"
var direction: Vector2 = Vector2.ZERO

# 无初始值(默认为 null)
var target: Node2D
var score: int

const 常量

# 常量必须在声明时初始化,之后不可修改
const MAX_HEALTH: int = 100
const GRAVITY: float = 980.0
const PLAYER_SPEED: float = 300.0

# 常量可以是复杂类型
const ENEMY_SCENES = {
    "slime": preload("res://scenes/Slime.tscn"),
    "goblin": preload("res://scenes/Goblin.tscn"),
}

# 常量表达式
const HALF_MAX: int = MAX_HEALTH / 2  # = 50

⚠️ 注意const 仅限基本类型和 preload 的资源,不能使用运行时计算的值。

变量作用域

extends Node

var class_var: int = 10  # 类变量(成员变量),整个脚本可访问

func _ready() -> void:
    var local_var: int = 20  # 局部变量,仅在 _ready 内可用
    print(class_var)  # ✅ 可访问
    print(local_var)  # ✅ 可访问

func _process(delta: float) -> void:
    print(class_var)  # ✅ 可访问
    # print(local_var)  # ❌ 错误!local_var 在此作用域不存在

数据类型详解

基本类型

类型说明示例
int整数42, -10, 0xFF
float浮点数3.14, -0.5, 1e10
bool布尔值true, false
String字符串"Hello", 'World'

向量与数学类型

类型说明示例
Vector22D 向量Vector2(1, 2)
Vector33D 向量Vector3(1, 2, 3)
Rect22D 矩形Rect2(0, 0, 100, 100)
Transform2D2D 变换位移+旋转+缩放
Transform3D 变换位移+旋转+缩放
Color颜色Color(1, 0, 0), Color.red

容器类型

# 数组 Array
var items: Array = ["sword", "shield", "potion"]
items.append("bow")         # 添加元素
items.erase("shield")       # 删除元素
var first = items[0]        # 访问元素
var count = items.size()    # 获取大小
items.sort()                # 排序
items.shuffle()             # 随机打乱

# 类型化数组
var scores: Array[int] = [100, 200, 300]

# 字典 Dictionary
var player = {
    "name": "Hero",
    "health": 100,
    "level": 1,
}
player["health"] = 80       # 修改值
player["attack"] = 25       # 添加新键
var hp = player.get("health", 0)  # 安全获取,带默认值
player.erase("level")       # 删除键

# 遍历字典
for key in player:
    print("%s: %s" % [key, player[key]])

# 检查键是否存在
if player.has("health"):
    print("生命值: ", player["health"])

类型推断与类型注解

类型推断

# Godot 自动推断类型
var x = 10          # 推断为 int
var y = 3.14        # 推断为 float
var name = "Godot"  # 推断为 String

显式类型注解(推荐)

# 明确声明类型有助于代码可读性和错误检查
var health: int = 100
var speed: float = 200.0
var name: String = "Player"
var direction: Vector2 = Vector2.RIGHT

# 函数参数和返回值类型
func take_damage(amount: int) -> bool:
    health -= amount
    return health <= 0

💡 提示:开启类型注解后,编辑器可以提供更好的自动补全和错误检测。

运算符

算术运算符

var a = 10
var b = 3

var sum = a + b       # 13   加法
var diff = a - b      # 7    减法
var prod = a * b      # 30   乘法
var quot = a / b      # 3    整数除法
var quot_f = 10.0 / 3 # 3.33 浮点除法
var rem = a % b       # 1    取余
var power = pow(2, 10) # 1024 幂运算

比较运算符

var x = 5
x == 5   # true   等于
x != 3   # true   不等于
x > 3    # true   大于
x < 10   # true   小于
x >= 5   # true   大于等于
x <= 4   # false  小于等于

逻辑运算符

var a = true
var b = false

a and b  # false  逻辑与
a or b   # true   逻辑或
not a    # false  逻辑非

# 短路求值
if health > 0 and is_alive:  # 如果 health <= 0,不会检查 is_alive
    print("角色存活")

位运算符

var flags = 0b1010  # 10

flags | 0b0001   # 0b1011 = 11  按位或
flags & 0b1100   # 0b1000 = 8   按位与
flags ^ 0b1111   # 0b0101 = 5   按位异或
~flags            # 按位取反
flags << 2        # 左移2位
flags >> 1        # 右移1位

赋值运算符

var x = 10
x += 5   # x = x + 5  = 15
x -= 3   # x = x - 3  = 12
x *= 2   # x = x * 2  = 24
x /= 4   # x = x / 4  = 6
x %= 4   # x = x % 4  = 2

向量运算

var a = Vector2(1, 2)
var b = Vector2(3, 4)

var sum = a + b        # Vector2(4, 6)
var diff = a - b       # Vector2(-2, -2)
var scaled = a * 2.0   # Vector2(2, 4)
var length = a.length() # 2.236
var normalized = a.normalized()  # 单位向量
var dist = a.distance_to(b)     # 距离
var dot = a.dot(b)     # 点积

字符串格式化

% 格式化(类似 C 的 printf)

var name = "Hero"
var level = 10
var hp = 85.5

# %s - 字符串
# %d - 整数
# f - 浮点数
# %02d - 补零的整数

print("玩家: %s, 等级: %d, 生命: %.1f%%" % [name, level, hp])
# 输出: 玩家: Hero, 等级: 10, 生命: 85.5%

var time = 125
print("%02d:%02d" % [time / 60, time % 60])
# 输出: 02:05

字符串常用方法

var text = "Hello, Godot!"

text.length()            # 13
text.to_upper()          # "HELLO, GODOT!"
text.to_lower()          # "hello, godot!"
text.begins_with("Hello") # true
text.ends_with("!")      # true
text.find("Godot")       # 7
text.replace("Godot", "World")  # "Hello, World!"
text.substr(0, 5)        # "Hello"
text.split(", ")         # ["Hello", "Godot!"]

# 字符串连接
var greeting = "Hello" + ", " + "World"  # 拼接
var greeting2 = "Hello, %s" % "World"    # 格式化

# 多行字符串
var dialogue = """这是一段
跨越多行的
对话文本。"""

注释

# 这是单行注释

"""
这是多行注释(文档字符串)
通常用于函数说明
"""

# === 区域标记 ===
# 可以在编辑器中折叠
# region 敌人逻辑
func spawn_enemy() -> void:
    pass
func kill_enemy() -> void:
    pass
# endregion

文档注释

# 计算伤害值
# @param base_damage 基础伤害
# @param multiplier 伤害倍率
# @return 最终伤害值
func calculate_damage(base_damage: int, multiplier: float) -> int:
    return int(base_damage * multiplier)

代码规范

命名约定

元素约定示例
变量snake_caseplayer_health
常量SCREAMING_SNAKE_CASEMAX_HEALTH
函数snake_casecalculate_damage()
信号snake_casehealth_changed
类名PascalCaseEnemyController
枚举PascalCaseEnemyState
枚举值SCREAMING_SNAKE_CASEIDLE, WALK

代码风格

# ✅ 推荐风格
extends KinematicBody2D

var speed: float = 200.0
var health: int = 100

signal died()
signal health_changed(new_health: int)

func _ready() -> void:
    pass

func take_damage(amount: int) -> void:
    health -= amount
    emit_signal("health_changed", health)
    if health <= 0:
        die()

func die() -> void:
    emit_signal("died")
    queue_free()

脚本生命周期

Godot 节点有一系列内置的生命周期回调函数:

节点创建
  │
  ├── _init()           # 构造函数
  │
  ├── _enter_tree()     # 进入场景树
  │   └── _ready()      # 子节点都已就绪(从下往上)
  │
  ├── _process(delta)       # 每帧调用(逻辑更新)
  ├── _physics_process(delta) # 每物理帧调用(物理更新)
  ├── _input(event)         # 输入事件
  │
  ├── _exit_tree()      # 离开场景树
  └── _notification()   # 通知回调

各回调详解

extends Node2D

# 构造函数(场景实例化时调用)
# 此时节点还未加入场景树,无法访问子节点
func _init() -> void:
    print("_init: 节点已创建")

# 进入场景树时调用(每次加入树都会调用)
func _enter_tree() -> void:
    print("_enter_tree: 进入场景树")

# 子节点的 _ready 已全部完成
# 适合初始化逻辑(最常用的初始化位置)
func _ready() -> void:
    print("_ready: 节点就绪")
    # 此时可以安全地使用 $ChildNode 访问子节点

# 每帧调用(参数 delta 是上一帧到这一帧的时间差,单位秒)
func _process(delta: float) -> void:
    # 用于非物理相关的逻辑:动画、UI更新、AI决策等
    position.x += 100 * delta

# 每个物理帧调用(默认每秒60次)
func _physics_process(delta: float) -> void:
    # 用于物理相关的逻辑:移动、碰撞检测
    move_and_slide(Vector2(200, 0))

# 输入事件回调
func _input(event: InputEvent) -> void:
    if event.is_action_pressed("ui_accept"):
        print("按下了确认键")

# 离开场景树时调用
func _exit_tree() -> void:
    print("_exit_tree: 离开场景树")

# Godot 通知回调
func _notification(what: int) -> void:
    match what:
        NOTIFICATION_PAUSED:
            print("游戏暂停")
        NOTIFICATION_UNPAUSED:
            print("游戏恢复")

delta 的使用

func _process(delta: float) -> void:
    # ❌ 错误:帧率不同时移动速度不一致
    position.x += 5
    
    # ✅ 正确:乘以 delta 使移动速度与帧率无关
    # 无论 60fps 还是 144fps,每秒都移动 300 像素
    position.x += 300 * delta

⚠️ 注意delta 单位是秒(如 1/60 ≈ 0.0167),乘以速度后得到每帧的位移量。

_process vs _physics_process

特性_process_physics_process
调用频率与帧率相同(可变)固定(默认 60fps)
用途视觉效果、UI、动画物理、移动、碰撞
受帧率影响✅ 是❌ 否
delta变化固定

💡 提示

  • 纯视觉逻辑(动画、UI 更新)用 _process
  • 物理相关逻辑(移动、碰撞)用 _physics_process
  • 两者都可以使用,但职责要分离

游戏开发场景

场景:一个简单的计时器系统

在许多游戏中,需要计时功能,如关卡时间限制、技能冷却、Boss 战阶段切换等。

extends Node

# 计时器变量
var elapsed_time: float = 0.0
var is_running: bool = false

# 信号
signal time_updated(current_time: float)
signal time_up()

# 配置
export var countdown_time: float = 60.0  # 倒计时秒数
export var is_countdown: bool = true      # 是否倒计时模式

func _process(delta: float) -> void:
    if not is_running:
        return
    
    if is_countdown:
        elapsed_time -= delta
        if elapsed_time <= 0.0:
            elapsed_time = 0.0
            is_running = false
            emit_signal("time_up")
    else:
        elapsed_time += delta
    
    emit_signal("time_updated", elapsed_time)

func start_timer() -> void:
    if is_countdown:
        elapsed_time = countdown_time
    else:
        elapsed_time = 0.0
    is_running = true

func stop_timer() -> void:
    is_running = false

func get_time_string() -> String:
    var minutes = int(elapsed_time) / 60
    var seconds = int(elapsed_time) % 60
    return "%02d:%02d" % [minutes, seconds]

扩展阅读