@icon("res://addons/TweenFX/icon.png") ## TweenFX — A juicy tween animation library for Godot 4. ## [br][br] ## Provides one-shot and looping animations for CanvasItem nodes. ## Call any animation directly, stop it explicitly, or await its completion. ## [br][br] ## All animations are tracked automatically. Use [method stop] and [method stop_all] ## to interrupt running animations, and [method is_playing] to check their state. ## [br][br] ## [b]Basic usage:[/b] ## [codeblock] ## TweenFX.shake(node) ## TweenFX.shake(node, 0.5, 20.0, 8) ## await TweenFX.shake(node).finished ## [/codeblock] extends Node const TweenManager = preload("res://addons/TweenFX/TweenManager.gd") #region ENUM ## Some animations support [b]PlayState[/b] for interactive use. ## [b]ENTER[/b] and [b]EXIT[/b] are designed as pairs.[br] ## [i]Pass the target property explicitly so TweenFX always knows where to go regardless of the node's current state. enum PlayState { FULL, # play complete animation ENTER, # animate to target state and hold EXIT # animate back to original } enum Animations { POP_IN, POP_OUT, PUNCH_IN, PUNCH_OUT, FADE_IN, FADE_OUT, DROP_IN, DROP_OUT, JUMP_SCARE, SPIN, SKEW, VANISH, SHAKE, PULSATE, JITTER, JELLY, FLIP, HOP, BLINK, SQUASH, STRETCH, SNAP, COLOR_CYCLE, HEARTBEAT, SWING, CHARGE_UP, RICOCHET, GLITCH, SPOTLIGHT, WAVE_DISTORT, WIGGLE, FLOAT_BOB, GLOW_PULSE, TWIST, ROTATE_HOP, EXPLODE, BLACK_HOLE, MELT, TV_SHUTDOWN, MAD_HELICO, SPIN_BOUNCE, IDLE_RUBBER, BUBBLE_ASCEND, CREEP_OUT, RUBBER_BAND, FIDGET, DEFLATE, DRUNK, IMPACT_LAND, BREATHE, SWAY, FLICKER, CRITICAL_HIT, UPGRADE, FOLD_IN, FOLD_OUT, ALARM, POINT, TADA, GHOST, ATTRACT, ORBIT, PRESS, PRESS_ROTATE, MAGNETIC_PULL, HEADSHAKE } enum AnimationType { ONE_SHOT, LOOPING } enum NodeRequirement { CANVAS_ITEM, NODE_2D } #endregion #region DICT const ANIMATION_TYPES: Dictionary = { Animations.POP_IN: AnimationType.ONE_SHOT, Animations.POP_OUT: AnimationType.ONE_SHOT, Animations.PUNCH_IN: AnimationType.ONE_SHOT, Animations.PUNCH_OUT: AnimationType.ONE_SHOT, Animations.FADE_IN: AnimationType.ONE_SHOT, Animations.FADE_OUT: AnimationType.ONE_SHOT, Animations.DROP_IN: AnimationType.ONE_SHOT, Animations.DROP_OUT: AnimationType.ONE_SHOT, Animations.JUMP_SCARE: AnimationType.ONE_SHOT, Animations.SPIN: AnimationType.ONE_SHOT, Animations.SKEW: AnimationType.ONE_SHOT, Animations.VANISH: AnimationType.ONE_SHOT, Animations.SHAKE: AnimationType.ONE_SHOT, Animations.PULSATE: AnimationType.ONE_SHOT, Animations.JITTER: AnimationType.ONE_SHOT, Animations.JELLY: AnimationType.ONE_SHOT, Animations.FLIP: AnimationType.ONE_SHOT, Animations.HOP: AnimationType.ONE_SHOT, Animations.BLINK: AnimationType.ONE_SHOT, Animations.SQUASH: AnimationType.ONE_SHOT, Animations.STRETCH: AnimationType.ONE_SHOT, Animations.SNAP: AnimationType.ONE_SHOT, Animations.CHARGE_UP: AnimationType.ONE_SHOT, Animations.RICOCHET: AnimationType.ONE_SHOT, Animations.GLITCH: AnimationType.ONE_SHOT, Animations.SPOTLIGHT: AnimationType.ONE_SHOT, Animations.TWIST: AnimationType.ONE_SHOT, Animations.EXPLODE: AnimationType.ONE_SHOT, Animations.BLACK_HOLE: AnimationType.ONE_SHOT, Animations.TV_SHUTDOWN: AnimationType.ONE_SHOT, Animations.CREEP_OUT: AnimationType.ONE_SHOT, Animations.RUBBER_BAND: AnimationType.ONE_SHOT, Animations.FIDGET: AnimationType.ONE_SHOT, Animations.DEFLATE: AnimationType.ONE_SHOT, Animations.DRUNK: AnimationType.ONE_SHOT, Animations.IMPACT_LAND: AnimationType.ONE_SHOT, Animations.CRITICAL_HIT: AnimationType.ONE_SHOT, Animations.UPGRADE: AnimationType.ONE_SHOT, Animations.FOLD_IN: AnimationType.ONE_SHOT, Animations.FOLD_OUT: AnimationType.ONE_SHOT, Animations.POINT: AnimationType.ONE_SHOT, Animations.TADA: AnimationType.ONE_SHOT, Animations.PRESS: AnimationType.ONE_SHOT, Animations.PRESS_ROTATE: AnimationType.ONE_SHOT, Animations.MAGNETIC_PULL: AnimationType.ONE_SHOT, Animations.HEADSHAKE: AnimationType.ONE_SHOT, Animations.COLOR_CYCLE: AnimationType.LOOPING, Animations.HEARTBEAT: AnimationType.LOOPING, Animations.SWING: AnimationType.LOOPING, Animations.WAVE_DISTORT: AnimationType.LOOPING, Animations.WIGGLE: AnimationType.LOOPING, Animations.FLOAT_BOB: AnimationType.LOOPING, Animations.GLOW_PULSE: AnimationType.LOOPING, Animations.ROTATE_HOP: AnimationType.LOOPING, Animations.SPIN_BOUNCE: AnimationType.LOOPING, Animations.MAD_HELICO: AnimationType.LOOPING, Animations.MELT: AnimationType.LOOPING, Animations.IDLE_RUBBER: AnimationType.LOOPING, Animations.BUBBLE_ASCEND: AnimationType.LOOPING, Animations.BREATHE: AnimationType.LOOPING, Animations.SWAY: AnimationType.LOOPING, Animations.FLICKER: AnimationType.LOOPING, Animations.ALARM: AnimationType.LOOPING, Animations.GHOST: AnimationType.LOOPING, Animations.ATTRACT: AnimationType.LOOPING, Animations.ORBIT: AnimationType.LOOPING, } const ANIMATION_REQUIREMENTS: Dictionary = { Animations.WAVE_DISTORT: NodeRequirement.NODE_2D, Animations.SKEW: NodeRequirement.NODE_2D, } #endregion #region LOGIC ## Stops a specific animation running on the node. func stop(node: CanvasItem, anim: Animations) -> void: TweenManager.stop(node, anim) ## Stops all animations currently running on the node. func stop_all(node: CanvasItem) -> void: TweenManager.stop_all(node) ## Returns true if the given animation is currently playing on the node. func is_playing(node: CanvasItem, anim: Animations) -> bool: return TweenManager.is_playing(node, anim) ## Returns true if the animation loops indefinitely until stopped. func is_looping_type(anim: Animations) -> bool: return ANIMATION_TYPES.get(anim, AnimationType.ONE_SHOT) == AnimationType.LOOPING ## Returns true if the animation requires a Node2D target instead of CanvasItem. func requires_node2d(anim: Animations) -> bool: return ANIMATION_REQUIREMENTS.get(anim, NodeRequirement.CANVAS_ITEM) == NodeRequirement.NODE_2D #endregion #region TWEEN ANIMATIONS #region Looping ## Goes through the colors. func color_cycle(node: CanvasItem, duration: float = 3.0, saturation: float = 1.0, value: float = 1.0) -> Tween: TweenManager.stop(node, Animations.COLOR_CYCLE) var tween := node.create_tween() tween.set_loops() tween.set_ease(Tween.EASE_IN_OUT) tween.set_trans(Tween.TRANS_SINE) var colors = [ Color.from_hsv(0.0, saturation, value), Color.from_hsv(0.17, saturation, value), Color.from_hsv(0.33, saturation, value), Color.from_hsv(0.5, saturation, value), Color.from_hsv(0.67, saturation, value), Color.from_hsv(0.83, saturation, value), Color.from_hsv(1.0, saturation, value), ] for color in colors: tween.tween_property(node, "modulate", color, duration / colors.size()) TweenManager.track(node, Animations.COLOR_CYCLE, tween) return tween ## Simulates a heartbeat with two rhythmic pulses. func heartbeat(node: CanvasItem, duration: float = 1.0, strength: float = 0.2) -> Tween: TweenManager.stop(node, Animations.HEARTBEAT) var original_scale: Vector2 = node.scale var tween := node.create_tween() tween.set_loops() tween.tween_property(node, "scale", original_scale * (1 + strength), 0.15).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT) tween.tween_property(node, "scale", original_scale, 0.15).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN) tween.tween_property(node, "scale", original_scale, 0.1) tween.tween_property(node, "scale", original_scale * (1 + strength * 0.6), 0.1).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT) tween.tween_property(node, "scale", original_scale, 0.1).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN) tween.tween_property(node, "scale", original_scale, duration - 0.6) TweenManager.track(node, Animations.HEARTBEAT, tween) return tween ## Rotates the node back and forth like a pendulum. func swing(node: CanvasItem, duration: float = 1.0, angle: float = 30.0) -> Tween: TweenManager.stop(node, Animations.SWING) var original_rotation : float = node.rotation_degrees var tween := node.create_tween() tween.set_loops() tween.set_ease(Tween.EASE_IN_OUT) tween.set_trans(Tween.TRANS_SINE) tween.tween_property(node, "rotation_degrees", original_rotation + angle, duration * 0.5) tween.tween_property(node, "rotation_degrees", original_rotation - angle, duration) tween.tween_property(node, "rotation_degrees", original_rotation, duration * 0.5) TweenManager.track(node, Animations.SWING, tween) return tween ## Applies a wave-like distortion effect to the node. func wave_distort(node: Node2D, duration: float = 1.0, amplitude: float = 0.1) -> Tween: TweenManager.stop(node, Animations.WAVE_DISTORT) var original_scale: Vector2 = node.scale var tween := node.create_tween() tween.set_loops() tween.set_ease(Tween.EASE_IN_OUT) tween.set_trans(Tween.TRANS_SINE) tween.tween_property(node, "scale:x", original_scale.x * (1 + amplitude), duration * 0.25) tween.parallel().tween_property(node, "scale:y", original_scale.y * (1 - amplitude/2), duration * 0.25) tween.parallel().tween_property(node, "skew", amplitude/2, duration * 0.25) tween.tween_property(node, "scale:x", original_scale.x * (1 - amplitude/2), duration * 0.25) tween.parallel().tween_property(node, "scale:y", original_scale.y * (1 + amplitude), duration * 0.25) tween.parallel().tween_property(node, "skew", -amplitude/2, duration * 0.25) tween.tween_property(node, "scale:x", original_scale.x * (1 - amplitude), duration * 0.25) tween.parallel().tween_property(node, "scale:y", original_scale.y * (1 - amplitude/2), duration * 0.25) tween.parallel().tween_property(node, "skew", amplitude/2, duration * 0.25) tween.tween_property(node, "scale:x", original_scale.x, duration * 0.25) tween.parallel().tween_property(node, "scale:y", original_scale.y, duration * 0.25) tween.parallel().tween_property(node, "skew", 0.0, duration * 0.25) TweenManager.track(node, Animations.WAVE_DISTORT, tween) return tween ## Slightly rotates the node back and forth. func wiggle(node: CanvasItem, duration: float = 1.2) -> Tween: TweenManager.stop(node, Animations.WIGGLE) var tween := node.create_tween() tween.set_loops() tween.tween_property(node, "rotation_degrees", 5.0, duration * 0.25).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.tween_property(node, "rotation_degrees", -5.0, duration * 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.tween_property(node, "rotation_degrees", 0.0, duration * 0.25).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) TweenManager.track(node, Animations.WIGGLE, tween) return tween ## Makes the node float up and down in a looping motion. func float_bob(node: CanvasItem, duration: float = 2.0, height: float = 5.0) -> Tween: TweenManager.stop(node, Animations.FLOAT_BOB) var original_pos: Vector2 = node.position var tween := node.create_tween() tween.set_loops() tween.tween_property(node, "position:y", original_pos.y - height, duration * 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.tween_property(node, "position:y", original_pos.y + height, duration * 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) TweenManager.track(node, Animations.FLOAT_BOB, tween) return tween ## Gently pulses the node's scale and opacity in a loop. func glow_pulse(node: CanvasItem, duration: float = 1.2, scale_amt: float = 0.05, alpha_amt: float = 0.3) -> Tween: TweenManager.stop(node, Animations.GLOW_PULSE) var original_scale: Vector2 = node.scale var original_alpha: float = node.modulate.a var tween := node.create_tween() tween.set_loops() tween.tween_property(node, "scale", original_scale * (1.0 + scale_amt), duration * 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.parallel().tween_property(node, "modulate:a", original_alpha * (1.0 - alpha_amt), duration * 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.tween_property(node, "scale", original_scale, duration * 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.parallel().tween_property(node, "modulate:a", original_alpha, duration * 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) TweenManager.track(node, Animations.GLOW_PULSE, tween) return tween ## Rotates a bit while doing a mini-hop. Good for idle feedback. func rotate_hop(node: CanvasItem, duration: float = 0.4, angle: float = 15.0, height: float = 10.0) -> Tween: TweenManager.stop(node, Animations.ROTATE_HOP) var start_pos = node.position var tween := node.create_tween() tween.set_loops() tween.tween_property(node, "rotation_degrees", angle, duration * 0.25) tween.parallel().tween_property(node, "position:y", start_pos.y - height, duration * 0.25) tween.tween_property(node, "rotation_degrees", -angle, duration * 0.25) tween.parallel().tween_property(node, "position:y", start_pos.y + height, duration * 0.25) tween.tween_property(node, "rotation_degrees", 0, duration * 0.25) tween.parallel().tween_property(node, "position:y", start_pos.y, duration * 0.25) TweenManager.track(node, Animations.ROTATE_HOP, tween) return tween ## Pure entropy: spin, random jitter, squash-stretch bounce. func spin_bounce(node: CanvasItem, duration: float = 0.6, bounce_scale: float = 0.2, spin_speed: float = 180.0) -> Tween: TweenManager.stop(node, Animations.SPIN_BOUNCE) var original_scale: Vector2 = node.scale var tween := node.create_tween() tween.set_loops() tween.parallel().tween_property(node, "rotation_degrees", node.rotation_degrees + spin_speed, duration).set_trans(Tween.TRANS_LINEAR) tween.parallel().tween_property(node, "scale", original_scale * Vector2(1.0 + randf_range(-bounce_scale, bounce_scale), 1.0 + randf_range(-bounce_scale, bounce_scale)), duration * 0.5) tween.tween_property(node, "scale", original_scale, duration * 0.5) TweenManager.track(node, Animations.SPIN_BOUNCE, tween) return tween ## Makes the object look like it's trying to fly off in a panic. func mad_helico(node: CanvasItem, duration: float = 0.6, spin_speed: float = 1080.0, bob_height: float = 5.0) -> Tween: TweenManager.stop(node, Animations.MAD_HELICO) var original_pos: Vector2 = node.position var tween := node.create_tween() tween.set_loops() tween.parallel().tween_property(node, "rotation_degrees", node.rotation_degrees + spin_speed, duration).set_trans(Tween.TRANS_LINEAR) tween.tween_property(node, "position:y", original_pos.y - bob_height, duration * 0.3).set_trans(Tween.TRANS_SINE) tween.tween_property(node, "position:y", original_pos.y + bob_height, duration * 0.3).set_trans(Tween.TRANS_SINE) tween.tween_property(node, "position:y", original_pos.y, duration * 0.3).set_trans(Tween.TRANS_SINE) TweenManager.track(node, Animations.MAD_HELICO, tween) return tween ## Makes objects drip down slowly and squash — like they're melting. func melt(node: CanvasItem, duration: float = 2.0, melt_distance: float = 20.0) -> Tween: TweenManager.stop(node, Animations.MELT) var original_pos: Vector2 = node.position var original_scale: Vector2 = node.scale var tween := node.create_tween() tween.set_loops() tween.parallel().tween_property(node, "position:y", original_pos.y + melt_distance, duration * 0.5).set_trans(Tween.TRANS_LINEAR) tween.parallel().tween_property(node, "scale:y", original_scale.y * 1.3, duration * 0.5) tween.tween_property(node, "position:y", original_pos.y, duration * 0.5) tween.tween_property(node, "scale:y", original_scale.y, duration * 0.5) TweenManager.track(node, Animations.MELT, tween) return tween ## Gives the object rubbery springy movement. func idle_rubber(node: CanvasItem, duration: float = 0.6, strength: float = 0.1) -> Tween: TweenManager.stop(node, Animations.IDLE_RUBBER) var original_pos: Vector2 = node.position var original_scale: Vector2 = node.scale var tween := node.create_tween() tween.set_loops() tween.tween_property(node, "position:x", original_pos.x + 6, duration * 0.3) tween.parallel().tween_property(node, "scale", original_scale * Vector2(1.1, 0.9), duration * 0.3) tween.tween_property(node, "position:x", original_pos.x - 6, duration * 0.3) tween.parallel().tween_property(node, "scale", original_scale * Vector2(0.9, 1.1), duration * 0.3) tween.tween_property(node, "position:x", original_pos.x, duration * 0.3) tween.parallel().tween_property(node, "scale", original_scale, duration * 0.3) TweenManager.track(node, Animations.IDLE_RUBBER, tween) return tween ## Floats upward with a bit of distortion. func bubble_ascend(node: CanvasItem, duration: float = 2.0, height: float = 15.0) -> Tween: TweenManager.stop(node, Animations.BUBBLE_ASCEND) var original_pos: Vector2 = node.position var original_scale: Vector2 = node.scale var tween := node.create_tween() tween.set_loops() tween.tween_property(node, "position:y", original_pos.y - height, duration * 0.6).set_trans(Tween.TRANS_SINE) tween.parallel().tween_property(node, "scale:y", original_scale.y * 0.95, duration * 0.3) tween.tween_property(node, "position:y", original_pos.y, duration * 0.4).set_trans(Tween.TRANS_SINE) tween.parallel().tween_property(node, "scale", original_scale, duration * 0.2) TweenManager.track(node, Animations.BUBBLE_ASCEND, tween) return tween ## Very subtle slow scale pulse. Good for alive/idle state. func breathe(node: CanvasItem, duration: float = 3.0, strength: float = 0.1) -> Tween: TweenManager.stop(node, Animations.BREATHE) var original_scale: Vector2 = node.scale var tween := node.create_tween() tween.set_loops() tween.tween_property(node, "scale", original_scale * (1.0 + strength), duration * 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.tween_property(node, "scale", original_scale, duration * 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) TweenManager.track(node, Animations.BREATHE, tween) return tween ## Slowly fades to low alpha and back, ethereal feel. func ghost(node: CanvasItem, duration: float = 2.0, min_alpha: float = 0.2) -> Tween: TweenManager.stop(node, Animations.GHOST) var original_alpha: float = node.modulate.a var tween := node.create_tween() tween.set_loops() tween.tween_property(node, "modulate:a", min_alpha, duration * 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.tween_property(node, "modulate:a", original_alpha, duration * 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) TweenManager.track(node, Animations.GHOST, tween) return tween ## Grows slightly then pulses to draw attention. func attract(node: CanvasItem, duration: float = 1.2, strength: float = 0.12) -> Tween: TweenManager.stop(node, Animations.ATTRACT) var original_scale: Vector2 = node.scale var tween := node.create_tween() tween.set_loops() tween.tween_property(node, "scale", original_scale * (1.0 + strength), duration * 0.3).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) tween.tween_property(node, "scale", original_scale * (1.0 + strength * 0.5), duration * 0.2).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.tween_property(node, "scale", original_scale * (1.0 + strength), duration * 0.2).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.tween_property(node, "scale", original_scale, duration * 0.3).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN) TweenManager.track(node, Animations.ATTRACT, tween) return tween ## Orbits the node in a circle around its original position. func orbit(node: CanvasItem, duration: float = 2.0, radius: float = 30.0, start_angle: float = 0.0) -> Tween: TweenManager.stop(node, Animations.ORBIT) var original_pos: Vector2 = node.position var tween := node.create_tween() tween.set_loops() var steps = 60 for i in range(steps + 1): var angle = start_angle + (float(i) / steps) * TAU var offset = Vector2(cos(angle), sin(angle)) * radius tween.tween_property(node, "position", original_pos + offset, duration / steps).set_trans(Tween.TRANS_LINEAR) TweenManager.track(node, Animations.ORBIT, tween) return tween ## Gentle organic sway like a plant in wind. func sway(node: CanvasItem, duration: float = 2.0, angle: float = 8.0) -> Tween: TweenManager.stop(node, Animations.SWAY) var tween := node.create_tween() tween.set_loops() tween.tween_property(node, "rotation_degrees", angle, duration * 0.4).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.tween_property(node, "rotation_degrees", -angle * 0.6, duration * 0.4).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.tween_property(node, "rotation_degrees", angle * 0.3, duration * 0.1).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.tween_property(node, "rotation_degrees", 0.0, duration * 0.1).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) TweenManager.track(node, Animations.SWAY, tween) return tween ## Random opacity flicker like a candle or broken light. func flicker(node: CanvasItem, duration: float = 0.08, min_alpha: float = 0.4, steps: int = 8) -> Tween: TweenManager.stop(node, Animations.FLICKER) var original_alpha: float = node.modulate.a var tween := node.create_tween() tween.set_loops() for i in range(steps): var alpha = randf_range(min_alpha, original_alpha) tween.tween_property(node, "modulate:a", alpha, duration * randf_range(0.5, 1.5)) TweenManager.track(node, Animations.FLICKER, tween) return tween ## Rapid red flash loop, urgent warning. func alarm(node: CanvasItem, duration: float = 0.3, color : Color = Color(2.0, 0.2, 0.2, 1.0)) -> Tween: TweenManager.stop(node, Animations.ALARM) var original_color: Color = node.modulate var tween := node.create_tween() tween.set_loops() tween.tween_property(node, "modulate", color, duration * 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN) tween.tween_property(node, "modulate", original_color, duration * 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT) TweenManager.track(node, Animations.ALARM, tween) return tween #endregion #region One-Shot ## Object fades into the dark and shrinks away. func creep_out(node: CanvasItem, duration: float = 1.0) -> Tween: TweenManager.stop(node, Animations.CREEP_OUT) var original_scale: Vector2 = node.scale var tween := node.create_tween() tween.tween_property(node, "modulate", Color(0.2, 0.2, 0.2, 1), duration * 0.5) tween.tween_property(node, "scale", original_scale * 0.8, duration * 0.5) tween.tween_property(node, "modulate:a", 0.0, duration * 0.3) TweenManager.track(node, Animations.CREEP_OUT, tween) return tween ## Makes the object look like it's being shut down like a cartoon TV. func tv_shutdown(node: CanvasItem, duration: float = 0.5) -> Tween: TweenManager.stop(node, Animations.TV_SHUTDOWN) var original_scale: Vector2 = node.scale var tween := node.create_tween() tween.tween_property(node, "scale", original_scale * Vector2(1.3, 0.7), duration * 0.25).set_trans(Tween.TRANS_ELASTIC) tween.tween_property(node, "scale", original_scale * Vector2(0.7, 1.3), duration * 0.25).set_trans(Tween.TRANS_ELASTIC) tween.tween_property(node, "scale", Vector2.ZERO, duration * 0.2).set_trans(Tween.TRANS_BOUNCE).set_ease(Tween.EASE_OUT) TweenManager.track(node, Animations.TV_SHUTDOWN, tween) return tween ## Spins and collapses into nothingness. func black_hole(node: CanvasItem, duration: float = 0.8) -> Tween: TweenManager.stop(node, Animations.BLACK_HOLE) var tween := node.create_tween() tween.parallel().tween_property(node, "scale", Vector2.ZERO, duration).set_trans(Tween.TRANS_EXPO).set_ease(Tween.EASE_IN) tween.parallel().tween_property(node, "modulate:a", 0.0, duration) tween.parallel().tween_property(node, "rotation_degrees", node.rotation_degrees + 720, duration) TweenManager.track(node, Animations.BLACK_HOLE, tween) return tween ## Boom! func explode(node: CanvasItem, duration: float = 0.4, scale_amt: float = 1.8) -> Tween: TweenManager.stop(node, Animations.EXPLODE) var tween := node.create_tween() tween.tween_property(node, "scale", Vector2.ONE * scale_amt, duration * 0.3).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) tween.parallel().tween_property(node, "rotation_degrees", randf_range(-10, 10), duration * 0.2) tween.tween_property(node, "scale", Vector2.ONE * 0.5, duration * 0.4).set_trans(Tween.TRANS_EXPO).set_ease(Tween.EASE_IN) tween.parallel().tween_property(node, "modulate:a", 0.0, duration * 0.4).set_trans(Tween.TRANS_LINEAR) TweenManager.track(node, Animations.EXPLODE, tween) return tween ## Builds up energy visually before a release. func charge_up(node: CanvasItem, duration: float = 1.0) -> Tween: TweenManager.stop(node, Animations.CHARGE_UP) var original_scale: Vector2 = node.scale var original_color: Color = node.modulate var tween := node.create_tween() tween.tween_property(node, "scale", original_scale * 0.8, duration * 0.4).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN) tween.parallel().tween_property(node, "modulate", Color(1.5, 1.5, 0.5, 1.0), duration * 0.4).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN) for i in range(3): var pulse_scale = 0.05 * (i + 1) tween.tween_property(node, "scale", original_scale * (0.8 + pulse_scale), duration * 0.1) tween.tween_property(node, "scale", original_scale * 0.8, duration * 0.1) tween.tween_property(node, "scale", original_scale * 1.5, duration * 0.2).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) tween.parallel().tween_property(node, "modulate", Color(2.0, 2.0, 1.0, 1.0), duration * 0.2) tween.tween_property(node, "scale", original_scale, duration * 0.2).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT) tween.parallel().tween_property(node, "modulate", original_color, duration * 0.2).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT) TweenManager.track(node, Animations.CHARGE_UP, tween) return tween ## Shrinks and wobbles like it took a hit. func punch_out(node: CanvasItem, duration: float = 0.5, min_scale: float = 0.5) -> Tween: TweenManager.stop(node, Animations.PUNCH_OUT) var original_scale: Vector2 = node.scale var original_pos: Vector2 = node.position var tween := node.create_tween() tween.tween_property(node, "scale", original_scale * min_scale, duration * 0.2).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN) for i in range(3): var jitter = Vector2(randf_range(-1, 1), randf_range(-1, 1)) * 5.0 tween.tween_property(node, "position", original_pos + jitter, duration * 0.1) tween.tween_property(node, "scale", original_scale, duration * 0.5).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT) tween.parallel().tween_property(node, "position", original_pos, duration * 0.1) TweenManager.track(node, Animations.PUNCH_OUT, tween) return tween ## Bounces around as if hitting walls. func ricochet(node: CanvasItem, duration: float = 0.8, strength: float = 30.0, bounces: int = 4) -> Tween: TweenManager.stop(node, Animations.RICOCHET) var original_pos: Vector2 = node.position var tween := node.create_tween() for i in range(bounces): var random_dir = Vector2(randf_range(-1, 1), randf_range(-1, 1)).normalized() var target_pos = original_pos + random_dir * strength tween.tween_property(node, "position", target_pos, (duration / bounces) * 0.8).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT) tween.tween_property(node, "position", original_pos, duration * 0.2).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) TweenManager.track(node, Animations.RICOCHET, tween) return tween ## Simulates a digital glitch with color and position jitter. func glitch(node: CanvasItem, duration: float = 1.0, intensity: float = 10.0, frames: int = 12) -> Tween: TweenManager.stop(node, Animations.GLITCH) var original_pos: Vector2 = node.position var original_color: Color = node.modulate var tween := node.create_tween() for i in range(frames): var offset = Vector2(randf_range(-1, 1), randf_range(-1, 1)) * intensity var color_shift = Color(randf_range(0.8, 1.2), randf_range(0.8, 1.2), randf_range(0.8, 1.2), 1.0) var glitch_time = duration / frames * 0.5 tween.tween_property(node, "position", original_pos + offset, glitch_time) tween.parallel().tween_property(node, "modulate", color_shift, glitch_time) tween.tween_property(node, "position", original_pos, glitch_time) tween.parallel().tween_property(node, "modulate", original_color, glitch_time) tween.tween_property(node, "position", original_pos, 0.1) tween.parallel().tween_property(node, "modulate", original_color, 0.1) TweenManager.track(node, Animations.GLITCH, tween) return tween ## A fast zoom and shake for a startling impact. func jump_scare(node: CanvasItem, duration: float = 0.4, intensity: float = 1.3) -> Tween: TweenManager.stop(node, Animations.JUMP_SCARE) var original_scale: Vector2 = node.scale var original_pos: Vector2 = node.position var tween := node.create_tween() tween.tween_property(node, "scale", original_scale * intensity, duration * 0.3).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) for i in range(6): var shake_offset = Vector2(randf_range(-1, 1), randf_range(-1, 1)).normalized() * 10.0 tween.parallel().tween_property(node, "position", original_pos + shake_offset, duration * 0.05) tween.tween_property(node, "position", original_pos, duration * 0.05) tween.parallel().tween_property(node, "scale", original_scale, duration * 0.7).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT) TweenManager.track(node, Animations.JUMP_SCARE, tween) return tween ## Highlights the node with a glowing effect. Supports PlayState for hold/release. func spotlight(node: CanvasItem, duration: float = 1.0, glow: Color = Color(1.5, 1.5, 1.5, 1.0), state: PlayState = PlayState.FULL, use_self_modulate : bool = false) -> Tween: TweenManager.stop(node, Animations.SPOTLIGHT) var original_color: Color = node.modulate if not use_self_modulate else node.self_modulate var node_modulate_property: String = "modulate" if not use_self_modulate else "self_modulate" var tween := node.create_tween() match state: PlayState.ENTER: tween.tween_property(node, node_modulate_property, glow, duration * 0.3).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN) PlayState.EXIT: tween.tween_property(node, node_modulate_property, glow, duration * 0.3).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT) PlayState.FULL: tween.tween_property(node, node_modulate_property, glow, duration * 0.3).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN) tween.tween_property(node, node_modulate_property, glow, duration * 0.4) tween.tween_property(node, node_modulate_property, original_color, duration * 0.3).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT) TweenManager.track(node, Animations.SPOTLIGHT, tween) return tween ## Flips the node along the given axis. func flip(node: CanvasItem, duration: float = 0.4, axis: String = "x", flips: int = 1) -> Tween: TweenManager.stop(node, Animations.FLIP) var original_scale: Vector2 = node.scale var tween := node.create_tween() for i in range(flips): tween.tween_property(node, "scale:" + axis, -original_scale[axis], duration * 0.5 / flips).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN) tween.tween_property(node, "scale:" + axis, original_scale[axis], duration * 0.5 / flips).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT) TweenManager.track(node, Animations.FLIP, tween) return tween ## Makes the node hop in a given direction and land back. func hop(node: CanvasItem, duration: float = 0.4, height: float = 20.0, direction: Vector2 = Vector2.UP) -> Tween: TweenManager.stop(node, Animations.HOP) var original_pos: Vector2 = node.position var offset = direction.normalized() * height var tween := node.create_tween() tween.tween_property(node, "position", original_pos + offset, duration * 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT) tween.tween_property(node, "position", original_pos, duration * 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN) TweenManager.track(node, Animations.HOP, tween) return tween ## Rapidly blinks the node's opacity. func blink(node: CanvasItem, duration: float = 0.1, times: int = 3, from: float = 0.0, to: float = 1.0) -> Tween: TweenManager.stop(node, Animations.BLINK) var tween := node.create_tween() for i in range(times): tween.tween_property(node, "modulate:a", from, duration) tween.tween_property(node, "modulate:a", to, duration) TweenManager.track(node, Animations.BLINK, tween) return tween ## Squashes the node along one axis and stretches the other, then returns. func squash(node: CanvasItem, duration: float = 0.2, amount: float = 0.3, horizontal: bool = true) -> Tween: TweenManager.stop(node, Animations.SQUASH) var original_scale: Vector2 = node.scale var target = Vector2(original_scale.x * (1 + amount), original_scale.y * (1 - amount)) if horizontal else Vector2(original_scale.x * (1 - amount), original_scale.y * (1 + amount)) var tween := node.create_tween() tween.tween_property(node, "scale", target, duration * 0.5).set_trans(Tween.TRANS_SINE) tween.tween_property(node, "scale", original_scale, duration * 0.5).set_trans(Tween.TRANS_SINE) TweenManager.track(node, Animations.SQUASH, tween) return tween ## Stretches the node vertically, then returns to original scale. func stretch(node: CanvasItem, duration: float = 0.2, amount: float = 0.3) -> Tween: TweenManager.stop(node, Animations.STRETCH) var original_scale: Vector2 = node.scale var tween := node.create_tween() tween.tween_property(node, "scale", Vector2(original_scale.x * (1 - amount), original_scale.y * (1 + amount)), duration * 0.5).set_trans(Tween.TRANS_SINE) tween.tween_property(node, "scale", original_scale, duration * 0.5).set_trans(Tween.TRANS_SINE) TweenManager.track(node, Animations.STRETCH, tween) return tween ## Scales the node up quickly, then resets. Supports PlayState for hold/release. func snap(node: CanvasItem, duration: float = 0.1, scale: Vector2 = Vector2(1.3, 1.3), state: PlayState = PlayState.FULL) -> Tween: TweenManager.stop(node, Animations.SNAP) var original_scale: Vector2 = node.scale var tween := node.create_tween() match state: PlayState.ENTER: tween.tween_property(node, "scale", scale, duration).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) PlayState.EXIT: tween.tween_property(node, "scale", scale, duration).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_IN) PlayState.FULL: tween.tween_property(node, "scale", scale, duration).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) tween.tween_property(node, "scale", original_scale, duration).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_IN) TweenManager.track(node, Animations.SNAP, tween) return tween ## Quickly flashes the node's opacity. func flash(node: CanvasItem, duration: float = 0.1, flashes: int = 3) -> Tween: TweenManager.stop(node, Animations.BLINK) var tween := node.create_tween() for i in range(flashes): tween.tween_property(node, "modulate", Color(1, 1, 1, 0), duration) tween.tween_property(node, "modulate", Color(1, 1, 1, 1), duration) TweenManager.track(node, Animations.BLINK, tween) return tween ## Fades the node in from transparent. func fade_in(node: CanvasItem, duration: float = 0.5) -> Tween: TweenManager.stop(node, Animations.FADE_IN) node.modulate.a = 0.0 var tween := node.create_tween() tween.tween_property(node, "modulate:a", 1.0, duration).set_trans(Tween.TRANS_LINEAR).set_ease(Tween.EASE_IN) TweenManager.track(node, Animations.FADE_IN, tween) return tween ## Fades the node out to transparent. func fade_out(node: CanvasItem, duration: float = 0.5) -> Tween: TweenManager.stop(node, Animations.FADE_OUT) var tween := node.create_tween() tween.tween_property(node, "modulate:a", 0.0, duration).set_trans(Tween.TRANS_LINEAR).set_ease(Tween.EASE_OUT) TweenManager.track(node, Animations.FADE_OUT, tween) return tween ## Twists the node in place with a quick rotation. func twist(node: CanvasItem, duration: float = 0.4, angle: float = 30.0) -> Tween: TweenManager.stop(node, Animations.TWIST) var start = node.rotation_degrees var tween := node.create_tween() tween.tween_property(node, "rotation_degrees", start + angle, duration * 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT) tween.tween_property(node, "rotation_degrees", start, duration * 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN) TweenManager.track(node, Animations.TWIST, tween) return tween ## Expands and contracts the node once. func pulsate(node: CanvasItem, duration: float = 0.5, scale_factor: float = 1.2) -> Tween: TweenManager.stop(node, Animations.PULSATE) var original_scale: Vector2 = node.scale var tween := node.create_tween() tween.tween_property(node, "scale", original_scale * scale_factor, duration * 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.tween_property(node, "scale", original_scale, duration * 0.5).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) TweenManager.track(node, Animations.PULSATE, tween) return tween ## Rapid nervous jitter with rotation and scale micro-variations. func jitter(node: CanvasItem, duration: float = 0.5, amount: float = 5.0, times: int = 8) -> Tween: TweenManager.stop(node, Animations.JITTER) var original_position : Vector2 = node.position var original_rotation : float = node.rotation_degrees var original_scale: Vector2 = node.scale var tween := node.create_tween() for i in range(times): var offset = Vector2(randf_range(-amount, amount), randf_range(-amount, amount)) var rot = randf_range(-3.0, 3.0) var scl = original_scale * Vector2(randf_range(0.95, 1.05), randf_range(0.95, 1.05)) tween.tween_property(node, "position", original_position + offset, duration / times) tween.parallel().tween_property(node, "rotation_degrees", original_rotation + rot, duration / times) tween.parallel().tween_property(node, "scale", scl, duration / times) tween.tween_property(node, "position", original_position, 0.05) tween.parallel().tween_property(node, "rotation_degrees", original_rotation, 0.05) tween.parallel().tween_property(node, "scale", original_scale, 0.05) TweenManager.track(node, Animations.JITTER, tween) return tween ## Alternates between squash and stretch repeatedly, like jelly wobbling. func jelly(node: CanvasItem, duration: float = 0.6, amount: float = 0.3, cycles: int = 2) -> Tween: TweenManager.stop(node, Animations.JELLY) var original_scale: Vector2 = node.scale var tween := node.create_tween() for i in range(cycles): tween.tween_property(node, "scale", original_scale * Vector2(1.0 + amount, 1.0 - amount), duration * 0.25 / cycles).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.tween_property(node, "scale", original_scale * Vector2(1.0 - amount, 1.0 + amount), duration * 0.25 / cycles).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.tween_property(node, "scale", original_scale, duration * 0.2).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT) TweenManager.track(node, Animations.JELLY, tween) return tween ## Rotates the node one or more full revolutions. func spin(node: CanvasItem, duration: float = 0.5, revolutions: float = 1.0, clockwise: bool = true) -> Tween: TweenManager.stop(node, Animations.SPIN) var original_rotation : float = node.rotation_degrees var direction = 1.0 if clockwise else -1.0 var tween := node.create_tween() tween.tween_property(node, "rotation_degrees", original_rotation + 360.0 * revolutions * direction, duration).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN_OUT) tween.tween_property(node, "rotation_degrees", original_rotation, 0.0) TweenManager.track(node, Animations.SPIN, tween) return tween ## Scales in from zero with a slight overshoot. func pop_in(node: CanvasItem, duration: float = 0.3, overshoot: float = 0.1) -> Tween: TweenManager.stop(node, Animations.POP_IN) var original_scale: Vector2 = node.scale var original_alpha := node.modulate.a node.scale = Vector2.ZERO node.modulate.a = 0.0 var tween := node.create_tween() tween.tween_property(node, "scale", original_scale * (1.0 + overshoot), duration).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) tween.tween_property(node, "scale", original_scale, duration * 0.33) tween.parallel().tween_property(node, "modulate:a", original_alpha, duration * 0.66) TweenManager.track(node, Animations.POP_IN, tween) return tween ## Scales out to zero with a slight pull in before vanishing. func pop_out(node: CanvasItem, duration: float = 0.3, overshoot: float = 0.1) -> Tween: TweenManager.stop(node, Animations.POP_OUT) var original_scale: Vector2 = node.scale var tween := node.create_tween() tween.tween_property(node, "scale", original_scale * (1.0 + overshoot), duration * 0.2).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) tween.tween_property(node, "scale", Vector2.ZERO, duration * 0.8).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_IN) tween.parallel().tween_property(node, "modulate:a", 0.0, duration * 0.8) TweenManager.track(node, Animations.POP_OUT, tween) return tween ## Skews the node along X and Y axes temporarily. func skew(node: Node2D, duration: float = 0.3, skew_x: float = 0.5, skew_y: float = 0.5) -> Tween: TweenManager.stop(node, Animations.SKEW) var original_scale: Vector2 = node.scale var target_scale := original_scale * Vector2(1.0 + skew_x, 1.0 + skew_y) var tween := node.create_tween() tween.tween_property(node, "scale", target_scale, duration).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN_OUT) tween.tween_property(node, "scale", original_scale, duration).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN_OUT).set_delay(duration) TweenManager.track(node, Animations.SKEW, tween) return tween ## Fades out and scales down the node. func vanish(node: CanvasItem, duration: float = 0.4) -> Tween: TweenManager.stop(node, Animations.VANISH) var tween := node.create_tween() tween.tween_property(node, "modulate:a", 0.0, duration) tween.parallel().tween_property(node, "scale", Vector2(0.0, 0.0), duration).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN) TweenManager.track(node, Animations.VANISH, tween) return tween ## Quickly scales the node in with a slight bounce. func punch_in(node: CanvasItem, duration: float = 0.15, strength: float = 0.3) -> Tween: TweenManager.stop(node, Animations.PUNCH_IN) var original_scale: Vector2 = node.scale var tween := node.create_tween() tween.tween_property(node, "scale", original_scale * (1 + strength), duration).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT) tween.tween_property(node, "scale", original_scale, duration).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN) TweenManager.track(node, Animations.PUNCH_IN, tween) return tween ## Moves the node back and forth rapidly. ## axis: Vector2.RIGHT for horizontal only, Vector2.DOWN for vertical only, Vector2.ONE for both. func shake(node: CanvasItem, duration: float = 0.3, amount: float = 10.0, shakes: int = 5, axis: Vector2 = Vector2.ONE) -> Tween: TweenManager.stop(node, Animations.SHAKE) var original_pos: Vector2 = node.position var tween := node.create_tween() for i in range(shakes): var offset = Vector2( randf_range(-amount, amount) * axis.x, randf_range(-amount, amount) * axis.y ) tween.tween_property(node, "position", original_pos + offset, duration / (shakes * 2)) tween.tween_property(node, "position", original_pos, duration / (shakes * 2)) TweenManager.track(node, Animations.SHAKE, tween) return tween ## Drops the node from above into its position. func drop_in(node: CanvasItem, duration: float = 0.5, drop_height: float = 100.0, scale_distort: Vector2 = Vector2(1.2, 0.8)) -> Tween: TweenManager.stop(node, Animations.DROP_IN) var original_pos: Vector2 = node.position var original_scale: Vector2 = node.scale var original_alpha: float = node.modulate.a node.position = original_pos - Vector2(0, drop_height) node.scale = scale_distort node.modulate.a = 0.0 var tween := node.create_tween() tween.tween_property(node, "position", original_pos, duration).set_trans(Tween.TRANS_BOUNCE).set_ease(Tween.EASE_OUT) tween.parallel().tween_property(node, "scale", original_scale, duration * 0.6).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT) tween.parallel().tween_property(node, "modulate:a", original_alpha, duration * 0.4) TweenManager.track(node, Animations.DROP_IN, tween) return tween ## Drops the node downward and fades out. func drop_out(node: CanvasItem, duration: float = 0.5, drop_height: float = 100.0) -> Tween: TweenManager.stop(node, Animations.DROP_OUT) var original_pos: Vector2 = node.position var tween := node.create_tween() tween.tween_property(node, "position", original_pos + Vector2(0, drop_height), duration).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN) tween.parallel().tween_property(node, "modulate:a", 0.0, duration * 0.6) TweenManager.track(node, Animations.DROP_OUT, tween) return tween ## Stretches in one direction then snaps back with overshoot. func rubber_band(node: CanvasItem, duration: float = 0.5, strength: float = 0.4) -> Tween: TweenManager.stop(node, Animations.RUBBER_BAND) var original_scale: Vector2 = node.scale var tween := node.create_tween() tween.tween_property(node, "scale", original_scale * Vector2(1.0 + strength, 1.0 - strength * 0.5), duration * 0.3).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT) tween.tween_property(node, "scale", original_scale * Vector2(0.9, 1.1), duration * 0.2).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) tween.tween_property(node, "scale", original_scale * Vector2(1.05, 0.97), duration * 0.2).set_trans(Tween.TRANS_SINE) tween.tween_property(node, "scale", original_scale, duration * 0.3).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT) TweenManager.track(node, Animations.RUBBER_BAND, tween) return tween ## Rapidly cycles random rotations then snaps to original. Good for randomness feedback. func fidget(node: CanvasItem, duration: float = 0.8, spins: int = 6) -> Tween: TweenManager.stop(node, Animations.FIDGET) var original_rotation : float = node.rotation_degrees var tween := node.create_tween() for i in range(spins): var random_rot = randf_range(-25.0, 25.0) tween.tween_property(node, "rotation_degrees", original_rotation + random_rot, duration / spins).set_trans(Tween.TRANS_SINE) tween.tween_property(node, "rotation_degrees", original_rotation, duration * 0.2).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) TweenManager.track(node, Animations.FIDGET, tween) return tween ## Slowly shrinks Y while expanding X, like air leaving a balloon. func deflate(node: CanvasItem, duration: float = 0.6) -> Tween: TweenManager.stop(node, Animations.DEFLATE) var original_scale: Vector2 = node.scale var tween := node.create_tween() tween.tween_property(node, "scale", original_scale * Vector2(1.3, 0.2), duration * 0.6).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN) tween.tween_property(node, "scale", original_scale * Vector2(1.1, 0.15), duration * 0.2).set_trans(Tween.TRANS_SINE) tween.tween_property(node, "scale", original_scale, duration * 0.4).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT) TweenManager.track(node, Animations.DEFLATE, tween) return tween ## Slow random wobble on position and rotation. Good for status effects. func drunk(node: CanvasItem, duration: float = 0.8) -> Tween: TweenManager.stop(node, Animations.DRUNK) var original_pos: Vector2 = node.position var original_rot = node.rotation_degrees var tween := node.create_tween() for i in range(4): var offset = Vector2(randf_range(-8, 8), randf_range(-5, 5)) var rot = randf_range(-8, 8) tween.tween_property(node, "position", original_pos + offset, duration * 0.25).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.parallel().tween_property(node, "rotation_degrees", original_rot + rot, duration * 0.25).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.tween_property(node, "position", original_pos, duration * 0.2).set_trans(Tween.TRANS_SINE) tween.parallel().tween_property(node, "rotation_degrees", original_rot, duration * 0.2).set_trans(Tween.TRANS_SINE) TweenManager.track(node, Animations.DRUNK, tween) return tween ## Squash on hit, bounce up, then settle. Classic landing feel. func impact_land(node: CanvasItem, duration: float = 0.5) -> Tween: TweenManager.stop(node, Animations.IMPACT_LAND) var original_scale: Vector2 = node.scale var original_pos: Vector2 = node.position var tween := node.create_tween() tween.tween_property(node, "scale", original_scale * Vector2(1.4, 0.6), duration * 0.15).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT) tween.tween_property(node, "scale", original_scale * Vector2(0.85, 1.2), duration * 0.2).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) tween.parallel().tween_property(node, "position:y", original_pos.y - 12, duration * 0.2) tween.tween_property(node, "scale", original_scale * Vector2(1.05, 0.97), duration * 0.15).set_trans(Tween.TRANS_SINE) tween.parallel().tween_property(node, "position:y", original_pos.y, duration * 0.15) tween.tween_property(node, "scale", original_scale, duration * 0.2).set_trans(Tween.TRANS_SINE) TweenManager.track(node, Animations.IMPACT_LAND, tween) return tween ## Big scale punch with chromatic color shift. Classic critical hit feedback. func critical_hit(node: CanvasItem, duration: float = 0.5, color: Color = Color(2.0, 0.3, 0.3, 1.0), scale_amount: float = 1.6) -> Tween: TweenManager.stop(node, Animations.CRITICAL_HIT) var original_scale: Vector2 = node.scale var original_color: Color = node.modulate var secondary_color = Color(color.r, clampf(color.g + 0.8, 0.0, 2.0), 0.2, 1.0) var tween := node.create_tween() if scale_amount != 1.0: tween.tween_property(node, "scale", original_scale * scale_amount, duration * 0.1).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) tween.parallel().tween_property(node, "modulate", color, duration * 0.1) tween.tween_property(node, "scale", original_scale * 0.85, duration * 0.15).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN) tween.parallel().tween_property(node, "modulate", secondary_color, duration * 0.15) tween.tween_property(node, "scale", original_scale * scale_amount * 0.69, duration * 0.1).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) tween.tween_property(node, "scale", original_scale, duration * 0.3).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT) tween.parallel().tween_property(node, "modulate", original_color, duration * 0.3) else: tween.tween_property(node, "modulate", color, duration * 0.1) tween.tween_property(node, "modulate", secondary_color, duration * 0.15) tween.tween_property(node, "modulate", original_color, duration * 0.3) TweenManager.track(node, Animations.CRITICAL_HIT, tween) return tween ## Scale up, glow, then settle. Celebratory upgrade feedback. func upgrade(node: CanvasItem, duration: float = 0.8, glow: Color = Color(2.0, 1.8, 0.2, 1.0), scale_amount: float = 1.5) -> Tween: TweenManager.stop(node, Animations.UPGRADE) var original_scale: Vector2 = node.scale var original_color: Color = node.modulate var secondary_color : Color = Color(glow.r * 0.9, glow.g * 0.9, glow.b + 0.3, 1.0) var tween := node.create_tween() if scale_amount != 1.0: tween.tween_property(node, "scale", original_scale * 0.8, duration * 0.1).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN) tween.tween_property(node, "scale", original_scale * scale_amount, duration * 0.2).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) tween.parallel().tween_property(node, "modulate", glow, duration * 0.2) tween.tween_property(node, "scale", original_scale * (scale_amount * 0.8), duration * 0.15).set_trans(Tween.TRANS_SINE) tween.parallel().tween_property(node, "modulate", secondary_color, duration * 0.15) tween.tween_property(node, "scale", original_scale * (scale_amount * 0.9), duration * 0.1).set_trans(Tween.TRANS_SINE) tween.tween_property(node, "scale", original_scale, duration * 0.3).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT) tween.parallel().tween_property(node, "modulate", original_color, duration * 0.3) else: tween.tween_property(node, "modulate", glow, duration * 0.2) tween.tween_property(node, "modulate", secondary_color, duration * 0.15) tween.tween_property(node, "modulate", original_color, duration * 0.3) TweenManager.track(node, Animations.UPGRADE, tween) return tween ## Unfolds the node by scaling Y from 0 to full size. func fold_in(node: CanvasItem, duration: float = 0.3) -> Tween: TweenManager.stop(node, Animations.FOLD_IN) var original_scale: Vector2 = node.scale node.scale.y = 0.0 var tween := node.create_tween() tween.tween_property(node, "scale:y", original_scale.y, duration).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) TweenManager.track(node, Animations.FOLD_IN, tween) return tween ## Folds the node away by scaling Y to 0. func fold_out(node: CanvasItem, duration: float = 0.3) -> Tween: TweenManager.stop(node, Animations.FOLD_OUT) var tween := node.create_tween() tween.tween_property(node, "scale:y", 0.0, duration).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_IN) TweenManager.track(node, Animations.FOLD_OUT, tween) return tween ## Quick nudge in one direction then back, like pointing at something. func point(node: CanvasItem, duration: float = 0.5, direction: Vector2 = Vector2.RIGHT, amount: float = 24.0) -> Tween: TweenManager.stop(node, Animations.POINT) var original_pos: Vector2 = node.position var tween := node.create_tween() tween.tween_property(node, "position", original_pos + direction.normalized() * amount, duration * 0.3).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) tween.tween_property(node, "position", original_pos, duration * 0.7).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT) TweenManager.track(node, Animations.POINT, tween) return tween ## Scale up, slight rotation, then settle. Celebratory reveal. func tada(node: CanvasItem, duration: float = 0.6) -> Tween: TweenManager.stop(node, Animations.TADA) var original_scale: Vector2 = node.scale var original_rotation : float = node.rotation_degrees var tween := node.create_tween() tween.tween_property(node, "scale", original_scale * 0.8, duration * 0.1).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN) tween.tween_property(node, "scale", original_scale * 1.4, duration * 0.2).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) tween.parallel().tween_property(node, "rotation_degrees", original_rotation - 8.0, duration * 0.2) tween.tween_property(node, "rotation_degrees", original_rotation + 8.0, duration * 0.15).set_trans(Tween.TRANS_SINE) tween.tween_property(node, "rotation_degrees", original_rotation, duration * 0.15).set_trans(Tween.TRANS_SINE) tween.tween_property(node, "scale", original_scale, duration * 0.3).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT) TweenManager.track(node, Animations.TADA, tween) return tween #endregion #region NEW ## Squash on press, bounce back with overshoot, then settle. func press(node: CanvasItem, duration: float = 0.3) -> Tween: TweenManager.stop(node, Animations.PRESS) var original_scale: Vector2 = node.scale var tween := node.create_tween() tween.tween_property(node, "scale", original_scale * 0.8, duration * 0.24).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT) tween.tween_property(node, "scale", original_scale * 1.1, duration * 0.45).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) tween.tween_property(node, "scale", original_scale, duration * 0.30).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_OUT) TweenManager.track(node, Animations.PRESS, tween) return tween ## Scale + rotation wiggle on press. func press_rotate(node: CanvasItem, duration: float = 0.3) -> Tween: TweenManager.stop(node, Animations.PRESS_ROTATE) var original_scale: Vector2 = node.scale var original_rotation : float = node.rotation var tween := node.create_tween() tween.set_parallel(true) tween.tween_property(node, "scale", original_scale * 0.85, duration * 0.33).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) tween.tween_property(node, "scale", original_scale, duration * 0.66).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT).set_delay(duration * 0.33) tween.tween_property(node, "rotation", original_rotation + deg_to_rad(5), duration * 0.33).set_trans(Tween.TRANS_SINE) tween.tween_property(node, "rotation", original_rotation - deg_to_rad(5), duration * 0.33).set_trans(Tween.TRANS_SINE).set_delay(duration * 0.33) tween.tween_property(node, "rotation", original_rotation, duration * 0.33).set_trans(Tween.TRANS_SINE).set_delay(duration * 0.66) TweenManager.track(node, Animations.PRESS_ROTATE, tween) return tween ## Moves toward a target point and snaps back. Magnetic pull feel. func magnetic_pull(node: CanvasItem, duration: float = 0.5, target: Vector2 = Vector2.ZERO, strength: float = 0.4) -> Tween: TweenManager.stop(node, Animations.MAGNETIC_PULL) var original_pos: Vector2 = node.position var pull_pos = original_pos.lerp(target, strength) var tween := node.create_tween() tween.tween_property(node, "position", pull_pos, duration * 0.4).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN) tween.tween_property(node, "position", original_pos, duration * 0.6).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT) TweenManager.track(node, Animations.MAGNETIC_PULL, tween) return tween ## Shake left-right — denial, wrong answer, access refused, refusal/no gesture. func headshake(node: CanvasItem, duration: float = 0.5, amount: float = 8.0, times: int = 3) -> Tween: TweenManager.stop(node, Animations.HEADSHAKE) var original_rotation : float = node.rotation_degrees var tween := node.create_tween() for i in range(times): var dir = 1.0 if i % 2 == 0 else -1.0 tween.tween_property(node, "rotation_degrees", original_rotation + amount * dir, duration * 0.3 / times).set_trans(Tween.TRANS_SINE).set_ease(Tween.EASE_IN_OUT) tween.tween_property(node, "rotation_degrees", original_rotation, duration * 0.25).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) TweenManager.track(node, Animations.HEADSHAKE, tween) return tween #endregion #endregion