Godot 3 GDScript 教程 / 2D 基础:Sprite 与动画
2D 基础:Sprite 与动画
Sprite 节点
Sprite 是 Godot 2D 中最基础的显示节点,用于显示一张纹理(图片)。
基本属性
| 属性 | 类型 | 说明 |
|---|---|---|
texture | Texture | 显示的纹理 |
offset | Vector2 | 纹理偏移 |
flip_h | bool | 水平翻转 |
flip_v | bool | 垂直翻转 |
region_enabled | bool | 启用区域裁剪 |
region_rect | Rect2 | 裁剪区域 |
centered | bool | 是否以节点位置为中心 |
modulate | Color | 颜色调制 |
self_modulate | Color | 自身颜色调制(不影响子节点) |
代码中使用 Sprite
extends Sprite
func _ready() -> void:
# 加载纹理
texture = load("res://assets/sprites/player.png")
# 或使用预加载
texture = preload("res://assets/sprites/player.png")
# 水平翻转(面向不同方向)
flip_h = false # 向右
flip_h = true # 向左
# 区域裁剪(图集中选择单帧)
region_enabled = true
region_rect = Rect2(0, 0, 32, 32) # 裁剪左上角 32x32 区域
# 颜色效果
modulate = Color.red # 染成红色
modulate.a = 0.5 # 半透明
func flash_damage() -> void:
modulate = Color.red
yield(get_tree().create_timer(0.1), "timeout")
modulate = Color.white
AnimatedSprite
AnimatedSprite 用于播放帧动画,适合角色行走、待机等动画。
设置 AnimatedSprite
- 添加 AnimatedSprite 节点
- 在 Inspector 中新建 SpriteFrames 资源
- 在 SpriteFrames 编辑器中添加动画和帧
代码控制
extends KinematicBody2D
onready var anim_sprite = $AnimatedSprite
func _process(delta: float) -> void:
var velocity = _get_input()
if velocity.length() > 0:
anim_sprite.play("walk")
anim_sprite.flip_h = velocity.x < 0 # 根据方向翻转
else:
anim_sprite.play("idle")
func attack() -> void:
anim_sprite.play("attack")
yield(anim_sprite, "animation_finished") # 等待动画完成
anim_sprite.play("idle")
func _get_input() -> Vector2:
var vel = Vector2.ZERO
if Input.is_action_pressed("move_right"):
vel.x += 1
if Input.is_action_pressed("move_left"):
vel.x -= 1
return vel.normalized()
SpriteFrames 编辑器动画设置
| 动画名 | 帧数 | FPS | 循环 | 用途 |
|---|---|---|---|---|
| idle | 4 | 8 | ✅ | 待机 |
| walk | 6 | 12 | ✅ | 行走 |
| run | 6 | 16 | ✅ | 奔跑 |
| jump | 3 | 10 | ❌ | 跳跃 |
| attack | 5 | 15 | ❌ | 攻击 |
| hurt | 2 | 8 | ❌ | 受伤 |
| die | 4 | 8 | ❌ | 死亡 |
图集(Atlas Texture)
图集将多个小图合并为一张大图,减少绘制调用(Draw Call),提升性能。
使用 AtlasTexture
# 在代码中使用 AtlasTexture
extends Sprite
func set_frame(frame_index: int, frame_size: Vector2) -> void:
var atlas = AtlasTexture.new()
atlas.atlas = preload("res://assets/sprites/characters.png")
atlas.region = Rect2(
frame_index * frame_size.x,
0,
frame_size.x,
frame_size.y
)
texture = atlas
💡 提示:在 Godot 编辑器中,可以使用 Import 面板自动将图片切割为 AtlasTexture。
Camera2D
Camera2D 控制 2D 游戏中的视口显示区域。
基本设置
extends Camera2D
export var smoothing_speed: float = 5.0
export var look_ahead: float = 100.0
func _ready() -> void:
# 启用平滑移动
smoothing_enabled = true
smoothing_speed = 5.0
# 启用拖拽(相机延迟跟随,适合平台游戏)
drag_margin_h_enabled = true
drag_margin_v_enabled = true
drag_margin_left = 0.2
drag_margin_right = 0.2
drag_margin_top = 0.2
drag_margin_bottom = 0.2
func _process(delta: float) -> void:
# 跟随玩家
global_position = global_position.linear_interpolate(
$Player.global_position,
smoothing_speed * delta
)
相机震动效果
extends Camera2D
var shake_amount: float = 0.0
var shake_decay: float = 5.0
func shake(amount: float) -> void:
shake_amount = amount
func _process(delta: float) -> void:
if shake_amount > 0:
offset = Vector2(
rand_range(-shake_amount, shake_amount),
rand_range(-shake_amount, shake_amount)
)
shake_amount = max(0, shake_amount - shake_decay * delta)
else:
offset = Vector2.ZERO
相机边界限制
extends Camera2D
func _ready() -> void:
# 设置相机限制(不超出关卡边界)
limit_left = 0
limit_top = 0
limit_right = 2000
limit_bottom = 1000
# 启用限制平滑
limit_smoothed = true
视口(Viewport)
Viewport 用于渲染到纹理、分屏、小地图等效果。
小地图实现
extends Control
# 通过 SubViewport 显示小地图
onready var minimap_camera = $SubViewport/Camera2D
onready var minimap_display = $MinimapRect
func _ready() -> void:
# 配置小地图视口
$SubViewport.size = Vector2(200, 200)
$SubViewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
func _process(delta: float) -> void:
# 小地图相机跟随玩家
minimap_camera.global_position = $Player.global_position
minimap_camera.zoom = Vector2(3, 3) # 缩小视野范围
2D 光照
Godot 2D 支持动态光照效果。
灯光节点
| 节点 | 功能 |
|---|---|
Light2D | 点光源/聚光灯 |
Line2D | 线条绘制 |
Polygon2D | 多边形绘制 |
光照代码示例
extends Light2D
export var flicker_speed: float = 10.0
export var flicker_amount: float = 0.2
var base_energy: float = 1.0
func _ready() -> void:
base_energy = energy
func _process(delta: float) -> void:
# 火把闪烁效果
energy = base_energy + rand_range(-flicker_amount, flicker_amount)
texture_scale = 1.0 + rand_range(-0.05, 0.05)
⚠️ 注意:2D 光照需要被照亮的节点开启 Light → Enabled,并且需要设置适当的混合模式。
Parallax 背景
ParallaxBackground 和 ParallaxLayer 实现视差滚动效果,增加场景深度感。
设置方式
- 添加 ParallaxBackground 节点
- 添加 ParallaxLayer 子节点
- 设置滚动比率(motion_scale)
代码示例
extends ParallaxBackground
func _process(delta: float) -> void:
# 自动滚动背景
scroll_offset.x -= 50 * delta # 永不停止的横向滚动
# 或跟随相机自动滚动(默认行为)
ParallaxLayer 设置
# 远景层(移动慢)- motion_scale 较小
$ParallaxLayer.motion_scale = Vector2(0.5, 0.5)
# 中景层
$ParallaxLayer2.motion_scale = Vector2(0.8, 0.8)
# 近景层(移动快)- motion_scale 接近 1
$ParallaxLayer3.motion_scale = Vector2(0.95, 0.95)
多层背景示例
# 层级结构:
# ParallaxBackground
# ├── ParallaxLayer (远山, motion_scale = 0.2)
# │ └── Sprite
# ├── ParallaxLayer (中景树, motion_scale = 0.5)
# │ └── Sprite
# └── ParallaxLayer (近景灌木, motion_scale = 0.8)
# └── Sprite
# 设置无限重复
func _ready() -> void:
# 对每个 ParallaxLayer 设置重复
$FarMountains.motion_mirroring = Vector2(1920, 0)
$MidTrees.motion_mirroring = Vector2(1920, 0)
$NearBushes.motion_mirroring = Vector2(1920, 0)
动画关键帧(AnimationPlayer)
AnimationPlayer 是 Godot 最强大的动画系统,可以动画化任何节点属性。
创建动画
extends Node2D
onready var anim = $AnimationPlayer
func _ready() -> void:
# 播放动画
anim.play("idle")
# 播放动画(带过渡时间)
anim.play("walk", 0.2)
# 倒放
anim.play_backwards("open_door")
# 暂停/恢复
anim.stop()
anim.play() # 从暂停位置继续
# 设置播放速度
anim.playback_speed = 2.0 # 2倍速
# 动画完成信号
func _on_AnimationPlayer_animation_finished(anim_name: String) -> void:
match anim_name:
"attack":
anim.play("idle")
"die":
queue_free()
动画关键帧轨道类型
| 轨道类型 | 用途 | 示例 |
|---|---|---|
| Property | 属性动画 | 位置、颜色、透明度 |
| Call Method | 方法调用 | 在特定帧播放音效 |
| Bezier | 贝塞尔曲线 | 复杂的缓动效果 |
| Audio | 音频播放 | 精确的音效时机 |
| Animation | 动画嵌套 | 复杂动画组合 |
AnimationPlayer 代码创建动画
extends Node2D
func _ready() -> void:
var anim_player = AnimationPlayer.new()
add_child(anim_player)
# 创建新动画
var anim = Animation.new()
var track_index = anim.add_track(Animation.TYPE_VALUE)
# 设置轨道路径: NodePath("Sprite:modulate")
anim.track_set_path(track_index, NodePath("Sprite:modulate"))
# 添加关键帧
anim.track_insert_key(track_index, 0.0, Color.white)
anim.track_insert_key(track_index, 0.2, Color.red)
anim.track_insert_key(track_index, 0.4, Color.white)
# 添加到动画库
anim_player.add_animation("flash_damage", anim)
anim_player.play("flash_damage")
Tween 补间动画
Tween 节点用于属性平滑过渡,适合 UI 动画和简单补间。
Tween 基本使用
extends Node2D
onready var tween = $Tween
func move_to(target_pos: Vector2, duration: float = 0.5) -> void:
# 停止之前的补间
tween.stop_all()
# 补间属性: (对象, 属性, 起始值, 终止值, 时间, 过渡类型, 缓动类型)
tween.interpolate_property(
self, # 目标对象
"position", # 属性名
position, # 起始值
target_pos, # 终止值
duration, # 持续时间
Tween.TRANS_QUAD, # 过渡类型
Tween.EASE_OUT # 缓动类型
)
tween.start()
func fade_out(duration: float = 0.3) -> void:
tween.stop_all()
tween.interpolate_property(
self, "modulate:a",
modulate.a, 0.0, duration,
Tween.TRANS_LINEAR, Tween.EASE_IN
)
tween.start()
func bounce_effect() -> void:
var original_scale = scale
tween.stop_all()
tween.interpolate_property(
self, "scale",
original_scale * 1.3, original_scale, 0.3,
Tween.TRANS_ELASTIC, Tween.EASE_OUT
)
tween.start()
Tween 过渡类型
| 类型 | 说明 |
|---|---|
TRANS_LINEAR | 线性 |
TRANS_SINE | 正弦 |
TRANS_QUAD | 二次方 |
TRANS_CUBIC | 三次方 |
TRANS_QUART | 四次方 |
TRANS_QUINT | 五次方 |
TRANS_EXPO | 指数 |
TRANS_ELASTIC | 弹性 |
TRANS_BOUNCE | 弹跳 |
TRANS_BACK | 回弹 |
Tween 缓动类型
| 类型 | 说明 |
|---|---|
EASE_IN | 先慢后快 |
EASE_OUT | 先快后慢 |
EASE_IN_OUT | 中间快两头慢 |
EASE_OUT_IN | 中间慢两头快 |
Tween 连续与并行动画
extends Control
onready var tween = $Tween
func show_menu() -> void:
tween.stop_all()
# 连续动画(第一个完成后开始第二个)
tween.interpolate_property(
self, "rect_position:x",
-200, 0, 0.5,
Tween.TRANS_BACK, Tween.EASE_OUT
)
tween.start()
yield(tween, "tween_all_completed")
# 并行动画(同时进行)
tween.interpolate_property(
$Title, "modulate:a",
0, 1, 0.3,
Tween.TRANS_LINEAR, Tween.EASE_IN
)
tween.interpolate_property(
$Button, "modulate:a",
0, 1, 0.3,
Tween.TRANS_LINEAR, Tween.EASE_IN
)
tween.start()
Tween 循环
# 循环播放
tween.set_repeat(true)
# 来回循环(yoyo 效果)
# 通过两次 interpolate 实现
func floating_effect() -> void:
var base_y = position.y
tween.interpolate_property(
self, "position:y",
base_y, base_y - 10, 1.0,
Tween.TRANS_SINE, Tween.EASE_IN_OUT
)
tween.interpolate_property(
self, "position:y",
base_y - 10, base_y, 1.0,
Tween.TRANS_SINE, Tween.EASE_IN_OUT,
1.0 # 延迟 1 秒后开始
)
tween.set_repeat(true)
tween.start()
⚠️ 注意:Godot 4 中 Tween 的用法完全不同(不再使用 Tween 节点,改为 create_tween())。
2D 粒子(Particles2D)
Particles2D 用于创建 2D 粒子效果,如火焰、烟雾、灰尘等。
基本设置
extends Particles2D
func _ready() -> void:
# 设置粒子材质
var material = ParticlesMaterial.new()
# 发射方向
material.direction = Vector3(0, -1, 0) # 向上
material.spread = 15.0 # 扩散角度
# 初始速度
material.initial_velocity = 100.0
material.initial_velocity_random = 0.3
# 生命周期
material.lifetime = 1.0
material.lifetime_random = 0.2
# 重力
material.gravity = Vector3(0, 98, 0) # 向下
# 颜色
material.color_ramp = _create_color_ramp()
# 缩放
material.scale_curve = _create_scale_curve()
process_material = material
# 发射量
amount = 50
emitting = true
lifetime = 1.0
func _create_color_ramp() -> GradientTexture:
var gradient = Gradient.new()
gradient.set_color(0, Color(1, 0.5, 0, 1)) # 橙色
gradient.set_color(1, Color(1, 0, 0, 0)) # 透明红
var texture = GradientTexture.new()
texture.gradient = gradient
return texture
常见粒子效果参数
| 效果 | direction | spread | gravity | lifetime |
|---|---|---|---|---|
| 火焰 | (0, -1, 0) | 15° | (0, -20, 0) | 0.5-1s |
| 烟雾 | (0, -1, 0) | 30° | (0, -10, 0) | 1-2s |
| 灰尘 | 各方向 | 360° | (0, 20, 0) | 0.3-0.5s |
| 雨滴 | (0, 1, 0) | 5° | (0, 500, 0) | 0.5-1s |
| 星星 | (0, -1, 0) | 360° | (0, 0, 0) | 0.5-1s |
| 爆炸 | 各方向 | 360° | (0, 50, 0) | 0.3-0.5s |
代码触发粒子
extends Node2D
onready var particles = $Particles2D
func emit_explosion() -> void:
particles.emitting = true
# 或使用 one_shot 模式
# particles.one_shot = true
# particles.restart()
func stop_particles() -> void:
particles.emitting = false
游戏开发场景
场景:角色动画状态机
extends KinematicBody2D
enum State { IDLE, WALK, JUMP, ATTACK, HURT }
var current_state: int = State.IDLE
onready var anim_sprite = $AnimatedSprite
onready var tween = $Tween
export var speed: float = 200.0
var velocity: Vector2 = Vector2.ZERO
func _physics_process(delta: float) -> void:
match current_state:
State.IDLE:
_process_idle(delta)
State.WALK:
_process_walk(delta)
State.JUMP:
_process_jump(delta)
func _process_idle(_delta: float) -> void:
velocity = Vector2.ZERO
if Input.is_action_pressed("move_left") or Input.is_action_pressed("move_right"):
_change_state(State.WALK)
if Input.is_action_just_pressed("jump"):
_change_state(State.JUMP)
if Input.is_action_just_pressed("attack"):
_change_state(State.ATTACK)
func _process_walk(delta: float) -> void:
velocity.x = _get_input_direction() * speed
velocity = move_and_slide(velocity)
anim_sprite.flip_h = velocity.x < 0
if velocity.x == 0:
_change_state(State.IDLE)
if Input.is_action_just_pressed("jump"):
_change_state(State.JUMP)
func _change_state(new_state: int) -> void:
current_state = new_state
match new_state:
State.IDLE:
anim_sprite.play("idle")
State.WALK:
anim_sprite.play("walk")
State.JUMP:
anim_sprite.play("jump")
State.ATTACK:
anim_sprite.play("attack")
State.HURT:
anim_sprite.play("hurt")
tween.interpolate_property(
self, "modulate",
Color.red, Color.white, 0.3
)
tween.start()
func _get_input_direction() -> float:
var dir = 0
if Input.is_action_pressed("move_right"):
dir += 1
if Input.is_action_pressed("move_left"):
dir -= 1
return dir