第一部分 05:生命周期方法

2026-05-19
396214 分钟
...

这一章讲 Godot 写脚本时最常见的几个方法:

_ready()
_process(delta)
_physics_process(delta)
_input(event)
_unhandled_input(event)
_enter_tree()
_exit_tree()

你可以先用一句话理解:

生命周期方法 = Godot 在特定时机自动帮你调用的方法

它们不是你主动调用的,而是 Godot 引擎在节点进入场景、准备完成、每帧更新、物理更新、接收输入、离开场景时自动调用。

Godot 官方文档把 _process() 称为 idle processing,也就是每帧处理;_physics_process() 是 physics processing,用固定时间间隔运行,默认物理帧率是 60 次每秒,适合角色移动、碰撞等物理相关逻辑。(Godot Engine documentation)

1. 生命周期方法是什么

在前端里,你可能熟悉这些概念:

Vue:
onMounted
onUnmounted
watch
computed
事件监听

React:
useEffect
useLayoutEffect
onClick
组件卸载清理

Godot 里类似:

_ready             节点准备好了
_process           每一帧执行
_physics_process   每个物理帧执行
_input             收到输入事件
_unhandled_input   收到还没被 UI/其他节点处理的输入
_exit_tree         离开场景树

举个最简单的例子:

extends Node2D

func _ready():
    print("节点准备好了")

func _process(delta):
    print("每一帧都会执行")

你不用写:

_ready()
_process()

Godot 会自动调用。

2. 最常用的四个方法

入门阶段,先重点掌握这四个:

func _ready():
    pass

func _process(delta):
    pass

func _physics_process(delta):
    pass

func _input(event):
    pass

它们分别负责:

_ready初始化
_process普通每帧逻辑
_physics_process物理和移动逻辑
_input输入事件

最常见的错误就是:

角色移动放错地方
初始化代码写太早
每帧检测和一次性按键事件混用
UI 输入和游戏输入互相打架

这一章就是专门解决这些问题。

3. _ready:节点准备完成

它是什么

_ready() 会在节点进入场景树,并且它的子节点也准备好之后调用。

常见用途:

获取子节点
初始化变量
连接信号
设置初始 UI
播放默认动画
初始化玩家状态

Node 文档中提到,节点进入 SceneTree 后会有对应回调,_ready() 发生在节点及其子节点都准备完成之后。(Godot Engine documentation)

基础例子

extends Node2D

func _ready():
    print("我准备好了")

运行场景时,它会打印:

我准备好了

获取子节点

比如场景结构是:

Player CharacterBody2D
├── BodySprite Sprite2D
├── CollisionShape2D
└── AnimationPlayer

脚本:

extends CharacterBody2D

func _ready():
    $BodySprite.visible = true
    $AnimationPlayer.play("idle")

意思是:

节点准备好后显示玩家图片并播放 idle 动画

更推荐的写法:@onready

通常我们会这样写:

extends CharacterBody2D

@onready var body_sprite: Sprite2D = $BodySprite
@onready var animation_player: AnimationPlayer = $AnimationPlayer

func _ready():
    body_sprite.visible = true
    animation_player.play("idle")

@onready 的意思是:

等节点 ready 时再赋值

这很适合获取子节点。

前端类比

React 里你不会在 DOM 还没挂载时操作 DOM。

你会等组件挂载后:

useEffect(() => {
  // DOM 已经存在后再做事情
}, [])

Godot 里类似:

func _ready():
    # 子节点已经准备好可以安全访问

_ready 适合做什么

适合:

设置初始状态
连接信号
拿子节点引用
初始化 UI
读取配置
播放初始动画
初始化血量金币任务状态

比如:

extends Node

signal health_changed(value: int)

@export var max_health: int = 100

var health: int

func _ready():
    health = max_health
    health_changed.emit(health)

_ready 不适合做什么

不适合:

持续移动角色
每帧检测输入
不断刷新 UI
物理碰撞移动

