第一部分 06:GDScript 基础语法

2026-05-19
423715 分钟
...

Godot 入门笔记|第一部分 06:GDScript 基础语法

这一章开始讲 GDScript。

GDScript 是 Godot 自带的脚本语言,语法有点像 Python,但它是专门为 Godot 场景、节点、资源、信号这些系统设计的。官方文档里也明确把变量、常量、函数、类型、注解、数组、字典等都作为 GDScript 的核心基础内容。(Godot Engine documentation)

你可以先这样理解:

GDScriptGodot 世界里的 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 会推断 nameString

之后你不能随便把它改成数字:

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:实例化场景

preloadload 得到的是资源,不是真正的节点。

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 负责行为

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
godot2026-07-27
Godot 4 常用 UI 节点详解
Godot 4 常用 UI 节点详解 在 Godot 4 中,UI 系统基于 Control(控件) 节点构建。所有 UI 节点都继承自 Control,形成一棵完整的 UI 树。与游戏引擎中常见的"Canvas + DOM"模式不同,Godot 的 UI 系统是声明式的——你在场景中搭好节点树,引擎自动完成布局计算。 一、布局系统:Container 家族 Container 是 Godot UI 的 骨架 。它决定了子节点的大小和位...
学习
godot2026-07-09
Godot 4 自动地形系统(AutoTileSet / Terrains)完全指南
前言 在 2D 游戏开发中,地形瓦片(tile)的拼接是一个绕不开的问题。想象一下:你有一片草地、一条河流、一段平台——如果每一块边缘、角落、过渡区域都要手动选择对应的瓦片图,工作量将是巨大的。 自动地形系统 就是为了解决这个问题而生的。 一、什么是自动地形(Autotiling)? 自动地形的核心思想很简单: 你只管画,引擎帮你选对瓦片 。 当你在 TileMap 上绘制地形时,引擎会自动检测每个瓦片的上下左右邻居,然后根据预设的规则...
学习
godot2026-06-25
Godot 中 zindex 和 ysort 的区别总结
在 Godot 2D 游戏开发中,角色、树木、怪物、地面、技能特效、UI 都需要正确的显示顺序。比如角色走到树前面时,角色应该挡住树;角色走到树后面时,树又应该挡住角色。 这种显示顺序主要和两个概念有关: 和 。 其中 用来手动控制图层顺序, 用来根据物体的 Y 坐标自动排序。 一句话理解 是手动分层。 是根据 Y 坐标自动排序。 简单来说: | 属性 | 作用 | 适合场景 | | | | | | | 数值越大,显示越靠前 | 地面、...
学习
godot2026-06-11
Tileset 资源图的标准和规范
一、什么是 Tileset 资源图 Tileset,中文通常叫“图块资源图”或“瓦片图”,是 2D 游戏中非常常见的一种地图资源组织方式。 简单来说,Tileset 就是把很多小图块按照固定尺寸排列在一张图片里。游戏引擎会按照固定的格子大小去切割这张图片,然后把每个小格子当成一个独立的地图块使用。 比如一个 32×32 像素的 Tileset 中,每一个 tile 都是 32×32 像素。地图编辑器或游戏引擎会按照 32×32 的网格,...
学习
godot2026-05-29
用户角色精灵图制作角色的完整流程
在 2D RPG 游戏里,角色通常不是用一张单独图片完成的,而是用一张“角色精灵图”来做。 所谓角色精灵图,通常是一张包含多个动作帧的大图。比如角色向下走有 4 帧,向左走有 4 帧,向右走有 4 帧,向上走有 4 帧。Godot 会根据这些帧不断切换图片,看起来角色就动起来了。 这篇文章主要介绍:拿到一张角色精灵图之后,如何在 Godot 中把它做成一个可以正常移动、播放动画、和地图产生遮挡关系的角色。 一、先理解角色精灵图是什么 角...
学习
godot2026-05-26
Godot 节点系统详细介绍
Godot 里最核心的东西不是“类”,也不是“组件”,而是 节点 Node 。 你可以把 Godot 的节点理解成: 在前端里,一个页面是由很多 DOM 元素组成的; 在 Godot 里,一个游戏场景是由很多 Node 节点组成的。 比如一个玩家角色,可能不是一个单独对象,而是这样的结构: 这里的 是根节点,下面挂着显示图片、播放动画、碰撞检测、摄像机、音效等子节点。 Godot 官方文档也把节点和场景放在一起讲:多个节点组成树状结构后...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录