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

Godot 3 GDScript 教程 / 变量、类型与常量

变量、类型与常量

基本类型详解

GDScript 拥有丰富的内置类型,可以分为基本类型、容器类型、引擎类型三大类。

基本类型一览

类型关键字大小默认值示例
整数int64 位042, -10, 0xFF, 0b1010
浮点数float64 位0.03.14, -0.5, 1.2e3
布尔bool1 位falsetrue, false
字符串String动态"""Hello", 'World'

进制表示法

var decimal = 255        # 十进制
var hex = 0xFF           # 十六进制 = 255
var binary = 0b11111111  # 二进制 = 255
var octal = 0o377        # 八进制 = 255
var scientific = 1.5e10  # 科学计数法

# 用下划线提高可读性
var million = 1_000_000
var color_val = 0xFF_CC_00

int 与 float 的转换

var i = 10
var f = 3.14

# 隐式转换(int → float,安全)
var result = i + f  # 13.14,类型为 float

# 显式转换
var f2 = float(i)   # 10.0
var i2 = int(f)     # 3(截断小数)
var i3 = int(round(f))  # 3(四舍五入)

# 注意整数除法
print(7 / 2)    # 3(整数除法,截断小数)
print(7.0 / 2)  # 3.5(浮点除法)
print(7 % 2)    # 1(取余)

⚠️ 注意:GDScript 中 int / int 返回 int,不会自动转为 float

bool 类型

var is_alive = true
var is_dead = false

# 真值判断
# 以下值在布尔上下文中被视为 false:
# false, 0, 0.0, "", null, Vector2.ZERO, Vector3.ZERO

# 以下值被视为 true:
# true, 非零数字, 非空字符串, 非null对象

if 0:
    print("不会执行")
if 1:
    print("会执行")

# 布尔运算
var can_attack = is_alive and has_ammo
var can_move = is_on_floor or is_jumping
var is_blocked = not can_move

枚举(enum)

枚举用于定义一组命名常量,提高代码可读性。

基本枚举

# 定义枚举
enum Direction { UP, DOWN, LEFT, RIGHT }
# 等价于: UP=0, DOWN=1, LEFT=2, RIGHT=3

# 使用枚举
var facing = Direction.UP
print(facing)        # 0
print(Direction.UP)  # 0

# 比较
if facing == Direction.UP:
    print("朝上")

# 匹配
match facing:
    Direction.UP:
        print("上")
    Direction.DOWN:
        print("下")
    Direction.LEFT:
        print("左")
    Direction.RIGHT:
        print("右")

自定义枚举值

enum GameState {
    MENU = 0,
    PLAYING = 1,
    PAUSED = 2,
    GAME_OVER = 3,
}

enum ItemRarity {
    COMMON = 10,
    UNCOMMON = 20,
    RARE = 50,
    EPIC = 100,
    LEGENDARY = 200,
}

# 间隔值
enum Permission {
    READ = 1,      # 001
    WRITE = 2,     # 010
    EXECUTE = 4,   # 100
}

导出枚举到 Inspector

extends Sprite

enum EnemyType { SLIME, GOBLIN, DRAGON }
export(EnemyType) var type = EnemyType.SLIME

enum WeaponType { SWORD, BOW, STAFF }
export(WeaponType) var weapon = WeaponType.SWORD

💡 提示:导出枚举后,Inspector 中会出现下拉菜单,方便设计师调整。

位标志(Bit Flags)

位标志用单个整数中的每一位来表示不同的开关状态。

# 定义位标志
enum Flag {
    VISIBLE   = 0b0001,  # 1
    COLLIDABLE = 0b0010,  # 2
    DAMAGEABLE = 0b0100,  # 4
    INTERACTIVE = 0b1000, # 8
}

var flags: int = 0

# 设置标志(位或)
flags |= Flag.VISIBLE
flags |= Flag.COLLIDABLE
# flags = 0b0011 = 3

# 检查标志(位与)
if flags & Flag.VISIBLE:
    print("可见")

if flags & Flag.DAMAGEABLE:
    print("可受伤")  # 不会执行

# 清除标志
flags &= ~Flag.VISIBLE
# flags = 0b0010 = 2

# 切换标志(位异或)
flags ^= Flag.DAMAGEABLE  # 开启
flags ^= Flag.DAMAGEABLE  # 关闭

# 检查是否包含所有指定标志
var required = Flag.VISIBLE | Flag.COLLIDABLE
if (flags & required) == required:
    print("包含所有必要标志")

实际应用:碰撞层

# Godot 的碰撞层/掩码本质上就是位标志
# Layer 1 = 0b0001 (1)
# Layer 2 = 0b0010 (2)
# Layer 3 = 0b0100 (4)

# 设置碰撞层(Layer 1 和 Layer 3)
collision_layer = 0b0101  # 5

# 设置碰撞掩码(检测 Layer 1 和 Layer 2)
collision_mask = 0b0011   # 3

向量类型

Vector2

# 创建方式
var v1 = Vector2(1, 2)
var v2 = Vector2(3.0, 4.0)
var v3 = Vector2.ZERO     # Vector2(0, 0)
var v4 = Vector2.ONE      # Vector2(1, 1)
var v5 = Vector2.UP       # Vector2(0, -1)
var v6 = Vector2.DOWN     # Vector2(0, 1)
var v7 = Vector2.LEFT     # Vector2(-1, 0)
var v8 = Vector2.RIGHT    # Vector2(1, 0)

# 分量访问
var pos = Vector2(100, 200)
print(pos.x)  # 100
print(pos.y)  # 200

# 数学运算
var a = Vector2(1, 2)
var b = Vector2(3, 4)

a + b           # Vector2(4, 6)
a - b           # Vector2(-2, -2)
a * 2.0         # Vector2(2, 4)
a.length()      # 2.236...
a.length_squared()  # 5(避免开方,性能更好)
a.normalized()  # 单位向量
a.distance_to(b)    # 距离
a.dot(b)        # 点积 = 1*3 + 2*4 = 11
a.angle()       # 与X轴的夹角(弧度)
a.angle_to(b)   # 两向量夹角
a.rotated(PI/2) # 旋转90度
a.linear_interpolate(b, 0.5)  # 线性插值,返回中间点

# 方向向量
var angle = deg2rad(45)
var dir = Vector2(cos(angle), sin(angle))  # 45度方向

Vector3

var pos3d = Vector3(1.0, 2.0, 3.0)
var zero = Vector3.ZERO
var up = Vector3.UP       # Vector3(0, 1, 0)
var forward = Vector3.FORWARD  # Vector3(0, 0, -1)(Godot 默认)

# 分量访问
pos3d.x  # 1.0
pos3d.y  # 2.0
pos3d.z  # 3.0

# 叉积(3D 特有,计算垂直向量)
var normal = Vector3.UP.cross(Vector3.RIGHT)  # Vector3(0, 0, -1)

实际应用:移动方向

extends KinematicBody2D

export var speed: float = 300.0
var velocity: Vector2 = Vector2.ZERO

func _physics_process(delta: float) -> void:
    var input_dir = Vector2.ZERO
    
    if Input.is_action_pressed("move_right"):
        input_dir.x += 1
    if Input.is_action_pressed("move_left"):
        input_dir.x -= 1
    if Input.is_action_pressed("move_down"):
        input_dir.y += 1
    if Input.is_action_pressed("move_up"):
        input_dir.y -= 1
    
    # 归一化确保斜向移动不会更快
    velocity = input_dir.normalized() * speed
    velocity = move_and_slide(velocity)

颜色类型(Color)

# 创建方式
var red = Color(1, 0, 0)            # RGB
var semi = Color(1, 0, 0, 0.5)      # RGBA(半透明)
var white = Color.white              # 预定义颜色
var black = Color.black
var transparent = Color.transparent  # 完全透明

# 十六进制
var gold = Color("#FFD700")
var from_hex = ColorN("red")

# 预定义颜色常量
Color.red       # (1, 0, 0)
Color.green     # (0, 1, 0)
Color.blue      # (0, 0, 1)
Color.yellow    # (1, 1, 0)
Color.cyan      # (0, 1, 1)
Color.magenta   # (1, 0, 1)
Color.orange    # (1, 0.65, 0)
Color.gray      # (0.75, 0.75, 0.75)

# 颜色操作
var c = Color.red
c.lightened(0.3)   # 变亮
c.darkened(0.3)    # 变暗
c.inverted()       # 反色
c.blend(Color.blue) # 混合颜色

# HSL 方式
var hsl_color = Color.from_hsv(0.5, 0.8, 0.9)  # 色相/饱和度/明度

# 应用颜色
$Sprite.modulate = Color.red       # 整体染红
$Sprite.self_modulate = Color(1,1,1,0.5)  # 半透明
$Label.add_color_override("font_color", Color.yellow)

Transform2D 和 Transform

Transform2D

var t = Transform2D.IDENTITY

# 包含三个 Vector2:
# t.x   - X轴方向(通常为 (1,0) 或旋转后的方向)
# t.y   - Y轴方向(通常为 (0,1) 或旋转后的方向)
# t.origin - 位移/位置

# 从位移、旋转、缩放创建
var transform = Transform2D()
transform.origin = Vector2(100, 200)  # 位置
transform = transform.rotated(deg2rad(45))  # 旋转45度

# 节点的 transform 属性
var player_transform = $Player.global_transform
var player_pos = player_transform.origin

Transform(3D)

# Transform 包含 Basis(3x3旋转/缩放矩阵)和 origin(位置)
var t3d = Transform.IDENTITY

# 获取 Basis
var basis = t3d.basis
var forward = basis.z  # 前方向
var up = basis.y       # 上方向
var right = basis.x    # 右方向

Resource 类型

Resource 是 Godot 中所有数据资产的基类。

# 常见 Resource 类型
# Texture, AudioStream, Script, PackedScene, Material, Shape2D, etc.

# 预加载(编译时加载,适合小资源)
var bullet_scene = preload("res://scenes/Bullet.tscn")
var explosion_sound = preload("res://assets/audio/explosion.ogg")

# 动态加载(运行时加载,适合大资源)
var level_data = load("res://data/level_01.tres")

# 检查资源是否存在
if ResourceLoader.exists("res://scenes/Boss.tscn"):
    var boss_scene = load("res://scenes/Boss.tscn")

preload vs load

特性preloadload
加载时机脚本加载时(编译期)执行到该行时(运行时)
性能🟢 更快(已缓存)🟡 较慢(首次加载)
错误检测🟢 编译期检查🟡 运行时检查
适用场景必需的小资源大资源、按需加载

⚠️ 注意preload 中的路径必须是常量字符串,不能用变量。

export 变量导出

export 使得变量在 Inspector 中可见和可编辑。

基本导出

# 自动推断类型
export var health = 100

# 显式类型导出
export var speed: float = 200.0
export var name: String = "Player"
export var is_active: bool = true
export var color: Color = Color.white
export var texture: Texture
export var scene: PackedScene

带范围的导出

# float 范围导出(滑块)
export(float, 0.0, 100.0) var percentage = 50.0
export(float, 0.0, 100.0, 5.0) var step_value = 50.0  # 步进 5

# int 范围导出
export(int, 1, 99) var level = 1
export(int, 1, 10, 2) var odd_value = 5  # 步进 2

# 带 exp 标记的范围(指数滑块)
export(float, 0, 10000, exp) var large_range = 100.0

枚举与标记导出

# 枚举导出(下拉菜单)
export(Array, String) var tags = ["enemy", "boss"]

# 位标志导出(多选)
export(int, FLAGS, "Fire", "Water", "Earth", "Wind") var elements = 0
# 在 Inspector 中可以多选勾选

# 带提示字符串的导出
export(String, "Easy", "Normal", "Hard") var difficulty = "Normal"

数组与资源导出

# 数组导出
export(Array) var items = []
export(Array, int) var scores = []
export(Array, Texture) var sprites = []
export(Array, PackedScene) var enemy_scenes = []

# 文件路径导出
export(String, FILE, "*.tscn") var scene_path = ""
export(String, DIR) var asset_dir = ""
export(String, FILE, "*.png,*.jpg") var image_path = ""

# 资源导出
export(Texture) var sprite_texture
export(AudioStream) var sound_effect
export(Shader) var custom_shader

导出元数据

# 使用 hint_string 限制
export(int, "Red", "Green", "Blue") var color_choice = 0

# 多行文本
export(String, MULTILINE) var description = ""

# 颜色拾取器
export(Color, RGB) var tint = Color.white

@onready(Godot 3 的 onready)

onready 使得变量在 _ready() 被调用时自动初始化。

extends Node2D

# 声明时无法访问子节点(此时子节点还未加入场景树)
# var sprite = $Sprite  # ❌ 错误!

# onready 在 _ready 之前自动赋值
onready var sprite = $Sprite              # 等价于在 _ready 中赋值
onready var camera = $Camera2D
onready var collision = $CollisionShape2D
onready var anim_player = $AnimationPlayer
onready var ui = $CanvasLayer/UI

# 等价写法
var sprite2
func _ready() -> void:
    sprite2 = $Sprite

💡 提示onready 是 Godot 的语法糖,仅在 extends Node 及其子类的脚本中有效。

get_node 与 $ 语法

访问子节点

extends Node2D

# $ 是 get_node() 的简写
var child = $ChildNode           # 相对路径
var child2 = get_node("ChildNode")  # 等价写法

# 访问孙子节点
var grandchild = $Child/GrandChild

# 访问兄弟节点(使用 ..)
var sibling = get_node("../SiblingNode")

# 使用绝对路径(从场景树根开始)
var ui_node = get_node("/root/Main/UI")

使用 Unique Name

# 在 Inspector 中勾选节点的 "Access as Unique Name"(齿轮图标)
# 之后可以用 % 访问,无论当前节点位置
var player = %Player
var enemy_spawner = %EnemySpawner

安全访问(可能返回 null)

# get_node_or_null 避免崩溃
var node = get_node_or_null("MaybeNotExist")
if node:
    node.do_something()
else:
    print("节点不存在")

# $ 简写在节点不存在时会报错
var bad = $NotExist  # ❌ 运行时错误!

等待节点就绪

func _ready() -> void:
    # 方法1:使用 yield 等待子节点就绪
    yield(get_tree(), "idle_frame")
    
    # 方法2:使用 call_deferred
    call_deferred("_init_after_ready")

类型转换(as / is)

is - 类型检查

func _on_body_entered(body: Node) -> void:
    # 检查类型
    if body is KinematicBody2D:
        print("是运动学体")
    elif body is RigidBody2D:
        print("是刚体")
    elif body is Area2D:
        print("是区域")
    
    # 检查自定义类
    if body is Player:
        body.take_damage(10)

as - 类型转换

func _on_area_entered(area: Area2D) -> void:
    var enemy = area as Enemy
    if enemy:
        enemy.take_damage(20)
        print("命中敌人!")
    
    # 安全转换(失败返回 null)
    var player = get_node("Player") as Player
    if player:
        player.heal(50)

类型注解与检查

# 使用类型注解,编辑器会提供更好的补全
var player: Player = $Player as Player
var enemies: Array = get_tree().get_nodes_in_group("enemies")

# 函数返回值类型
func get_player() -> Player:
    return $Player as Player

# 获取所有敌人并检查类型
func damage_all_enemies(amount: int) -> void:
    for node in get_tree().get_nodes_in_group("enemies"):
        if node.has_method("take_damage"):
            node.take_damage(amount)

⚠️ 注意:类型转换不会改变对象本身,as 仅是告诉编译器"我现在当它是这个类型"。

游戏开发场景

场景:角色属性系统

RPG 游戏中,角色有多种属性,需要灵活地管理、读取和修改。

extends Node

# 使用枚举定义属性类型
enum Stat { HP, MP, ATTACK, DEFENSE, SPEED }

# 基础属性字典
var base_stats: Dictionary = {
    Stat.HP: 100,
    Stat.MP: 50,
    Stat.ATTACK: 20,
    Stat.DEFENSE: 15,
    Stat.SPEED: 10,
}

# 当前属性(含加成)
var current_stats: Dictionary = {}

# 装备加成
var equipment_bonus: Dictionary = {}

# 等级
export var level: int = 1

func _ready() -> void:
    recalculate_stats()

func recalculate_stats() -> void:
    for stat in base_stats:
        current_stats[stat] = base_stats[stat] + equipment_bonus.get(stat, 0)
    emit_signal("stats_updated", current_stats)

func get_stat(stat_type: int) -> int:
    return current_stats.get(stat_type, 0)

func apply_equipment(bonus: Dictionary) -> void:
    for stat in bonus:
        equipment_bonus[stat] = equipment_bonus.get(stat, 0) + bonus[stat]
    recalculate_stats()

func remove_equipment(bonus: Dictionary) -> void:
    for stat in bonus:
        equipment_bonus[stat] = equipment_bonus.get(stat, 0) - bonus[stat]
    recalculate_stats()

signal stats_updated(new_stats: Dictionary)

扩展阅读