Godot 3 GDScript 教程 / 变量、类型与常量
变量、类型与常量
基本类型详解
GDScript 拥有丰富的内置类型,可以分为基本类型、容器类型、引擎类型三大类。
基本类型一览
| 类型 | 关键字 | 大小 | 默认值 | 示例 |
|---|---|---|---|---|
| 整数 | int | 64 位 | 0 | 42, -10, 0xFF, 0b1010 |
| 浮点数 | float | 64 位 | 0.0 | 3.14, -0.5, 1.2e3 |
| 布尔 | bool | 1 位 | false | true, 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
| 特性 | preload | load |
|---|---|---|
| 加载时机 | 脚本加载时(编译期) | 执行到该行时(运行时) |
| 性能 | 🟢 更快(已缓存) | 🟡 较慢(首次加载) |
| 错误检测 | 🟢 编译期检查 | 🟡 运行时检查 |
| 适用场景 | 必需的小资源 | 大资源、按需加载 |
⚠️ 注意: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)