extends Node2D func get_half_size(node: Node2D) -> Vector2: var tex_size := Vector2.ZERO var sprite: Node2D = null # Try to find a Sprite2D or AnimatedSprite2D child if node is Sprite2D or node is AnimatedSprite2D: sprite = node else: for child in node.get_children(): if child is Sprite2D or child is AnimatedSprite2D: sprite = child break if sprite == null: return Vector2.ZERO # Get texture size if sprite is Sprite2D: if sprite.texture: tex_size = sprite.texture.get_size() elif sprite is AnimatedSprite2D: var tex: Texture2D = sprite.sprite_frames.get_frame_texture(sprite.animation, 0) if tex: tex_size = tex.get_size() # Combine parent scaling (global scale includes both) var global_scl = sprite.get_global_transform().get_scale() return (tex_size * global_scl) * 0.5 func wrap_position(node: Node2D) -> void: var rect = node.get_viewport_rect() var pos = node.position var half_size = get_half_size(node) # Horizontal wrap if pos.x < -half_size.x: pos.x = rect.size.x + half_size.x elif pos.x > rect.size.x + half_size.x: pos.x = -half_size.x # Vertical wrap if pos.y < -half_size.y: pos.y = rect.size.y + half_size.y elif pos.y > rect.size.y + half_size.y: pos.y = -half_size.y node.position = pos func play_sound(sound: Resource, parent: Node2D): var player = AudioStreamPlayer.new() parent.add_child(player) player.stream = sound player.play() player.finished.connect(func(): player.queue_free())