因为 _ready() 只执行一次。

4. _process(delta):每一帧执行

它是什么

_process(delta) 会在每一帧执行。

比如游戏 60 FPS,它大概每秒执行 60 次。

如果游戏 144 FPS,它可能每秒执行 144 次。

Godot 文档说明,_process() 是 idle processing,会尽可能每一帧运行;执行频率取决于游戏运行的帧率。(Godot Engine documentation)

基础例子

extends Node2D

func _process(delta):
    rotation += 1.0 * delta

这个节点会持续旋转。

delta 是什么

delta 是距离上一帧过去了多少秒。

比如:

60 FPSdelta 大约是 0.016
30 FPSdelta 大约是 0.033

为什么需要 delta?

因为不同电脑帧率不同。

错误写法:

position.x += 10

意思是每帧移动 10 像素。

如果一台电脑 60 FPS:

每秒移动 600 像素

另一台电脑 144 FPS:

每秒移动 1440 像素

这就离谱了,玩家像被风油精灌了三瓶。

正确写法:

position.x += 100 * delta

意思是:

每秒移动 100 像素

不管帧率多少,速度都比较稳定。

_process 适合做什么

适合:

普通视觉更新
非物理动画
UI 数值刷新
倒计时显示
镜头平滑逻辑
天气视觉效果
鼠标悬浮效果
简单特效

例子:让云缓慢移动

extends Node2D

@export var cloud_speed: float = 20.0

func _process(delta):
    position.x += cloud_speed * delta

例子:倒计时显示

extends Label

var time_left: float = 10.0

func _process(delta):
    time_left -= delta
    text = "剩余时间:" + str(round(time_left))

_process 不适合做什么

不推荐把角色物理移动放这里:

func _process(delta):
    velocity = direction * speed
    move_and_slide()

角色移动、碰撞、物理判断,更推荐放到:

func _physics_process(delta):
    pass

5. _physics_process(delta):物理帧执行

它是什么

_physics_process(delta) 按固定时间间隔执行。

默认情况下,Godot 物理处理是每秒 60 次。这个固定频率适合让碰撞、角色移动、物理交互更稳定。(Godot Engine documentation)

基础例子

extends CharacterBody2D

@export var speed: float = 120.0

func _physics_process(delta):
    var direction = Vector2.RIGHT
    velocity = direction * speed
    move_and_slide()

这个角色会持续向右移动。

为什么角色移动放这里

因为角色移动通常涉及:

碰撞检测
墙体阻挡
滑动
地面判断
移动稳定性
物理交互

这些东西应该和物理系统同步。

Godot 的角色移动教程也会把玩家移动逻辑放在 _physics_process() 里,因为它专门用于物理相关代码,比如移动 kinematic/rigid body,并且按固定间隔更新。(Godot Engine documentation)

2D RPG 玩家移动标准写法

extends CharacterBody2D

@export var speed: float = 120.0

func _physics_process(delta):
    var direction = Vector2.ZERO

    if Input.is_action_pressed("ui_right"):
        direction.x += 1
    if Input.is_action_pressed("ui_left"):
        direction.x -= 1
    if Input.is_action_pressed("ui_down"):
        direction.y += 1
    if Input.is_action_pressed("ui_up"):
        direction.y -= 1

    velocity = direction.normalized() * speed
    move_and_slide()

这里的逻辑是:

1. 每个物理帧读取输入
2. 计算方向
3. 设置 velocity
4. 调用 move_and_slide()

为什么要 normalized

如果同时按右和下:

direction = Vector2(1, 1)

这个方向的长度比单独向右更长,会导致斜着走更快。

所以要:

direction.normalized()

它会把方向变成单位向量,保证上下左右和斜向移动速度一致。

_physics_process 适合做什么

适合:

玩家移动
NPC 移动
怪物追踪
子弹移动
碰撞相关逻辑
地面检测
攻击判定
物理相关状态更新

例子:怪物追玩家

