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

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 注解
空安全可选类型支持
枚举基础支持支持自动赋值和标志位
awaityieldawait 关键字

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 变量类型对照表

类型默认值示例说明
int0var x: int = 42整数
float0.0var f: float = 3.14浮点数
boolfalsevar b: bool = true布尔值
String""var s: String = "hi"字符串
Vector2Vector2(0,0)var v := Vector2(1, 2)2D 向量
Vector3Vector3(0,0,0)var v := Vector3(1, 2, 3)3D 向量
Array[]var a: Array = [1, 2]数组
Dictionary{}var d: Dictionary = {}字典
ColorColor(0,0,0,1)var c := Color.RED颜色
NodePathNodePath("")var p := ^"path/to"节点路径
Transform2DTransform2D()var t: Transform2D2D 变换
Transform3DTransform3D()var t: Transform3D3D 变换

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 / 23整数除法
幂运算**2 ** 38
比较== != < > <= >=a > b比较
逻辑and or nota and b布尔逻辑
位运算& `^ ~ « »`a & b
赋值= += -= *= /=a += 1赋值组合
三元if ... else (表达式)x if cond else y条件表达式
类型is asnode 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