Godot 4 GDScript 教程 / GDScript 2.0 基础语法
GDScript 2.0 基础语法
GDScript 2.0 是 Godot 4 的原生脚本语言,在 1.0 基础上引入了类型系统、注解(annotation)、f-string 语法和 lambda 表达式等现代特性。本章全面介绍 GDScript 2.0 的基础语法。
1. GDScript 2.0 变更概述
| 变更领域 | GDScript 1.0 (Godot 3) | GDScript 2.0 (Godot 4) |
|---|---|---|
| 类型系统 | 可选,无强制 | 类型化,支持推断 |
| 信号连接 | connect("signal", obj, "method") | signal.connect(callable) |
| 字符串 | %s 格式化 | f-string 支持 |
| Lambda | 不支持 | func(): ... 表达式 |
| 注解 | export 关键字 | @export 注解 |
| 空安全 | 无 | 可选类型支持 |
| 枚举 | 基础支持 | 支持自动赋值和标志位 |
await | yield | await 关键字 |
2. 脚本基础结构
2.1 最小脚本模板
# 基础脚本结构
extends Node # 继承的节点类型
# ── 常量 ──────────────────────────────
const MAX_SPEED: int = 500
# ── 信号 ──────────────────────────────
signal health_changed(new_health: int)
# ── 变量 ──────────────────────────────
var health: int = 100
var velocity: Vector2 = Vector2.ZERO
# ─� 生命周期函数 ─────────────────────
func _ready() -> void:
"""节点进入场景树时调用"""
pass
func _process(delta: float) -> void:
"""每帧调用(逻辑更新)"""
pass
func _physics_process(delta: float) -> void:
"""物理帧调用(物理相关)"""
pass
# ── 自定义函数 ────────────────────────
func take_damage(amount: int) -> void:
health -= amount
health_changed.emit(health)
2.2 脚本继承
# base_character.gd - 基类
class_name BaseCharacter
extends CharacterBody2D
var health: int = 100
func take_damage(amount: int) -> void:
health -= amount
if health <= 0:
die()
func die() -> void:
queue_free()
# player.gd - 继承基类
extends BaseCharacter
var score: int = 0
func take_damage(amount: int) -> void:
super(amount) # 调用父类方法
# 可以在这里添加受伤特效
func die() -> void:
# 覆写死亡逻辑
print("游戏结束!最终分数: %d" % score)
super() # 调用父类的 die()
💡 提示: 使用 class_name 注册的全局类名可以在整个项目中引用,无需 preload。
3. 变量与常量
3.1 变量声明
# 无类型变量(动态类型)
var health = 100
var name = "Player"
var speed = 3.14
# 类型化变量(推荐)
var health: int = 100
var name: String = "Player"
var speed: float = 3.14
var is_alive: bool = true
var position: Vector2 = Vector2.ZERO
var color: Color = Color.WHITE
var texture: Texture2D = null
# 类型推断(编译器自动推断类型)
var damage := 50 # 推断为 int
var label := "Hello" # 推断为 String
var ratio := 0.5 # 推断为 float
3.2 变量类型对照表
| 类型 | 默认值 | 示例 | 说明 |
|---|---|---|---|
int | 0 | var x: int = 42 | 整数 |
float | 0.0 | var f: float = 3.14 | 浮点数 |
bool | false | var b: bool = true | 布尔值 |
String | "" | var s: String = "hi" | 字符串 |
Vector2 | Vector2(0,0) | var v := Vector2(1, 2) | 2D 向量 |
Vector3 | Vector3(0,0,0) | var v := Vector3(1, 2, 3) | 3D 向量 |
Array | [] | var a: Array = [1, 2] | 数组 |
Dictionary | {} | var d: Dictionary = {} | 字典 |
Color | Color(0,0,0,1) | var c := Color.RED | 颜色 |
NodePath | NodePath("") | var p := ^"path/to" | 节点路径 |
Transform2D | Transform2D() | var t: Transform2D | 2D 变换 |
Transform3D | Transform3D() | var t: Transform3D | 3D 变换 |
3.3 常量
# 编译时常量(值必须在编译时确定)
const MAX_HEALTH: int = 100
const GRAVITY: float = 980.0
const GAME_TITLE: String = "我的游戏"
# 枚举常量
enum State { IDLE, RUNNING, JUMPING, FALLING }
# 常量可以是表达式
const HALF_PI: float = PI / 2
const SCREEN_CENTER: Vector2 = Vector2(576, 324)
# 不能用运行时值
# var runtime_value = 10
# const BAD: int = runtime_value # 错误!
⚠️ 注意: 常量必须在声明时确定值,不能使用运行时表达式。常量值在整个程序生命周期内不可改变。
4. 类型系统改进
4.1 类型注解
# 变量类型注解
var player_name: String = "Hero"
var health: int = 100
var speed: float = 200.0
# 函数参数和返回值类型
func calculate_damage(base: int, multiplier: float) -> int:
return int(base * multiplier)
# 节点类型注解
@onready var sprite: Sprite2D = $Sprite2D
@onready var camera: Camera2D = $Camera2D
# 数组类型注解
var enemies: Array[Node2D] = []
var scores: Array[int] = [100, 200, 300]
4.2 类型安全
var health: int = 100
func _ready() -> void:
# 类型不匹配会报错
# health = "full" # 编译错误!
# 类型转换
var float_val: float = 3.14
var int_val: int = int(float_val) # 3
var string_val: String = "42"
var parsed_int: int = string_val.to_int() # 42
# 安全类型检查
var node: Node = $Sprite2D
if node is Sprite2D:
var sprite: Sprite2D = node as Sprite2D
print("Sprite 纹理: %s" % sprite.texture)
4.3 自定义类型
# 定义自定义类
class_name HealthComponent
extends Node
signal health_depleted
signal health_changed(old_value: int, new_value: int)
@export var max_health: int = 100
var current_health: int
func _ready() -> void:
current_health = max_health
func take_damage(amount: int) -> void:
var old_health := current_health
current_health = clampi(current_health - amount, 0, max_health)
health_changed.emit(old_health, current_health)
if current_health <= 0:
health_depleted.emit()
func heal(amount: int) -> void:
var old_health := current_health
current_health = clampi(current_health + amount, 0, max_health)
health_changed.emit(old_health, current_health)
func get_health_percent() -> float:
return float(current_health) / float(max_health)
5. 字符串
5.1 字符串基础
# 基本字符串
var greeting: String = "Hello, Godot!"
# 多行字符串
var dialogue: String = """这是一段
跨越多行的
对话内容"""
# 转义字符
var escaped: String = "他说:\"你好\"\t\t换行\n"
# 原始字符串(不转义)
var path: String = r"C:\Users\Player\game"
5.2 F-String 语法(Godot 4 新增)
var player_name: String = "Hero"
var level: int = 5
var health: float = 85.5
# f-string 基本用法
var info: String = f"玩家: {player_name}, 等级: {level}"
print(info) # 输出: 玩家: Hero, 等级: 5
# 表达式支持
print(f"生命值: {health / 100 * 100:.1f}%")
print(f"升级所需经验: {100 * level * level}")
# 格式化数字
var price: float = 19.99
print(f"价格: ¥{price:.2f}") # 输出: 价格: ¥19.99
# 访问对象属性
var pos := Vector2(100, 200)
print(f"位置: ({pos.x}, {pos.y})")
# 嵌套引号
var item_name: String = "生命药水"
var msg: String = f'你获得了 "{item_name}"'
5.3 字符串操作
var text: String = "Hello, Godot 4!"
# 基本操作
var length: int = text.length() # 15
var upper: String = text.to_upper() # "HELLO, GODOT 4!"
var lower: String = text.to_lower() # "hello, godot 4!"
var stripped: String = " hi ".strip_edges() # "hi"
# 查找与替换
var has_godot: bool = text.contains("Godot") # true
var pos: int = text.find("Godot") # 7
var replaced: String = text.replace("Godot", "GDScript")
var count: int = text.count("o") # 2
# 分割与连接
var csv: String = "apple,banana,cherry"
var fruits: PackedStringArray = csv.split(",")
var joined: String = "-".join(fruits) # "apple-banana-cherry"
# 子字符串
var sub: String = text.substr(7, 5) # "Godot"
# 类型转换
var num_str: String = "42"
var num: int = num_str.to_int() # 42
var float_str: String = "3.14"
var f: float = float_str.to_float() # 3.14
# 检查前后缀
var filename: String = "scene.tscn"
var is_scene: bool = filename.ends_with(".tscn") # true
var is_prefixed: bool = filename.begins_with("scene") # true
6. 数组 Array
6.1 基本数组操作
# 创建数组
var fruits: Array = ["apple", "banana", "cherry"]
var numbers: Array[int] = [1, 2, 3, 4, 5]
var mixed: Array = [1, "two", 3.0, true]
# 添加元素
fruits.append("date") # 末尾添加
numbers.insert(0, 0) # 指定位置插入
numbers.push_back(6) # 末尾添加(等同 append)
numbers.push_front(-1) # 开头添加
# 删除元素
fruits.erase("banana") # 按值删除
numbers.remove_at(0) # 按索引删除
numbers.pop_back() # 移除并返回最后一个
numbers.pop_front() # 移除并返回第一个
# 查找元素
var has_apple: bool = fruits.has("apple") # true
var idx: int = fruits.find("cherry") # 2
var count: int = fruits.count("apple") # 出现次数
# 排序
numbers.sort() # 升序排列
numbers.sort_custom(func(a, b): return a > b) # 降序
numbers.reverse() # 反转
numbers.shuffle() # 随机打乱
# 其他操作
var size: int = numbers.size() # 长度
var empty: bool = numbers.is_empty() # 是否为空
numbers.clear() # 清空
var sliced: Array = numbers.slice(1, 4) # 切片
6.2 类型化数组
# 类型化数组(Godot 4 新增)
var scores: Array[int] = []
var names: Array[String] = ["Alice", "Bob"]
var enemies: Array[Node2D] = []
# 类型安全
scores.append(100) # OK
# scores.append("100") # 类型错误!
# 遍历
for score in scores:
print(f"分数: {score}")
# 带索引遍历
for i in range(scores.size()):
print(f"第 {i+1} 项: {scores[i]}")
# 函数式操作
var doubled: Array[int] = scores.map(func(x: int) -> int: return x * 2)
var big_scores: Array = scores.filter(func(x: int) -> bool: return x > 50)
var total: int = scores.reduce(func(acc: int, x: int) -> int: return acc + x, 0)
7. 字典 Dictionary
7.1 基本操作
# 创建字典
var player: Dictionary = {
"name": "Hero",
"health": 100,
"level": 1,
"inventory": ["sword", "shield"]
}
# 类型化字典(Godot 4.4+)
var scores: Dictionary = {"alice": 100, "bob": 200}
# 访问和修改
player["name"] = "Warrior"
var name: String = player.get("name", "Unknown") # 带默认值
# 添加/删除
player["mana"] = 50
player.erase("mana")
# 检查键是否存在
var has_name: bool = player.has("name")
var has_key: bool = "name" in player
# 获取所有键/值
var keys: Array = player.keys()
var values: Array = player.values()
# 遍历
for key in player:
print(f"{key}: {player[key]}")
# 合并字典
var extra: Dictionary = {"attack": 10, "defense": 5}
player.merge(extra)
# 大小
var size: int = player.size()
var empty: bool = player.is_empty()
7.2 字典实战
# 游戏配置数据
var game_config: Dictionary = {
"display": {
"resolution": Vector2i(1920, 1080),
"fullscreen": true,
"vsync": true
},
"audio": {
"master": 1.0,
"music": 0.8,
"sfx": 1.0
},
"difficulty": "normal"
}
func get_nested_value(dict: Dictionary, key_path: String, default = null):
"""安全地获取嵌套字典的值"""
var keys = key_path.split("/")
var current = dict
for key in keys:
if current is Dictionary and current.has(key):
current = current[key]
else:
return default
return current
# 使用示例
var volume: float = get_nested_value(game_config, "audio/music", 1.0)
8. 枚举 enum
8.1 基本枚举
# 基本枚举
enum Direction { UP, DOWN, LEFT, RIGHT }
# 自动赋值: UP=0, DOWN=1, LEFT=2, RIGHT=3
# 自定义值
enum ErrorCode {
SUCCESS = 0,
NOT_FOUND = 404,
SERVER_ERROR = 500,
UNAUTHORIZED = 401
}
# 标志位枚举(位掩码)
enum Permission {
NONE = 0,
READ = 1, # 0b001
WRITE = 2, # 0b010
EXECUTE = 4, # 0b100
ALL = 7 # READ | WRITE | EXECUTE
}
# 使用枚举
var current_dir: Direction = Direction.UP
var user_perm: Permission = Permission.READ | Permission.WRITE
# 检查权限
func has_permission(user_perms: Permission, required: Permission) -> bool:
return (user_perms & required) == required
# 枚举作函数参数
func move(direction: Direction) -> void:
match direction:
Direction.UP:
velocity.y = -speed
Direction.DOWN:
velocity.y = speed
Direction.LEFT:
velocity.x = -speed
Direction.RIGHT:
velocity.x = speed
8.2 枚举在 @export 中使用
extends Node
enum EnemyType { SLIME, SKELETON, DRAGON }
enum AttackMode { MELEE, RANGED, MAGIC }
@export var enemy_type: EnemyType = EnemyType.SLIME
@export var attack_mode: AttackMode = AttackMode.MELEE
@export_flags("Fire", "Water", "Earth", "Wind") var elements: int = 0
@export_enum("Easy", "Normal", "Hard", "Insane") var difficulty: String = "Normal"
💡 提示: @export_enum 可以让枚举值在检查器中以选择框形式显示,提高编辑效率。
9. 运算符
9.1 运算符速查表
| 类别 | 运算符 | 示例 | 说明 |
|---|---|---|---|
| 算术 | + - * / % | a + b | 加减乘除取余 |
| 整除 | / (int/int) | 7 / 2 → 3 | 整数除法 |
| 幂运算 | ** | 2 ** 3 → 8 | 幂 |
| 比较 | == != < > <= >= | a > b | 比较 |
| 逻辑 | and or not | a and b | 布尔逻辑 |
| 位运算 | & ` | ^ ~ « »` | a & b |
| 赋值 | = += -= *= /= | a += 1 | 赋值组合 |
| 三元 | if ... else (表达式) | x if cond else y | 条件表达式 |
| 类型 | is as | node is Sprite2D | 类型检查/转换 |
| 成员 | . [] | node.position | 访问成员 |
| 范围 | .. | 0..5 → [0,1,2,3,4,5] | 范围表达式 |
9.2 运算符示例
func _ready() -> void:
# 算术运算
var a: int = 10
var b: int = 3
print(f"{a} + {b} = {a + b}") # 13
print(f"{a} - {b} = {a - b}") # 7
print(f"{a} * {b} = {a * b}") # 30
print(f"{a} / {b} = {a / b}") # 3 (整数除法)
print(f"{a} % {b} = {a % b}") # 1 (取余)
print(f"{a} ** {b} = {a ** b}") # 1000 (幂)
# 向量运算
var v1 := Vector2(1, 2)
var v2 := Vector2(3, 4)
print(f"向量加法: {v1 + v2}") # (4, 6)
print(f"向量点积: {v1.dot(v2)}") # 11
print(f"向量长度: {v1.length()}") # ~2.236
# 条件表达式(三元运算)
var max_val = a if a > b else b
print(f"最大值: {max_val}") # 10
# 类型检查
var node: Node = $Sprite2D
if node is Sprite2D:
var sprite := node as Sprite2D
print(f"精灵名称: {sprite.name}")
10. 注释与文档注释
10.1 注释类型
# 单行注释
#
# 多行注释
# 使用多个单行注释
#
## 文档注释 - 会在编辑器工具提示中显示
## @param amount: 伤害值
## @return: 剩余生命值
func take_damage(amount: int) -> int:
health -= amount
return health
## 代表玩家角色
## 这个类管理玩家的所有状态和行为
class_name Player
extends CharacterBody2D
## 最大生命值
@export var max_health: int = 100
## 移动速度(像素/秒)
@export var move_speed: float = 300.0
10.2 代码规范
# 推荐的代码风格(参照官方 GDScript 风格指南)
# 1. 声明顺序
extends Node
# 常量
const MAX_VALUE: int = 100
# 信号
signal value_changed(new_value: int)
# 枚举
enum State { IDLE, ACTIVE }
# 导出变量
@export var enabled: bool = true
# 公共变量
var current_value: int = 0
# 私有变量(前缀下划线)
var _internal_state: int = 0
# @onready 变量
@onready var label: Label = $Label
# 生命周期函数
func _ready() -> void:
pass
func _process(delta: float) -> void:
pass
# 公共函数
func update_value(new_value: int) -> void:
current_value = new_value
value_changed.emit(current_value)
# 私有函数
func _calculate_something() -> int:
return 0
11. 游戏开发场景
场景:RPG 角色属性系统
class_name RPGCharacter
extends Node
## 角色职业
enum CharacterClass { WARRIOR, MAGE, ROGUE, HEALER }
## 元素亲和
enum Element { NONE, FIRE, ICE, LIGHTNING }
## 基础属性
@export_group("Base Stats")
@export var character_name: String = "Unknown"
@export var character_class: CharacterClass = CharacterClass.WARRIOR
@export var base_health: int = 100
@export var base_attack: int = 10
@export var base_defense: int = 5
## 当前状态
var level: int = 1
var experience: int = 0
var current_health: int
## 装备加成
var equipment_bonus: Dictionary = {
"health": 0,
"attack": 0,
"defense": 0
}
## 经验表
const EXP_TABLE: Array[int] = [
0, 100, 300, 600, 1000, 1500, 2100, 2800, 3600, 4500
]
func _ready() -> void:
current_health = get_max_health()
func get_max_health() -> int:
return base_health + equipment_bonus["health"] + (level - 1) * 10
func get_attack() -> int:
return base_attack + equipment_bonus["attack"] + (level - 1) * 2
func get_defense() -> int:
return base_defense + equipment_bonus["defense"] + (level - 1) * 1
func gain_experience(amount: int) -> bool:
"""获得经验,返回是否升级"""
experience += amount
if level < EXP_TABLE.size() and experience >= EXP_TABLE[level]:
level_up()
return true
return false
func level_up() -> void:
"""升级"""
level += 1
current_health = get_max_health()
print(f"{character_name} 升级到 Lv.{level}!")
func take_damage(raw_damage: int) -> int:
"""受到伤害,返回实际伤害"""
var actual_damage: int = maxi(raw_damage - get_defense(), 1)
current_health = clampi(current_health - actual_damage, 0, get_max_health())
if current_health <= 0:
_on_death()
return actual_damage
func _on_death() -> void:
"""角色死亡处理"""
print(f"{character_name} 已阵亡!")
12. 扩展阅读
上一章: 03 - 编辑器界面与工作流 下一章: 05 - 类型化 GDScript