第一部分 06:GDScript 基础语法
Godot 入门笔记|第一部分 06:GDScript 基础语法
这一章开始讲 GDScript。
GDScript 是 Godot 自带的脚本语言,语法有点像 Python,但它是专门为 Godot 场景、节点、资源、信号这些系统设计的。官方文档里也明确把变量、常量、函数、类型、注解、数组、字典等都作为 GDScript 的核心基础内容。(Godot Engine documentation)
你可以先这样理解:
GDScript ≈ Godot 世界里的 JavaScript / TypeScript
但它更贴近 Godot 的节点系统,比如:
extends CharacterBody2D
@export var speed: float = 120.0
@onready var sprite: Sprite2D = $Sprite2D
func _physics_process(delta):
velocity = Vector2.RIGHT * speed
move_and_slide()
这段代码里就包含了很多 Godot 常见语法:
extends:继承某个节点类型
@export:暴露变量到 Inspector
@onready:等节点 ready 后再初始化
var:变量
float:类型
func:函数
Vector2:二维向量
$Sprite2D:获取子节点
1. GDScript 文件是什么
GDScript 文件后缀是:
.gd
比如:
player.gd
chest.gd
npc.gd
weather_manager.gd
inventory.gd
一个脚本通常挂在一个节点上。
比如:
Player CharacterBody2D
└── player.gd
脚本开头通常是:
extends CharacterBody2D
意思是:
这个脚本继承 CharacterBody2D
这个脚本要挂在 CharacterBody2D 或它的子类节点上
2. extends:继承节点类型
它是什么
extends 表示当前脚本继承自哪个类。
比如:
extends Node2D
表示这个脚本拥有 Node2D 的能力。
如果写:
extends CharacterBody2D
表示这个脚本拥有 CharacterBody2D 的能力,比如:
velocity
move_and_slide()
is_on_floor()
如果写:
extends Control
表示这个脚本是 UI 控件逻辑,可以使用 UI 相关属性,比如:
size
position
visible
mouse_filter
GDScript 官方参考中也大量使用 extends 来声明脚本继承的基础类,这是 Godot 脚本绑定节点能力的核心写法之一。(Godot Engine documentation)
前端类比
有点像 TypeScript 里的:
class Player extends CharacterBody2D {}
但 Godot 里你一般不会直接写 class Player,而是:
extends CharacterBody2D
因为这个脚本本身就是挂在节点上的。
常见写法
玩家:
extends CharacterBody2D
地图控制器:
extends Node2D
UI 面板:
extends Control
游戏管理器:
extends Node
宝箱:
extends StaticBody2D
检测区域:
extends Area2D
3. 注释
GDScript 注释用 #。
# 这是单行注释
var speed = 120
没有 JS 里的这种多行注释:
/*
多行注释
*/
GDScript 通常就是每行前面写 #:
# 这里是玩家移动速度
# 单位是像素每秒
var speed = 120
4. var:变量
基础写法
var health = 100
var speed = 120.0
var player_name = "勇者"
var is_dead = false
变量可以被重新赋值:
var health = 100
health = 80
health = 50
前端类比
有点像 JavaScript 的:
let health = 100
常见变量类型
var hp = 100
var speed = 120.0
var name = "村民"
var is_opened = false
var position = Vector2(100, 200)
对应含义:
int:整数
float:小数
String:字符串
bool:布尔值
Vector2:二维向量
5. 类型标注
GDScript 可以不写类型,也可以写类型。
不写类型
var health = 100
var name = "Player"
Godot 会自己推断。
写类型
var health: int = 100
var speed: float = 120.0
var player_name: String = "Player"
var is_dead: bool = false
var direction: Vector2 = Vector2.ZERO
官方静态类型文档说明,GDScript 的静态类型可以用于变量、常量、函数、参数和返回值;使用类型能帮助减少错误,并改善编辑器体验。(Godot Engine documentation)
推荐你怎么写
入门阶段,我建议重要变量写类型:
var health: int = 100
var speed: float = 120.0
var is_moving: bool = false
这样编辑器补全更好,也更容易看懂。
尤其你有前端 TypeScript 背景,这种写法会比较舒服:
var speed: float = 120.0
类似:
let speed: number = 120
6. := 类型推断
GDScript 里还有一种写法:
var speed := 120.0
意思是:
根据右边的值推断类型,并锁定这个变量类型
比如:
var name := "Player"
Godot 会推断 name 是 String。
之后你不能随便把它改成数字:
name = 123 # 不合适
和普通 var 的区别
var value = 100
更宽松。
var value := 100
更偏静态类型。
推荐用法
你刚开始可以优先写显式类型:
var speed: float = 120.0
等熟了再多用:
var speed := 120.0
7. const:常量
常量用 const。
const MAX_HEALTH = 100
const MOVE_SPEED = 120.0
const PLAYER_SCENE = preload("res://scenes/player.tscn")
常量定义后不能重新赋值。
const MAX_HEALTH = 100
MAX_HEALTH = 200 # 不应该这样
前端类比
类似 JS / TS:
const MAX_HEALTH = 100
常见用途
固定数值:
const TILE_SIZE = 16
const SAVE_FILE_PATH = "user://save.json"
固定资源:
const SLIME_SCENE = preload("res://scenes/enemies/slime.tscn")
固定方向:
const DIR_DOWN = Vector2(0, 1)
const DIR_UP = Vector2(0, -1)
8. 基本数据类型
int:整数
var gold: int = 100
var level: int = 1
var damage: int = 10
适合:
金币
等级
血量
数量
伤害值
float:小数
var speed: float = 120.0
var cooldown: float = 1.5
var time_left: float = 10.0
适合:
速度
冷却时间
倒计时
透明度
缩放比例
String:字符串
var player_name: String = "勇者"
var item_id: String = "potion"
var dialog_text: String = "你好,旅行者。"
适合:
名字
ID
对话文本
路径
提示文字
bool:布尔值
var is_dead: bool = false
var is_opened: bool = false
var player_inside: bool = false
适合:
是否死亡
是否打开
是否在范围内
是否正在移动
是否暂停
9. Vector2:二维向量
这个在 2D 游戏里超级常见。
var direction: Vector2 = Vector2.ZERO
var spawn_position: Vector2 = Vector2(100, 200)
Vector2 表示二维数值:
x
y
比如:
Vector2(100, 200)
意思是:
x = 100
y = 200
常见内置值
Vector2.ZERO # Vector2(0, 0)
Vector2.RIGHT # Vector2(1, 0)
Vector2.LEFT # Vector2(-1, 0)
Vector2.DOWN # Vector2(0, 1)
Vector2.UP # Vector2(0, -1)
移动方向例子
var direction: Vector2 = Vector2.ZERO
if Input.is_action_pressed("move_right"):
direction.x += 1
if Input.is_action_pressed("move_left"):
direction.x -= 1
if Input.is_action_pressed("move_down"):
direction.y += 1
if Input.is_action_pressed("move_up"):
direction.y -= 1
normalized
direction = direction.normalized()
意思是把方向长度归一化。
用于避免斜向移动更快。
velocity = direction.normalized() * speed
10. String 字符串
基础写法
var name: String = "Player"
var message: String = "你好"
字符串拼接
var player_name = "勇者"
var text = "你好," + player_name
数字要转成字符串:
var gold = 100
var text = "金币:" + str(gold)
字符串格式化
var gold = 100
var text = "金币:%s" % gold
多个值:
var name = "勇者"
var level = 5
var text = "角色:%s,等级:%s" % [name, level]
常见用途
$GoldLabel.text = "金币:" + str(gold)
$HealthLabel.text = "生命:" + str(health)
11. func:函数
基础写法
func say_hello():
print("Hello")
调用:
say_hello()
带参数
func take_damage(amount):
health -= amount
调用:
take_damage(10)
参数写类型
func take_damage(amount: int):
health -= amount
返回值
func get_health() -> int:
return health
没有返回值
func open_chest() -> void:
print("打开宝箱")
-> void 表示这个函数不返回值。
推荐写法
func take_damage(amount: int) -> void:
health -= amount
func get_health_percent() -> float:
return float(health) / float(max_health)
这对你这种前端开发者会更友好,因为类型信息清楚。
12. 缩进很重要
GDScript 用缩进表示代码块。
类似 Python。
func _ready():
print("第一行")
print("第二行")
下面这样就不对:
func _ready():
print("缩进错误")
if 也靠缩进
if health <= 0:
die()
函数体也靠缩进
func die():
print("玩家死亡")
queue_free()
这个和 JS 不一样,JS 用 {},GDScript 用缩进。
13. if 条件判断
基础写法
if health <= 0:
die()
if / else
if health <= 0:
die()
else:
print("还活着")
if / elif / else
if gold >= 100:
print("可以买高级装备")
elif gold >= 50:
print("可以买普通装备")
else:
print("金币不够")
多条件
if player_inside and Input.is_action_pressed("interact"):
open()
if is_dead or health <= 0:
die()
if not is_opened:
open()
对应 JS:
if (!isOpened) {
open()
}
GDScript 写:
if not is_opened:
open()
14. match:类似 switch
GDScript 的 match 类似 JS 里的 switch。
match direction:
"up":
animation_player.play("walk_up")
"down":
animation_player.play("walk_down")
"left":
animation_player.play("walk_left")
"right":
animation_player.play("walk_right")
_:
animation_player.play("idle")
_ 表示默认情况。
前端类比
类似:
switch (direction) {
case "up":
break
default:
break
}
RPG 例子:根据物品类型处理
func use_item(item_type: String) -> void:
match item_type:
"potion":
heal(30)
"mana":
restore_mana(20)
"key":
unlock_door()
_:
print("不能使用这个物品")
15. for 循环
遍历数字范围
for i in range(5):
print(i)
输出:
0
1
2
3
4
遍历数组
var items = ["potion", "key", "coin"]
for item in items:
print(item)
遍历节点
比如:
Enemies Node2D
├── Slime
├── Bat
└── Goblin
代码:
for enemy in $Enemies.get_children():
enemy.queue_free()
意思是删除所有敌人。
16. while 循环
var count = 0
while count < 5:
print(count)
count += 1
不过游戏逻辑里要小心 while,不要写死循环。
危险写法:
while true:
print("无限循环")
这会卡住游戏。
在游戏开发里,很多持续逻辑更适合放:
_process
_physics_process
Timer
而不是手写死循环。
17. Array:数组
基础写法
var items = ["potion", "key", "coin"]
访问:
print(items[0]) # potion
添加:
items.append("sword")
删除:
items.erase("key")
长度:
print(items.size())
Godot 的 Array 是常用容器类型;官方 Array 文档也说明 GDScript 支持 typed array,也就是可以声明只包含特定类型元素的数组。(Godot Engine documentation)
写类型
var item_ids: Array[String] = ["potion", "key", "coin"]
var damages: Array[int] = [10, 20, 30]
背包例子
var inventory: Array[String] = []
func add_item(item_id: String) -> void:
inventory.append(item_id)
func remove_item(item_id: String) -> void:
inventory.erase(item_id)
func has_item(item_id: String) -> bool:
return inventory.has(item_id)
18. Dictionary:字典
Dictionary 类似 JS 里的对象,也类似 Map。
官方 Dictionary 文档说明,Dictionary 是通过唯一 key 引用 value 的关联容器,并且会保留插入顺序。(Godot Engine documentation)
基础写法
var player_data = {
"name": "勇者",
"level": 1,
"gold": 100
}
读取:
print(player_data["name"])
修改:
player_data["gold"] = 200
新增:
player_data["hp"] = 100
判断 key 是否存在:
if player_data.has("gold"):
print(player_data["gold"])
前端类比
类似:
const playerData = {
name: "勇者",
level: 1,
gold: 100
}
物品配置例子
var item_config = {
"potion": {
"name": "生命药水",
"heal": 30,
"price": 50
},
"mana_potion": {
"name": "魔力药水",
"mana": 20,
"price": 60
}
}
读取:
var potion = item_config["potion"]
print(potion["name"])
print(potion["heal"])
存档数据例子
var save_data = {
"player_position": [100, 200],
"gold": 300,
"inventory": ["potion", "key"],
"current_day": 3
}
19. Array 和 Dictionary 怎么选
用 Array 的情况
数据是一组列表:
背包物品列表
敌人列表
任务列表
对话句子列表
技能列表
例子:
var quests = ["find_sword", "talk_to_elder", "open_gate"]
用 Dictionary 的情况
数据需要 key-value:
玩家数据
物品配置
任务状态
NPC 对话配置
存档数据
例子:
var quest_status = {
"find_sword": true,
"talk_to_elder": false
}
20. null:空值
null 表示没有值。
var target = null
判断:
if target == null:
return
或者:
if target != null:
target.queue_free()
常见场景
var current_npc = null
func interact():
if current_npc == null:
return
current_npc.talk()
21. @export:暴露到 Inspector
它是什么
@export 可以把脚本变量显示到 Godot 的 Inspector 面板里。
官方文档说明,被导出的 class member 会保存到它所附加的资源或场景中,并且可以在属性编辑器里编辑;导出使用 @export 注解。(Godot Engine documentation)
基础写法
@export var speed: float = 120.0
@export var max_health: int = 100
@export var npc_name: String = "村民"
这样你选中挂了脚本的节点,就能在右侧 Inspector 里看到这些属性。
前端类比
有点像组件 props:
<Player speed={120} maxHealth={100} />
但 Godot 里是通过 Inspector 配置。
常见用途
玩家速度:
@export var speed: float = 120.0
怪物伤害:
@export var damage: int = 10
NPC 名字:
@export var npc_name: String = "村民"
宝箱奖励:
@export var reward_gold: int = 100
场景引用:
@export var bullet_scene: PackedScene
节点引用:
@export var target: Node2D
为什么 @export 很重要
不用 @export,你每次调数值都要改代码:
var speed = 120.0
用了 @export,可以在编辑器里直接调:
@export var speed: float = 120.0
这对游戏开发非常重要,因为很多参数都需要反复调:
移动速度
攻击伤害
怪物血量
交互距离
镜头缩放
天气变化速度
NPC 巡逻范围
22. @export_range:限制数值范围
比如玩家速度只允许 0 到 500:
@export_range(0, 500) var speed: float = 120.0
比如音量 0 到 1:
@export_range(0, 1) var volume: float = 0.5
这样 Inspector 里会更好调。
常见例子
@export_range(0, 100) var max_health: int = 100
@export_range(0, 500) var move_speed: float = 120.0
@export_range(0, 10) var fade_duration: float = 1.0
23. @onready:等 ready 后初始化
它是什么
@onready 表示这个变量等节点进入场景树并 ready 时再初始化。
官方 GDScript 文档说明,场景只有进入活动 SceneTree 后才会被设置好,子节点通常要到 Node._ready() 调用时才能获取;@onready 可以把成员变量的初始化推迟到 _ready() 调用时。(Godot Engine documentation)
常见写法
@onready var sprite: Sprite2D = $Sprite2D
@onready var animation_player: AnimationPlayer = $AnimationPlayer
@onready var collision_shape: CollisionShape2D = $CollisionShape2D
等价写法
这个:
@onready var sprite: Sprite2D = $Sprite2D
大概等价于:
var sprite: Sprite2D
func _ready():
sprite = $Sprite2D
为什么需要它
因为脚本刚加载的时候,子节点可能还没准备好。
你不能太早获取:
var sprite = $Sprite2D
更稳的是:
@onready var sprite = $Sprite2D
常见用途
玩家:
@onready var body_sprite: Sprite2D = $BodySprite
@onready var animation_player: AnimationPlayer = $AnimationPlayer
宝箱:
@onready var interact_area: Area2D = $InteractArea
@onready var animation_player: AnimationPlayer = $AnimationPlayer
HUD:
@onready var health_label: Label = $HealthLabel
@onready var gold_label: Label = $GoldLabel
24. @export 和 @onready 的区别
这两个经常一起出现,但作用完全不同。
@export:
把变量暴露给 Inspector,让你在编辑器里配置。
@onready:
延迟初始化变量,等节点 ready 后再赋值。
@export 例子
@export var speed: float = 120.0
意思是:
这个速度可以在 Inspector 里改
@onready 例子
@onready var sprite: Sprite2D = $Sprite2D
意思是:
等节点准备好后,获取 Sprite2D 子节点
常见组合
extends CharacterBody2D
@export var speed: float = 120.0
@onready var body_sprite: Sprite2D = $BodySprite
含义:
speed 是外部可配置参数
body_sprite 是内部子节点引用
25. class_name:给脚本起全局类名
它是什么
class_name 可以给脚本注册一个类名。
class_name Player
extends CharacterBody2D
之后其他脚本里就可以用 Player 这个类型。
例子
player.gd:
class_name Player
extends CharacterBody2D
@export var speed: float = 120.0
其他脚本:
@export var player: Player
或者:
func set_player(value: Player) -> void:
player = value
什么时候用
适合比较核心、会被其他地方引用的脚本:
Player
Enemy
ItemData
Inventory
QuestData
DialogueData
WeatherManager
TimeManager
刚开始不用每个脚本都写 class_name。
26. self:当前对象
self 表示当前脚本所在对象。
self.position = Vector2(100, 200)
通常可以省略:
position = Vector2(100, 200)
这两个大多数情况下等价。
什么时候会用 self
当参数名和成员变量名冲突时:
var health: int = 100
func set_health health:
self.health = health
不过更推荐换个参数名:
var health: int = 100
func set_health(value: int) -> void:
health = value
27. get_node 和 $ 简写
get_node
var sprite = get_node("Sprite2D")
$ 简写
var sprite = $Sprite2D
这两个基本等价。
常见写法
@onready var sprite: Sprite2D = $Sprite2D
@onready var health_label: Label = $UI/HealthLabel
注意路径
如果结构是:
Player
└── Visual
└── BodySprite
你要写:
@onready var body_sprite: Sprite2D = $Visual/BodySprite
不能写:
@onready var body_sprite: Sprite2D = $BodySprite
28. as:类型转换
有时候你拿到的是一个通用节点,需要转换成具体类型。
var sprite = $Sprite2D as Sprite2D
或者:
func _on_body_entered(body):
var player = body as Player
if player == null:
return
player.take_damage(10)
这意思是:
尝试把 body 当成 Player
如果不是 Player,就得到 null
29. is:类型判断
if body is Player:
print("进入的是玩家")
例子:
func _on_body_entered(body):
if body is Player:
body.take_damage(10)
这个比判断名字更稳。
不推荐长期这样写:
if body.name == "Player":
pass
更推荐:
if body is Player:
pass
或者用 group,后面会讲。
30. await:等待
await 用来等待信号或异步结果。
常见例子:等待 1 秒。
await get_tree().create_timer(1.0).timeout
print("1 秒后执行")
受伤闪红:
func flash_damage() -> void:
$BodySprite.modulate = Color(1, 0.3, 0.3)
await get_tree().create_timer(0.1).timeout
$BodySprite.modulate = Color(1, 1, 1)
等待动画播放结束:
$AnimationPlayer.play("open")
await $AnimationPlayer.animation_finished
print("动画播放完了")
前端类比
有点像 JS 的:
await sleep(1000)
但 Godot 的 await 经常等待的是信号,比如:
Timer.timeout
AnimationPlayer.animation_finished
31. signal:自定义信号
虽然信号前面讲过,但这里放到语法里再看一次。
定义信号
signal health_changed(value: int)
发出信号
health_changed.emit(health)
连接信号
player.health_changed.connect(_on_player_health_changed)
完整例子
extends Node
signal health_changed(value: int)
var health: int = 100
func take_damage(amount: int) -> void:
health -= amount
health_changed.emit(health)
UI 监听:
func _ready():
player.health_changed.connect(_on_player_health_changed)
func _on_player_health_changed(value: int) -> void:
$HealthLabel.text = "生命:" + str(value)
32. enum:枚举
枚举适合表示有限状态。
enum PlayerState {
IDLE,
WALK,
ATTACK,
DEAD
}
使用:
var state: PlayerState = PlayerState.IDLE
判断:
if state == PlayerState.WALK:
print("正在移动")
RPG 例子
enum WeatherType {
SUNNY,
RAINY,
SNOWY,
FOGGY
}
var current_weather: WeatherType = WeatherType.SUNNY
搭配 match:
match current_weather:
WeatherType.SUNNY:
print("晴天")
WeatherType.RAINY:
print("雨天")
WeatherType.SNOWY:
print("雪天")
WeatherType.FOGGY:
print("雾天")
33. preload 和 load
preload
脚本加载时就加载资源。
const SLIME_SCENE = preload("res://scenes/enemies/slime.tscn")
适合固定会用到的资源。
load
运行到这一行时再加载资源。
var scene = load("res://scenes/enemies/slime.tscn")
适合根据条件动态加载。
常见例子
const CHEST_SCENE = preload("res://scenes/items/chest.tscn")
func spawn_chest():
var chest = CHEST_SCENE.instantiate()
add_child(chest)
34. instantiate:实例化场景
preload 或 load 得到的是资源,不是真正的节点。
const SLIME_SCENE = preload("res://scenes/enemies/slime.tscn")
要生成一个真正的节点实例,需要:
var slime = SLIME_SCENE.instantiate()
然后加入场景树:
add_child(slime)
完整:
const SLIME_SCENE = preload("res://scenes/enemies/slime.tscn")
func spawn_slime(pos: Vector2) -> void:
var slime = SLIME_SCENE.instantiate()
add_child(slime)
slime.global_position = pos
35. queue_free:删除节点
queue_free()
删除当前节点。
比如敌人死亡:
func die() -> void:
queue_free()
删除别的节点:
enemy.queue_free()
常见用途
子弹命中后删除
敌人死亡后删除
特效播放完删除
拾取物被拿走后删除
临时 UI 关闭后删除
36. print:调试输出
print("Hello")
打印变量:
print("生命:", health)
打印位置:
print("玩家位置:", global_position)
调试时非常有用。
你刚开始做 Godot,不要吝啬 print。
比如:
func _ready():
print("Player ready")
func _physics_process(delta):
print("velocity:", velocity)
不过移动逻辑里每帧 print 会刷屏,调试完记得删。
37. 一个完整玩家脚本例子
class_name Player
extends CharacterBody2D
signal health_changed(value: int)
@export var speed: float = 120.0
@export var max_health: int = 100
@onready var body_sprite: Sprite2D = $BodySprite
@onready var animation_player: AnimationPlayer = $AnimationPlayer
var health: int
var last_direction: Vector2 = Vector2.DOWN
func _ready() -> void:
health = max_health
health_changed.emit(health)
animation_player.play("idle_down")
func _physics_process(delta: float) -> void:
var direction := get_move_direction()
if direction != Vector2.ZERO:
last_direction = direction
velocity = direction.normalized() * speed
play_walk_animation(direction)
else:
velocity = Vector2.ZERO
play_idle_animation(last_direction)
move_and_slide()
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("interact"):
try_interact()
func get_move_direction() -> Vector2:
var direction := Vector2.ZERO
if Input.is_action_pressed("move_right"):
direction.x += 1
if Input.is_action_pressed("move_left"):
direction.x -= 1
if Input.is_action_pressed("move_down"):
direction.y += 1
if Input.is_action_pressed("move_up"):
direction.y -= 1
return direction
func take_damage(amount: int) -> void:
health -= amount
health = max(health, 0)
health_changed.emit(health)
if health <= 0:
die()
func die() -> void:
print("玩家死亡")
set_physics_process(false)
animation_player.play("die")
func try_interact() -> void:
print("尝试交互")
func play_walk_animation(direction: Vector2) -> void:
if abs(direction.x) > abs(direction.y):
if direction.x > 0:
animation_player.play("walk_right")
else:
animation_player.play("walk_left")
else:
if direction.y > 0:
animation_player.play("walk_down")
else:
animation_player.play("walk_up")
func play_idle_animation(direction: Vector2) -> void:
if abs(direction.x) > abs(direction.y):
if direction.x > 0:
animation_player.play("idle_right")
else:
animation_player.play("idle_left")
else:
if direction.y > 0:
animation_player.play("idle_down")
else:
animation_player.play("idle_up")
这个脚本包含了:
class_name
extends
signal
@export
@onready
var
func
if
return
Vector2
Input
move_and_slide
print
也就是你现在最常见的一批 GDScript 语法。
38. 一个完整宝箱脚本例子
class_name Chest
extends StaticBody2D
signal opened(reward_gold: int)
@export var reward_gold: int = 100
@onready var animation_player: AnimationPlayer = $AnimationPlayer
@onready var interact_shape: CollisionShape2D = $InteractArea/CollisionShape2D
var is_opened: bool = false
var player_inside: bool = false
func _ready() -> void:
$InteractArea.body_entered.connect(_on_body_entered)
$InteractArea.body_exited.connect(_on_body_exited)
func _unhandled_input(event: InputEvent) -> void:
if event.is_action_pressed("interact") and player_inside:
open()
func open() -> void:
if is_opened:
return
is_opened = true
animation_player.play("open")
interact_shape.set_deferred("disabled", true)
opened.emit(reward_gold)
func _on_body_entered(body: Node2D) -> void:
if body is Player:
player_inside = true
func _on_body_exited(body: Node2D) -> void:
if body is Player:
player_inside = false
这个脚本很典型:
@export 配置奖励金币
@onready 获取子节点
_ready 连接信号
_unhandled_input 处理交互键
open 执行打开逻辑
body_entered/body_exited 记录玩家是否在范围内
39. 一个完整 HUD 脚本例子
class_name HUD
extends Control
@onready var health_label: Label = $HealthLabel
@onready var gold_label: Label = $GoldLabel
@onready var inventory_button: Button = $InventoryButton
var gold: int = 0
func _ready() -> void:
inventory_button.pressed.connect(_on_inventory_button_pressed)
update_gold(0)
func bind_player(player: Player) -> void:
player.health_changed.connect(_on_player_health_changed)
func update_gold(value: int) -> void:
gold = value
gold_label.text = "金币:" + str(gold)
func _on_player_health_changed(value: int) -> void:
health_label.text = "生命:" + str(value)
func _on_inventory_button_pressed() -> void:
print("打开背包")
这个脚本里没有 _process,因为 HUD 不需要每帧刷新。
数据变化时再更新 UI,这个思路和前端状态更新很像。
40. GDScript 新手常见坑
坑 1:缩进错误
错误:
func _ready():
print("Hello")
正确:
func _ready():
print("Hello")
坑 2:忘记冒号
错误:
if health <= 0
die()
正确:
if health <= 0:
die()
函数也一样。
错误:
func die()
queue_free()
正确:
func die():
queue_free()
坑 3:把 = 和 == 搞混
赋值:
health = 100
判断:
if health == 100:
print("满血")
坑 4:在节点没 ready 时拿子节点
不推荐:
var sprite = $Sprite2D
推荐:
@onready var sprite: Sprite2D = $Sprite2D
坑 5:路径写错
结构:
Player
└── Visual
└── BodySprite
错误:
@onready var body_sprite = $BodySprite
正确:
@onready var body_sprite = $Visual/BodySprite
坑 6:函数只定义了,没有调用
func open():
print("打开")
这只是定义函数。
你还要调用:
open()
或者由信号触发。
坑 7:数组下标越界
var items = ["potion"]
print(items[5]) # 不行
访问前可以判断:
if items.size() > 5:
print(items[5])
坑 8:Dictionary key 不存在
var data = {
"gold": 100
}
print(data["level"]) # 可能出问题
更稳:
if data.has("level"):
print(data["level"])
或者:
print(data.get("level", 1))
41. 这一章你先记住这些
extends:声明脚本继承哪个节点类型
var:变量
const:常量
func:函数
if / elif / else:条件判断
match:类似 switch
for:循环
Array:数组列表
Dictionary:键值数据
@export:暴露变量到 Inspector
@onready:等 ready 后初始化变量
class_name:给脚本注册类型名
signal:声明信号
emit:发出信号
await:等待信号或计时器
preload:提前加载资源
instantiate:实例化场景
queue_free:删除节点
最重要的一句话:
GDScript 不是孤立写代码,而是围绕节点写逻辑:extends 决定节点能力,@export 负责外部配置,@onready 负责拿子节点,func 负责行为。如果您觉得这篇文章有帮助,请点个赞吧~
评论
请登录后发表评论
去登录