diff --git a/scripts/objects/balloon.gd b/scripts/objects/balloon.gd new file mode 100644 index 0000000..ae561e2 --- /dev/null +++ b/scripts/objects/balloon.gd @@ -0,0 +1,55 @@ +## Balloon — tap to pop, auto-respawns after a delay. +class_name Balloon extends Node2D + +enum State { IDLE, POPPING, POPPED, RESPAWNING } + +const POP_DURATION: float = 0.15 +const RESPAWN_DURATION: float = 0.30 +const RESPAWN_DELAY: float = 5.0 +const BUTTON_HALF_SIZE: float = 20.0 + +var _state: State = State.IDLE + + +func _input(event: InputEvent) -> void: + if _state != State.IDLE: + return + if not event is InputEventScreenTouch: + return + var touch: InputEventScreenTouch = event as InputEventScreenTouch + if not touch.pressed: + return + var local: Vector2 = to_local(touch.position) + if abs(local.x) > BUTTON_HALF_SIZE or abs(local.y) > BUTTON_HALF_SIZE: + return + _start_pop() + + +func _start_pop() -> void: + AudioManager.play_sfx("object_tap") + _state = State.POPPING + var tween: Tween = create_tween() + tween.set_ease(Tween.EASE_IN) + tween.set_trans(Tween.TRANS_BACK) + tween.tween_property(self, "scale", Vector2.ZERO, POP_DURATION) + tween.tween_callback(_on_pop_complete) + + +func _on_pop_complete() -> void: + _state = State.POPPED + var tween: Tween = create_tween() + tween.tween_interval(RESPAWN_DELAY) + tween.tween_callback(_start_respawn) + + +func _start_respawn() -> void: + _state = State.RESPAWNING + var tween: Tween = create_tween() + tween.set_ease(Tween.EASE_OUT) + tween.set_trans(Tween.TRANS_BACK) + tween.tween_property(self, "scale", Vector2.ONE, RESPAWN_DURATION) + tween.tween_callback(_on_respawn_complete) + + +func _on_respawn_complete() -> void: + _state = State.IDLE diff --git a/test/unit/test_balloon.gd b/test/unit/test_balloon.gd new file mode 100644 index 0000000..8029c20 --- /dev/null +++ b/test/unit/test_balloon.gd @@ -0,0 +1,29 @@ +## Tests for Balloon — state machine transitions. +extends GutTest + +var _balloon: Balloon + + +func before_each() -> void: + _balloon = Balloon.new() + add_child_autofree(_balloon) + + +func test_initial_state_is_idle() -> void: + assert_eq(_balloon._state, Balloon.State.IDLE) + + +func test_start_pop_transitions_to_popping() -> void: + _balloon._start_pop() + assert_eq(_balloon._state, Balloon.State.POPPING) + + +func test_on_pop_complete_transitions_to_popped() -> void: + _balloon._on_pop_complete() + assert_eq(_balloon._state, Balloon.State.POPPED) + + +func test_on_respawn_complete_transitions_to_idle() -> void: + _balloon._state = Balloon.State.RESPAWNING + _balloon._on_respawn_complete() + assert_eq(_balloon._state, Balloon.State.IDLE)