class_name PickupBehaviorController extends Area2D ## Controls visual movement (bob, shake) and drift of a pickup. ## Attach to the root Area2D node of your pickup scene. @export var behavior: PickupBehavior @export var drift_behavior: PickupDriftBehavior var _icon_node: TextureRect var _spawn_position: Vector2 = Vector2.ZERO func _ready() -> void: # Find the runtime-added icon node. for child in get_children(): if child is TextureRect: _icon_node = child as TextureRect break # Capture the initial spawn position. _spawn_position = global_position if behavior != null: behavior.start() func _process(delta: float) -> void: if behavior == null or drift_behavior == null: return var alpha: float = behavior.process(delta) if _icon_node != null: _icon_node.visible = alpha > 0.01 if behavior != null and behavior.fade_in_duration > 0 and _icon_node is CanvasItem: _icon_node.modulate.a = alpha var offset := _compute_animation_offset() var drift_offset := _compute_drift_offset() position = _spawn_position + Vector2(drift_offset.x, drift_offset.y) if _icon_node != null: _icon_node.position = offset func _compute_animation_offset() -> Vector2: # Returns the bob + shake offset applied to the icon node. var result := Vector2.ZERO if behavior == null: return result # Bob (vertical oscillation). if behavior.bob_enabled: var bob_angle := _elapsed_time() * TAU * behavior.bob_frequency result.y += sin(bob_angle) * behavior.bob_amplitude # Shake (subtle jitter). if behavior.shake_enabled: var shake_angle := _elapsed_time() * TAU * behavior.shake_frequency result.x += sin(shake_angle) * behavior.shake_amplitude return result func _compute_drift_offset() -> Vector2: # Returns the global drift position offset from initial spawn position. if drift_behavior == null: return Vector2.ZERO match drift_behavior.drift_type: PickupDriftBehavior.DriftType.STATIONARY: return Vector2.ZERO PickupDriftBehavior.DriftType.SINE_WAVE: var t := _elapsed_time() * drift_behavior.wave_frequency var travel_speed: float = drift_behavior.drift_speed if drift_behavior.reverse_direction: travel_speed *= -1.0 var offset := Vector2.ZERO if drift_behavior.drift_along_x: # Travels along X; waves on Y. offset.x = travel_speed * _elapsed_time() offset.y = sin(t + drift_behavior.phase_offset) * drift_behavior.wave_amplitude else: # Travels along Y; waves on X. offset.y = travel_speed * _elapsed_time() offset.x = sin(t + drift_behavior.phase_offset) * drift_behavior.wave_amplitude return offset PickupDriftBehavior.DriftType.LEAF_FALL: var leaf_t := _elapsed_time() * drift_behavior.wave_frequency var travel_speed: float = drift_behavior.fall_speed if drift_behavior.reverse_direction: travel_speed *= -1.0 var offset := Vector2.ZERO if drift_behavior.drift_along_x: # Travels along X; waves on Y. offset.x = travel_speed * _elapsed_time() offset.y = sin(leaf_t + drift_behavior.phase_offset) * drift_behavior.wave_amplitude else: # Travels along Y; waves on X. offset.y = travel_speed * _elapsed_time() offset.x = sin(leaf_t + drift_behavior.phase_offset) * drift_behavior.wave_amplitude return offset PickupDriftBehavior.DriftType.DIAGONAL: var dir: float = 1.0 if drift_behavior.diagonal_forward else -1.0 if drift_behavior.reverse_direction: dir *= -1.0 var offset := Vector2.ZERO if drift_behavior.drift_along_x: # Travels along X; secondary on Y. offset.x = dir * drift_behavior.drift_speed * _elapsed_time() offset.y = dir * (drift_behavior.drift_speed * 0.5) * _elapsed_time() else: # Travels along Y; secondary on X. offset.y = dir * drift_behavior.drift_speed * _elapsed_time() offset.x = dir * (drift_behavior.drift_speed * 0.5) * _elapsed_time() return offset return Vector2.ZERO func _elapsed_time() -> float: if behavior == null: return 0.0 return behavior._elapsed