extends CharacterBody2D

@export var speed: float = 80.0
@export var player: Node2D

func _physics_process(delta):
    if player == null:
        return

    var direction = (player.global_position - global_position).normalized()
    velocity = direction * speed
    move_and_slide()

_physics_process 不适合做什么

不太适合:

 UI 刷新
普通文字变化
不涉及物理的视觉动画
按钮点击响应
一次性按键触发

这些更适合 _process_input 或信号。

6. _process 和 _physics_process 的区别

你可以这样记:

_process
每一帧执行帧率变化时执行次数也变化
适合视觉UI非物理逻辑

_physics_process
固定物理帧执行默认每秒 60
适合角色移动碰撞物理逻辑

前端类比

_process 有点像:

requestAnimationFrame(() => {
  // 每帧视觉更新
})

_physics_process 更像游戏引擎内部固定 tick:

每秒稳定跑 60 次的物理更新循环

前端开发里不常直接遇到这种固定物理循环,但写游戏时很重要。

简单判断法

看到这个问题:

这个逻辑和碰撞移动物理有关吗

如果答案是“是”,优先放 _physics_process

看到这个问题:

这个逻辑只是显示动画UI视觉变化吗

优先放 _process

7. _input(event):输入事件

它是什么

_input(event) 会在节点收到输入事件时被调用。

比如:

按键按下
按键松开
鼠标点击
鼠标移动
触摸事件
手柄输入

Godot 的输入事件系统会把键盘、鼠标、手柄、触摸等输入封装成 InputEvent。官方文档也说明,_input() 可以在 _unhandled_input() 之前拦截和处理输入事件。(Godot Engine documentation)

基础例子

extends Node

func _input(event):
    if event.is_action_pressed("ui_accept"):
        print("按下确认键")

一次性按键适合放这里

比如打开背包:

extends Node

@onready var inventory_panel = $UI/InventoryPanel

func _input(event):
    if event.is_action_pressed("open_inventory"):
        inventory_panel.visible = !inventory_panel.visible

这类“一按触发一次”的逻辑适合输入事件。

不建议用 _input 做持续移动

不推荐:

func _input(event):
    if event.is_action_pressed("ui_right"):
        position.x += 10

因为 _input 是事件驱动,不是稳定的移动循环。

持续移动更适合:

func _physics_process(delta):
    if Input.is_action_pressed("ui_right"):
        direction.x += 1

Input.is_action_pressed 和 event.is_action_pressed 的区别

这两个很容易混。

Input.is_action_pressed

主动查询当前状态。

if Input.is_action_pressed("ui_right"):
    print("右键正在按着")

适合持续行为:

移动
蓄力
持续瞄准
按住跑步

通常放在 _process_physics_process

event.is_action_pressed

判断当前这个输入事件是不是某个动作按下。

func _input(event):
    if event.is_action_pressed("ui_accept"):
        print("这一次按下了确认键")

适合一次性行为:

打开背包
确认对话
跳跃触发
攻击触发
暂停游戏

8. _unhandled_input(event):未处理输入

它是什么

_unhandled_input(event) 会在输入事件没有被其他地方处理后调用。

这对于游戏输入非常好用。

Godot 文档的玩家输入教程提到,处理玩家输入主要有两类工具:内置输入回调,尤其是 _unhandled_input(),适合响应不需要每帧发生的事件,比如按 Space 跳跃。(Godot Engine documentation)

为什么它很重要

假设你有一个 UI 按钮。

玩家点击按钮时,按钮应该先处理这个点击。

如果按钮已经处理了,游戏世界就不应该再把这次点击当成攻击、移动、交互。

所以:

_input比较早收到输入
_unhandled_input只有没人处理时才收到输入

RPG 里推荐用法

比如玩家按 E 交互:

extends CharacterBody2D

func _unhandled_input(event):
    if event.is_action_pressed("interact"):
        try_interact()

这样当玩家正在操作 UI,比如对话框、菜单、背包时,可以避免输入冲突。

