Overview
In games, objects moving smoothly feels much more natural and pleasant than instant teleportation. UI elements sliding in from off-screen, characters flickering when damaged, HP bars gradually decreasing—these "time-based value changes" can be easily implemented using Godot's Tween feature.
In Godot 4, you can easily create code-based animations by calling the create_tween() method.
Tween Basics: Start with One Line
The basic concept of Tween is simple: "Change a specific property of an object to a target value over a specified duration."
func _ready():
# 1. Create a Tween object
var tween = create_tween()
# 2. Queue the animation
# Change $Sprite2D's position property to Vector2(500, 300) over 1 second
tween.tween_property($Sprite2D, "position", Vector2(500, 300), 1.0)
Tweens created with create_tween() automatically terminate and release from memory when all queued animations complete. This "fire and forget" convenience is one of Tween's greatest strengths.
Note: When the node that created the Tween is deleted with
queue_free(), the Tween is automatically stopped and released. If you need animations to continue beyond the node's lifetime, consider binding the Tween to a different node withcreate_tween().set_ignore_time_scale(false).bind_node(another_node), or manage Tweens in an Autoload (singleton).
Practical Code Recipes
Recipe 1: UI Slide Animation (with Easing)
An effect where UI elements smoothly slide in from off-screen. Use set_trans() and set_ease() to achieve professional-looking motion.
func show_menu():
var menu_panel = $MenuPanel
menu_panel.visible = true
var viewport_width = get_viewport_rect().size.x
menu_panel.position.x = viewport_width
var tween = create_tween()
# TRANS_SINE: Sine curve interpolation
# EASE_OUT: Decelerate at the end of animation
tween.tween_property(menu_panel, "position:x", viewport_width - menu_panel.size.x, 0.5)\
.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
set_trans specifies the interpolation method, while set_ease specifies where easing is applied during the transition.
Recipe 2: Smooth HP Bar Changes
Instead of instantly reflecting damage or healing, having the HP bar change gradually helps players intuitively understand the situation.
@onready var hp_bar: ProgressBar = $ProgressBar
func update_health_smoothly(new_health: float):
var tween = create_tween()
tween.tween_property(hp_bar, "value", new_health, 0.4)\
.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN_OUT)
func _on_player_took_damage(damage_amount: float):
var current_health = hp_bar.value
update_health_smoothly(current_health - damage_amount)
Recipe 3: Damage Flicker Effect
Flickering effects to visually indicate invincibility frames when a character takes damage are another strength of Tween.
func start_invincibility_flicker():
# Set up a loop to flicker 5 times
var tween = create_tween().set_loops(5)
# Change the modulate property's alpha value (opacity)
tween.tween_property(self, "modulate:a", 0.3, 0.1)
tween.tween_property(self, "modulate:a", 1.0, 0.1)
Advanced Techniques for Full Animation Control
Sequence and Parallel
- Sequence: Writing consecutive
tween_propertycalls executes them in order. - Parallel: Inserting
parallel()between calls makes subsequent animations start simultaneously with the previous one.
func complex_animation():
var tween = create_tween()
var sprite = $Sprite2D
# 1. First, move right over 1 second
tween.tween_property(sprite, "position:x", 500.0, 1.0)
# 2. Next, move down while simultaneously rotating 45 degrees
tween.parallel().tween_property(sprite, "position:y", 300.0, 0.5)
tween.parallel().tween_property(sprite, "rotation_degrees", 45.0, 0.5)
# 3. Wait 0.5 seconds
tween.tween_interval(0.5)
# 4. Finally, fade out over 0.3 seconds
tween.tween_property(sprite, "modulate:a", 0.0, 0.3)
Async Integration with await
When you want to perform specific actions after an animation completes, GDScript's await feature is extremely useful.
func play_and_destroy():
var tween = create_tween()
tween.tween_property(self, "scale", Vector2.ZERO, 0.5)
# Wait at this line until tween's finished signal is emitted
await tween.finished
# Executed after animation completes
queue_free()
Common Mistakes and Best Practices
| Common Mistake | Best Practice |
|---|---|
Calling create_tween() every frame inside _process(). | Only call create_tween() when starting an animation. For continuous following, consider using lerp or other methods. |
| Starting new Tweens without stopping existing ones, causing conflicting movements. | Stop existing Tweens with kill() before starting new animations (see code example below). |
Connecting many processes to tween.finished signal, increasing complexity. | Use await tween.finished and write the process as an async function for cleaner, more linear code flow. |
| Trying to implement all animations with Tween. | Complex cutscenes involving multiple nodes and tracks are better suited for AnimationPlayer. |
Using kill() to Prevent Tween Conflicts
var current_tween: Tween
func animate_to(target_position: Vector2):
# Stop existing Tween if running
if current_tween and current_tween.is_valid():
current_tween.kill()
# Create new Tween
current_tween = create_tween()
current_tween.tween_property(self, "position", target_position, 0.5)
Choosing Between Tween and AnimationPlayer
| Comparison | Tween (create_tween) | AnimationPlayer |
|---|---|---|
| Best for | Code-based dynamic animations, UI effects, simple value changes | Pre-defined complex sequences, cutscenes, character animations |
| Setup | Easy (code-only) | More complex (requires keyframe setup in editor) |
| Flexibility | High (can freely generate animations based on runtime values) | Lower (primarily plays pre-defined animations) |
| Visual Editing | Not possible | Excellent (can visually edit and preview in timeline) |
Summary
Godot 4's Tween feature is a wonderful tool for easily and powerfully implementing code-based animations.
- UI effects
- Visual effects (flickering, fade-outs, etc.)
- Creating moving platforms and mechanics
By leveraging Tween for these elements, you can dramatically improve your game's quality.