
Godot Tweening
- 224 installs
- 454 repo stars
- Updated July 28, 2026
- thedivergentai/gd-agentic-skills
Use godot-tweening for development tasks
About
godot-tweening: A skill for development. This provides functionality for development workflows.
- godot-tweening
Godot Tweening by the numbers
- 224 all-time installs (skills.sh)
- +29 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #1,781 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/thedivergentai/gd-agentic-skills --skill godot-tweeningAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 224 |
|---|---|
| repo stars | ★ 454 |
| Last updated | July 28, 2026 |
| Repository | thedivergentai/gd-agentic-skills ↗ |
What it does
Use godot-tweening for development tasks
Files
Tweening
Tween property animation, easing curves, chaining, and lifecycle management define smooth programmatic motion.
Available Scripts
safe_tween_interruption.gd
Expert logic for killing active tweens before starting new ones to prevent property conflicts.
parallel_popup_animation.gd
Using set_parallel(true) and chain() for complex multi-property UI transitions.
text_counter_method_tween.gd
Animating non-property values (like score strings) using tween_method.
custom_curve_tween.gd
Driving property interpolation using visual Curve resources for bespoke easing.
camera_shake_tween_logic.gd
Implementing procedural screen shake using randomized looping tweens.
time_scale_ignored_ui.gd
Ensuring menu animations continue playing when Engine.time_scale is set to 0.
nested_subtween_cutscene.gd
Hierarchical cutscene management using tween_subtween for composable timelines.
relative_recoil_tween.gd
Using as_relative() and from_current() for dynamic movement offsets (recoil/nudges).
staggered_inventory_entry.gd
Animating collections of items sequentially using a single Tween object.
looped_hover_vfx.gd
Creating infinite ping-pong ambient effects to replace heavy AnimationPlayers.
NEVER Do in Tweening
- NEVER instantiate a Tween using `Tween.new()` — Tweens created manually are invalid. Always use
create_tween()orget_tree().create_tween()[3, 4]. - NEVER attempt to reuse a finished Tween — Tweens are single-use objects. To replay an animation, you must create a new one [4].
- NEVER manually instantiate `PropertyTweener` or `CallbackTweener` — These must be generated only by the parent Tween methods like
tween_property[5]. - NEVER create an infinite loop containing only 0-duration animations — This will freeze the engine. Always include at least one step with duration [10].
- NEVER use multiple Tweens to animate the same property simultaneously — The last one created takes priority, causing flicker. Use
kill()on the old reference first [11, 12]. - NEVER use linear interpolation for UI/Juice —
TRANS_LINEARfeels robotic. UseEASE_OUT + TRANS_QUADorEASE_IN_OUT + TRANS_CUBICfor organic motion [22]. - NEVER create tweens in `_process` without guards — Creating 60 tweens per second will crash the app. Use a state check or kill the running one.
- NEVER skip `bind_node(self)` for non-global tweens — If the node is freed while a tween is running, it can cause errors. Binding ensures it dies with the node [13].
- NEVER use 0-duration tweens for state changes — If you want an instant change, just set the property directly (
position = goal) to save overhead [20]. - NEVER forget to call `chain()` when returning from `set_parallel(true)` — If you want a sequence after a parallel block, you must explicitly chain it [15].
---
extends Sprite2D
func _ready() -> void:
# Create tween
var tween := create_tween()
# Animate position over 2 seconds
tween.tween_property(self, "position", Vector2(100, 100), 2.0)Tween Methods
Property Animation
# Tween single property
var tween := create_tween()
tween.tween_property($Sprite, "modulate:a", 0.0, 1.0) # Fade out
# Chain multiple tweens
tween.tween_property($Sprite, "position:x", 200, 1.0)
tween.tween_property($Sprite, "position:y", 100, 0.5)Callbacks
var tween := create_tween()
tween.tween_property($Sprite, "position", Vector2(100, 0), 1.0)
tween.tween_callback(func(): print("Animation done!"))
tween.tween_callback(queue_free) # Delete after animationIntervals
var tween := create_tween()
tween.tween_property($Label, "modulate:a", 0.0, 0.5)
tween.tween_interval(1.0) # Wait 1 second
tween.tween_property($Label, "modulate:a", 1.0, 0.5)Easing Functions
var tween := create_tween()
tween.set_ease(Tween.EASE_IN_OUT) # Smooth start and end
tween.set_trans(Tween.TRANS_CUBIC) # Cubic curve
tween.tween_property($Sprite, "position:x", 200, 1.0)Common Combinations:
EASE_IN + TRANS_QUAD: AcceleratingEASE_OUT + TRANS_QUAD: DeceleratingEASE_IN_OUT + TRANS_CUBIC: Smooth S-curveEASE_OUT + TRANS_BOUNCE: Bouncy effect
Advanced Patterns
Looping Animation
var tween := create_tween()
tween.set_loops() # Infinite loop
tween.tween_property($Sprite, "rotation", TAU, 2.0)Parallel Tweens
var tween := create_tween()
tween.set_parallel(true)
# Both happen simultaneously
tween.tween_property($Sprite, "position", Vector2(100, 100), 1.0)
tween.tween_property($Sprite, "scale", Vector2(2, 2), 1.0)UI Button Hover Effect
extends Button
func _ready() -> void:
mouse_entered.connect(_on_mouse_entered)
mouse_exited.connect(_on_mouse_exited)
func _on_mouse_entered() -> void:
var tween := create_tween()
tween.tween_property(self, "scale", Vector2(1.1, 1.1), 0.2)
func _on_mouse_exited() -> void:
var tween := create_tween()
tween.tween_property(self, "scale", Vector2.ONE, 0.2)Number Counter
extends Label
func count_to(target: int, duration: float = 1.0) -> void:
var current := int(text)
var tween := create_tween()
tween.tween_method(
func(value: int): text = str(value),
current,
target,
duration
)Camera Smooth Follow
extends Camera2D
@export var follow_speed := 5.0
var target: Node2D
func _process(delta: float) -> void:
if target:
var tween := create_tween()
tween.tween_property(
self,
"global_position",
target.global_position,
1.0 / follow_speed
)Best Practices
1. Kill Previous Tweens
var current_tween: Tween = null
func animate_to(pos: Vector2) -> void:
if current_tween:
current_tween.kill() # Stop previous animation
current_tween = create_tween()
current_tween.tween_property(self, "position", pos, 1.0)2. Use Signals for Completion
var tween := create_tween()
tween.tween_property($Sprite, "position", Vector2(100, 0), 1.0)
tween.finished.connect(_on_tween_finished)
func _on_tween_finished() -> void:
print("Animation complete!")3. Chaining for Sequences
var tween := create_tween()
# Fade out
tween.tween_property($Sprite, "modulate:a", 0.0, 0.5)
# Move while invisible
tween.tween_property($Sprite, "position", Vector2(200, 0), 0.0)
# Fade in at new position
tween.tween_property($Sprite, "modulate:a", 1.0, 0.5)Common Gotchas
Issue: Tween stops when node is removed
# Solution: Bind tween to SceneTree
var tween := get_tree().create_tween()
tween.tween_property($Sprite, "position", Vector2(100, 0), 1.0)Issue: Multiple conflicting tweens
# Solution: Use single tween or kill previous
# Always store reference to kill old tween---
Expert Pattern: Bezier-Path-Tween
Animate objects along complex, curved trajectories using Path2D and PathFollow2D instead of calculating math manually.
func play_curved_motion(follow_node: PathFollow2D, duration: float):
# 1. Ensure path starts at 0
follow_node.progress_ratio = 0.0
# 2. Tween the sampler progress
var tween := create_tween().bind_node(follow_node)
tween.tween_property(follow_node, "progress_ratio", 1.0, duration) \
.set_trans(Tween.TRANS_CUBIC) \
.set_ease(Tween.EASE_IN_OUT)---
Expert Pattern: Physics-Sync-Tweening
Prevent jitter and visual "streaking" when animating physics bodies or handling network corrections.
func apply_physics_tween(target: Node3D, goal: Vector3):
# 1. Prevent 'visual streak' if teleporting to a start position
target.global_position = start_pos
target.reset_physics_interpolation()
# 2. Sync tween steps with physics frames
var tween := create_tween().bind_node(target)
tween.set_process_mode(Tween.TWEEN_PROCESS_PHYSICS)
tween.tween_property(target, "global_position", goal, 0.5)---
Expert Pattern: Juice-Config-Resource
Decouple animation "feel" from logic by storing tween parameters in external resources for global balancing.
# juice_config.gd
class_name JuiceConfig extends Resource
@export var duration: float = 0.3
@export var trans: Tween.TransitionType = Tween.TRANS_ELASTIC
@export var ease: Tween.EaseType = Tween.EASE_OUT
# gameplay_object.gd
@export var juice: JuiceConfig
func play_bounce():
var tween := create_tween()
tween.set_trans(juice.trans)
tween.set_ease(juice.ease)
tween.tween_property(self, "scale", Vector2(1.2, 1.2), juice.duration)---
Expert Pattern: Tween-Event-Sequencing
Orchestrate complex mini-cutscenes by chaining property interpolations, delays, and callbacks into a single controlled sequence.
func play_mini_cutscene(actor: Sprite2D):
var tween := create_tween().bind_node(self)
# 1. Step 1: Move and fade in (Parallel)
tween.set_parallel(true)
tween.tween_property(actor, "position:x", 500.0, 1.0)
tween.tween_property(actor, "modulate:a", 1.0, 0.5)
# 2. Step 2: Wait then call logic (Chained)
tween.chain().tween_interval(0.5)
tween.tween_callback(func(): _play_vfx(actor.position))
# 3. Step 3: Shrink and exit
tween.tween_property(actor, "scale", Vector2.ZERO, 0.5).set_trans(Tween.TRANS_BACK)
tween.tween_callback(actor.queue_free)Reference
Related
- Master Skill: godot-master
# camera_shake_tween_logic.gd
# Procedural screen shake using randomized tweens
extends Camera2D
func apply_shake(intensity: float, duration: float):
var tween = create_tween().set_loops(5) # Shake 5 times
var offset_v = Vector2(randf_range(-1, 1), randf_range(-1, 1)) * intensity
tween.tween_property(self, "offset", offset_v, duration / 10.0)
tween.tween_property(self, "offset", Vector2.ZERO, duration / 10.0)
# Ensure the camera returns to 0,0 at the very end
tween.finished.connect(func(): offset = Vector2.ZERO)
# custom_curve_tween.gd
# Driving property animations using visual Curve resources
extends Node2D
@export var bounce_curve: Curve
# EXPERT NOTE: For juice, avoid standard TransTypes and use a
# Curve resource for total control over the easing profile.
func juice_impact():
var tween = create_tween()
# The scale will follow the visual curve exactly
tween.tween_property(self, "scale", Vector2(1.5, 1.5), 0.6)\
.set_custom_interpolator(func(v): return bounce_curve.sample(v))
tween.chain().tween_property(self, "scale", Vector2.ONE, 0.2)
# skills/tweening/code/juice_manager.gd
extends Node
## Tweening Expert Pattern
## Implements Custom Interpolators (Juice) and Bezier Easing.
@export var punch_curve: Curve
# 1. Custom Interpolators (tween_method)
func punch_ui(target: Control) -> void:
var tween = create_tween().set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT)
# Professional pattern: Kill previous tween to avoid conflicts.
# target.set_meta("active_tween", tween) # Simplified tracking
# Animate a custom method for values that aren't simple properties
# e.g. Animate a shader uniform or a multi-step calculation.
tween.tween_method(_apply_shader_distortion, 0.0, 1.0, 0.5)
# 2. Sequence Management
tween.parallel().tween_property(target, "scale", Vector2(1.2, 1.2), 0.1)
tween.chain().tween_property(target, "scale", Vector2.ONE, 0.3)
func _apply_shader_distortion(value: float) -> void:
# Logic for manual value interpolation
pass
# 3. Bezier Easing via Curves
func curve_animate(target: Node2D, destination: Vector2) -> void:
var tween = create_tween()
# Expert logic: Use a Move-To-Value approach driven by a Curve.
# This allows for high-end, artistic motion control.
tween.tween_method(
func(t: float):
var weight = punch_curve.sample(t)
target.global_position = target.global_position.lerp(destination, weight),
0.0, 1.0, 1.0
)
## EXPERT NOTE:
## Use 'Scene Tree Independence': For UI elements that persist
## across scene changes (like a loading bar), create the Tween
## on a Global Autoload Node rather than the Control node itself.
## For 'tweening', implement 'Rhythmic Transitions': Use
## 'tween.set_speed_scale(2.0)' to match UI animation speed to
## the game's BPM or combat pace.
## NEVER forget to call 'kill()' on older tweens when a new action
## interrupts an existing one (e.g. stopping a 'Hurt' shake to start
## a 'Death' fade) to prevent property flickering.
## Use 'set_parallel()' to trigger multi-property 'Juice' (Scale,
## Rotation, and Color) in a single rhythmic burst.
# looped_hover_vfx.gd
# Infinite ping-pong animations with set_loops()
extends Sprite2D
# EXPERT NOTE: Tweens can replace AnimationPlayer for simple
# ambient effects like floating or glowing.
func _ready() -> void:
var tween = create_tween().set_loops() # Infinite
tween.tween_property(self, "position:y", 10, 2.0)\
.as_relative().set_trans(Tween.TRANS_SINE)
tween.tween_property(self, "position:y", -10, 2.0)\
.as_relative().set_trans(Tween.TRANS_SINE)
# nested_subtween_cutscene.gd
# Hierarchical cutscene timing using tween_subtween()
extends Node
# EXPERT NOTE: Combine multiple complex sequences into a main
# timeline using subtweens for modular cutscene management.
func play_sequence():
var sub = create_tween()
sub.tween_property($Actor, "rotation", PI, 1.0)
sub.tween_property($Actor, "rotation", 0, 1.0)
var main = create_tween()
main.tween_property($Actor, "position:x", 500, 2.0)
# Main timeline waits for the rotation sequence to finish
main.tween_subtween(sub)
main.tween_property($Actor, "modulate:a", 0, 1.0)
# parallel_popup_animation.gd
# Simultaneous property animations with set_parallel()
extends Control
# EXPERT NOTE: Use set_parallel(true) for UI popups to move, fade,
# and scale all at once, then chain() for sequential cleanup.
func show_popup():
pivot_offset = size / 2
scale = Vector2.ZERO
modulate.a = 0
var tween = create_tween().set_parallel(true)
tween.tween_property(self, "scale", Vector2.ONE, 0.4)\
.set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
tween.tween_property(self, "modulate:a", 1.0, 0.3)
# Transition back to sequential mode to run a callback at the end
tween.chain().tween_callback(_on_show_complete)
func _on_show_complete():
print("Popup fully visible")
# relative_recoil_tween.gd
# Relative position offsets with as_relative() and from_current()
extends Sprite2D
# EXPERT NOTE: Use as_relative() for recoil or camera nudges so you
# don't need to know the 'base' value.
func shoot_recoil(strength: float):
var tween = create_tween()
# Relative movement from WHEREVER it is now
tween.tween_property(self, "position:x", -strength, 0.05)\
.as_relative().from_current().set_trans(Tween.TRANS_SINE)
# Snap back to center
tween.chain().tween_property(self, "position:x", 0, 0.2)
# safe_tween_interruption.gd
# Aborting active tweens before starting new ones on the same object
extends Node2D
# EXPERT NOTE: Multiple tweens fighting over the same property cause
# erratic behavior. Always kill the previous tween if it's still running.
var _active_tween: Tween
func animate_safe_hover():
if _active_tween and _active_tween.is_valid():
_active_tween.kill() # Terminate previous animation
# bind_node ensures the tween is killed if the node is deleted
_active_tween = create_tween().bind_node(self)
_active_tween.tween_property(self, "scale", Vector2(1.2, 1.2), 0.2)
_active_tween.set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT)
# staggered_inventory_entry.gd
# Looping through collections for sequential entry effects
extends GridContainer
func animate_items():
var tween = create_tween()
# Because set_parallel is false, these run one-by-one
for child in get_children():
child.scale = Vector2.ZERO
tween.tween_property(child, "scale", Vector2.ONE, 0.15)\
.set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
# Expert: Use a tiny interval if you want them to overlap slightly
# tween.set_parallel(true).tween_interval(0.05).set_parallel(false)
# text_counter_method_tween.gd
# Animating abstract values using tween_method [Score Counters]
extends Label
# EXPERT NOTE: Use tween_method to animate values that aren't
# direct properties, like UI text or custom shader parameters.
func update_score(target: int):
var curr_score = int(text.split(": ")[1])
var tween = create_tween()
# Calls '_set_score_text' with an interpolated int value
tween.tween_method(_set_score_text, curr_score, target, 1.5)\
.set_trans(Tween.TRANS_EXPO).set_ease(Tween.EASE_OUT)
func _set_score_text(val: int):
text = "Score: " + str(val)
# time_scale_ignored_ui.gd
# Ensuring UI tweens run while the game is paused [Engine.time_scale]
extends Control
# EXPERT NOTE: If you pause by setting Engine.time_scale = 0,
# standard tweens freeze. Use set_ignore_time_scale(true) for UI.
func open_pause_menu():
Engine.time_scale = 0 # Game world freezes
var tween = create_tween()
tween.set_ignore_time_scale(true) # This tween keeps running
tween.tween_property(self, "position:x", 0, 0.5)\
.set_trans(Tween.TRANS_QUART).set_ease(Tween.EASE_OUT)
func close_pause_menu():
Engine.time_scale = 1.0
queue_free()
# skills/tweening/scripts/tween_builder.gd
extends Node
## Tween Builder Expert Pattern
## Fluent API for complex tween chains with parallel and sequential operations.
class_name TweenBuilder
static func create_sequential() -> Tween:
var tween := Engine.get_main_loop().create_tween()
tween.set_parallel(false)
return tween
static func create_parallel() -> Tween:
var tween := Engine.get_main_loop().create_tween()
tween.set_parallel(true)
return tween
static func fade_out(node: CanvasItem, duration := 0.5) -> Tween:
var tween := create_sequential()
tween.tween_property(node, "modulate:a", 0.0, duration)
return tween
static func fade_in(node: CanvasItem, duration := 0.5) -> Tween:
var tween := create_sequential()
tween.tween_property(node, "modulate:a", 1.0, duration)
return tween
static func bounce_scale(node: Node, duration := 0.3, scale_multiplier := 1.2) -> Tween:
var original_scale = node.scale if node.has("scale") else Vector2.ONE
var tween := create_sequential()
tween.tween_property(node, "scale", original_scale * scale_multiplier, duration * 0.5)\
.set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
tween.tween_property(node, "scale", original_scale, duration * 0.5)\
.set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT)
return tween
static func shake(node: Node2D, duration := 0.3, intensity := 5.0) -> Tween:
var original_pos := node.position
var tween := create_sequential()
var shake_count := int(duration / 0.05)
for i in shake_count:
var offset := Vector2(
randf_range(-intensity, intensity),
randf_range(-intensity, intensity)
)
tween.tween_property(node, "position", original_pos + offset, 0.05)
tween.tween_property(node, "position", original_pos, 0.05)
return tween
static func chain_with_callback(tweens: Array[Tween], callbacks: Array[Callable]) -> void:
if tweens.size() != callbacks.size():
push_error("Tween and callback arrays must match in size")
return
for i in tweens.size():
if i < callbacks.size() and callbacks[i].is_valid():
tweens[i].finished.connect(callbacks[i])
## EXPERT USAGE:
## # Simple fade
## TweenBuilder.fade_out($Sprite)
##
## # Button press feedback
## TweenBuilder.bounce_scale($Button, 0.2, 1.1)
##
## # Complex chain
## var t1 := TweenBuilder.fade_in($Panel)
## var t2 := TweenBuilder.bounce_scale($Panel/Title)
## t1.finished.connect(func(): t2.play())