和 UI 的关系

Control 节点可以处理 UI 输入。Godot 文档提到,Control 可以调用 accept_event(),这样事件会变成已处理,后续 _unhandled_input() 就不会再处理它。(Godot Engine documentation)

这非常适合:

点击 UI 按钮时不触发游戏场景里的点击逻辑
打开菜单时不让玩家继续攻击
操作背包时不让角色乱动

9. _gui_input(event):UI 节点专用输入

它是什么

_gui_input(event) 主要用于 Control 节点。

比如:

Button
Panel
TextureRect
InventorySlot
DialogBox

它适合写 UI 控件自己的输入逻辑。

背包格子例子

extends TextureRect

func _gui_input(event):
    if event is InputEventMouseButton:
        if event.pressed and event.button_index == MOUSE_BUTTON_LEFT:
            print("点击了背包格子")

什么时候用它

适合:

背包格子点击
技能栏拖拽
UI 图片按钮
自定义控件
鼠标悬停提示

如果只是普通按钮,优先用 Button 自带的 pressed 信号。

10. _enter_tree:进入场景树

它是什么

_enter_tree() 会在节点进入 SceneTree 时调用。

它比 _ready() 更早。

extends Node

func _enter_tree():
    print("进入场景树")

func _ready():
    print("准备完成")

大概顺序是:

进入场景树
准备完成

它和 _ready 的区别

_enter_tree
节点刚进入场景树时触发比较早

_ready
节点和子节点都准备好后触发更适合初始化

入门阶段,大多数初始化写在 _ready() 就够了。

什么时候用 _enter_tree

适合比较少见的情况:

很早注册自己
和场景树结构相关的早期逻辑
某些底层初始化
插件或工具脚本

普通 RPG 开发中,不需要频繁使用。

11. _exit_tree:离开场景树

它是什么

_exit_tree() 会在节点离开 SceneTree 时调用。

extends Node

func _exit_tree():
    print("我要离开场景树了")

常见触发情况:

节点被 queue_free 删除
切换场景
父节点被删除
节点被 remove_child 移出场景树

适合做什么

适合清理工作:

断开信号
保存临时状态
停止音效
取消引用
关闭临时效果

例子:

extends Node

func _exit_tree():
    print("清理临时数据")

不过 Godot 很多资源会自动管理,入门阶段不用过度手动清理。

12. 生命周期执行顺序

假设一个节点从创建到销毁,流程大概是:

1. 节点被创建
2. 节点加入 SceneTree
3. 调用 _enter_tree()
4. 子节点也进入并准备
5. 调用 _ready()
6. 游戏运行中持续调用 _process(delta)
7. 游戏运行中持续调用 _physics_process(delta)
8. 收到输入时调用 _input(event)
9. 未处理输入时调用 _unhandled_input(event)
10. 节点离开 SceneTree
11. 调用 _exit_tree()

常用简化版:

_enter_tree
_ready
_process / _physics_process / input callbacks
_exit_tree

13. 父节点和子节点的 ready 顺序

这个是新手容易踩坑的地方。

结构:

Parent Node
└── Child Node

通常理解上:

_enter_tree父节点先进入再到子节点
_ready子节点先 ready再到父节点

这意味着:

父节点的 _ready 执行时子节点一般已经 ready

所以在父节点 _ready() 里拿子节点,通常是安全的。

extends Node

@onready var child = $Child

func _ready():
    child.do_something()

14. 一次性初始化和持续更新

很多初学者会把代码乱放,核心是没分清:

一次性逻辑
持续逻辑
输入事件
物理逻辑

一次性逻辑放 _ready

func _ready():
    health = max_health
    $AnimationPlayer.play("idle")

持续视觉逻辑放 _process

func _process(delta):
    $Clouds.position.x += 20 * delta

物理移动放 _physics_process

func _physics_process(delta):
    velocity = direction * speed
    move_and_slide()

一次性输入放 _input 或 _unhandled_input

func _unhandled_input(event):
    if event.is_action_pressed("interact"):
        interact()

15. 玩家脚本完整例子

这是一个比较标准的 2D RPG 玩家脚本结构:

extends CharacterBody2D

@export var speed: float = 120.0

@onready var body_sprite: Sprite2D = $BodySprite
@onready var animation_player: AnimationPlayer = $AnimationPlayer

var last_direction: Vector2 = Vector2.DOWN

func _ready():
    animation_player.play("idle_down")

func _physics_process(delta):
    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):
    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 play_walk_animation(direction: Vector2):
    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):
    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")

func try_interact():
    print("尝试交互")

这里的职责分工很清楚:

_ready
播放初始动画

_physics_process
处理移动速度动画状态

_unhandled_input
处理一次性交互输入

get_move_direction
获取移动方向

play_walk_animation
播放行走动画

play_idle_animation
播放待机动画

16. UI 脚本完整例子

比如 HUD:

HUD Control
├── HealthLabel Label
├── GoldLabel Label
└── InventoryButton Button

脚本:

extends Control

@onready var health_label: Label = $HealthLabel
@onready var gold_label: Label = $GoldLabel
@onready var inventory_button: Button = $InventoryButton

var gold: int = 0
var health: int = 100

func _ready():
    update_health_label()
    update_gold_label()
    inventory_button.pressed.connect(_on_inventory_button_pressed)

func update_health_label():
    health_label.text = "生命:" + str(health)

func update_gold_label():
    gold_label.text = "金币:" + str(gold)

func _on_inventory_button_pressed():
    print("打开背包")

这里没有用 _process()

因为 UI 不需要每帧刷新。

只有当数据变化时,主动调用更新函数就行。

17. 宝箱脚本完整例子

结构:

Chest StaticBody2D
├── Sprite2D
├── CollisionShape2D
├── InteractArea Area2D
│   └── CollisionShape2D
└── AnimationPlayer

脚本:

extends StaticBody2D

@onready var animation_player: AnimationPlayer = $AnimationPlayer
@onready var interact_shape: CollisionShape2D = $InteractArea/CollisionShape2D

var opened: bool = false
var player_inside: bool = false

func _ready():
    $InteractArea.body_entered.connect(_on_body_entered)
    $InteractArea.body_exited.connect(_on_body_exited)

func _unhandled_input(event):
    if event.is_action_pressed("interact") and player_inside:
        open()

func open():
    if opened:
        return

    opened = true
    animation_player.play("open")
    interact_shape.set_deferred("disabled", true)
    print("宝箱打开")

func _on_body_entered(body):
    if body.name == "Player":
        player_inside = true

func _on_body_exited(body):
    if body.name == "Player":
        player_inside = false

这里的职责:

_ready
连接进入/离开区域信号

_unhandled_input
玩家按交互键时打开宝箱

open
执行打开逻辑

body_entered/body_exited
记录玩家是否在附近

18. 什么时候不需要 _process

很多新手会习惯性写:

func _process(delta):
    pass

但其实没必要。

如果没有每帧逻辑,就不要写。

比如按钮、宝箱、NPC 对话,很多逻辑都可以靠信号和输入事件触发。

不要把所有东西都塞进 _process()

这种写法会越来越乱:

func _process(delta):
    check_player_input()
    check_chest()
    check_npc()
    update_ui()
    update_weather()
    update_dialog()

更好的方式是:

玩家移动Player._physics_process
宝箱交互Chest._unhandled_input + Area2D 信号
UI 更新数据变化时主动调用
天气变化WeatherManager Timer 或专门方法
NPC 对话Area2D + interact 输入

19. Timer 和 _process 的选择

如果你想每隔几秒做一次事情,不一定要在 _process() 里手动累计时间。

可以用 Timer。

用 _process 累计时间

var timer: float = 0.0

func _process(delta):
    timer += delta

    if timer >= 3.0:
        timer = 0.0
        print("每 3 秒执行一次")

用 Timer

func _ready():
    $Timer.timeout.connect(_on_timer_timeout)
    $Timer.start()

func _on_timer_timeout():
    print("时间到了")

更推荐初学者用 Timer,因为直观,也能在编辑器里配置。

20. process_mode:暂停时是否继续执行

Godot 节点有处理模式,决定游戏暂停时这个节点是否继续处理。

这在做暂停菜单时很重要。

比如:

游戏暂停了
玩家不能动
怪物不能动
但暂停菜单按钮还要能点

这就涉及 process_mode

入门先记:

普通游戏对象暂停时停止处理
暂停菜单 UI暂停时仍然处理

这部分后面讲“暂停系统”时单独展开。

21. set_process 和 set_physics_process

Godot 可以手动开启或关闭 _process()_physics_process()

set_process(false)
set_physics_process(false)

比如敌人死亡后,不想再执行逻辑:

func die():
    set_physics_process(false)
    $AnimationPlayer.play("die")

再比如某个节点只有激活后才需要每帧处理:

func activate():
    set_process(true)

func deactivate():
    set_process(false)

不过入门阶段不用频繁用,先知道有这个能力。

22. 常见错误

错误 1:在 _ready 里写移动

func _ready():
    position.x += 100

这只会移动一次。

持续移动应该写:

func _process(delta):
    position.x += 100 * delta

角色碰撞移动写:

func _physics_process(delta):
    velocity.x = 100
    move_and_slide()

错误 2:在 _process 里写角色碰撞移动

不推荐:

func _process(delta):
    velocity = direction * speed
    move_and_slide()

推荐:

func _physics_process(delta):
    velocity = direction * speed
    move_and_slide()

错误 3:忘记使用 delta

不推荐:

func _process(delta):
    position.x += 10

推荐:

func _process(delta):
    position.x += 100 * delta

错误 4:把一次性输入写成持续检测

比如打开背包:

不推荐:

func _process(delta):
    if Input.is_action_pressed("open_inventory"):
        inventory.visible = !inventory.visible

这样按住一瞬间可能开关疯狂闪烁。

推荐:

func _unhandled_input(event):
    if event.is_action_pressed("open_inventory"):
        inventory.visible = !inventory.visible

错误 5:所有节点都写 _process

不是每个节点都需要 _process()

能用信号就用信号。

能数据变化时更新,就不要每帧更新。

能用 Timer,就不用自己每帧累计。

23. 生命周期速查

初始化节点
_ready

每帧视觉更新
_process(delta)

角色移动碰撞物理
_physics_process(delta)

输入事件
_input(event)

游戏输入但避开 UI
_unhandled_input(event)

UI 控件自己的输入
_gui_input(event)

节点进入场景树
_enter_tree

节点离开场景树
_exit_tree

24. 给你的 2D RPG 推荐使用规则

对于你现在要做的 RPG,可以先按这个规则来:

Player
_ready 初始化
_physics_process 移动
_unhandled_input 交互

NPC
_ready 初始化
_physics_process 移动或巡逻
Area2D 信号检测玩家靠近

Chest
_ready 连接信号
_unhandled_input 打开
AnimationPlayer 播放动画

HUD
_ready 初始化 UI
数据变化时更新 UI
按钮用 pressed 信号

WeatherManager
_ready 初始化
Timer _process 控制视觉变化

TimeManager
Timer 更新时间
 signal 通知 UI 和世界变化

Camera2D
跟随玩家可以用 _process
涉及物理插值时再优化

25. 这一章最重要的一句话

_ready 做初始化_process 做每帧视觉更新_physics_process 做移动和碰撞_unhandled_input 做游戏输入

再压缩一下:

初始化_ready
视觉_process
物理_physics_process
输入_unhandled_input

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

分享文章

相关文章

更多文章 →
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 官方文档也把节点和场景放在一起讲:多个节点组成树状结构后...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录