class_name Paddle extends AnimatableBody2D ## Follows the player's finger (or mouse) along the bottom of the screen. ## Emitted when the player lifts their finger; lets go of caught balls. signal released enum Width { NORMAL, HALF, DOUBLE } const SCALE := { Width.NORMAL: 1.0, Width.HALF: 0.5, Width.DOUBLE: 2.0 } const LASER_INTERVAL := 0.4 # seconds between shots const LASER_FLASH := 0.08 # seconds a beam stays visible var width := Width.NORMAL ## While above 0, balls that land on the paddle are caught. var catch_time := 0.0 var _laser_time := 0.0 var _laser_cooldown := 0.0 var _beams: Array[Vector2] = [] # local end points of the beams on screen var _beam_time := 0.0 var _screen_width := 0.0 var _shape := RectangleShape2D.new() @onready var _timer: Timer = $NormalTimer func _ready() -> void: _screen_width = get_viewport_rect().size.x $CollisionShape2D.shape = _shape _timer.timeout.connect(normal_size) normal_size() func _unhandled_input(event: InputEvent) -> void: if event is InputEventScreenTouch: var touch := event as InputEventScreenTouch if touch.is_pressed(): _move_to(touch.position.x) else: released.emit() elif event is InputEventScreenDrag: _move_to((event as InputEventScreenDrag).position.x) func half_width() -> float: return Params.PADDLE_WIDTH / 2.0 * SCALE[width] ## Doubles the paddle for a while, or cancels a halving. func double_size() -> void: if width == Width.HALF: normal_size() return _set_width(Width.DOUBLE) _timer.start(Params.POWER_UP_PADDLE_SECONDS) ## Halves the paddle for a while, or cancels a doubling. func half_size() -> void: if width == Width.DOUBLE: normal_size() return _set_width(Width.HALF) _timer.start(Params.POWER_UP_PADDLE_SECONDS) func normal_size() -> void: _timer.stop() _set_width(Width.NORMAL) func set_catch(seconds: float) -> void: catch_time = maxf(catch_time, seconds) queue_redraw() func set_laser(seconds: float) -> void: _laser_time = maxf(_laser_time, seconds) queue_redraw() func _physics_process(delta: float) -> void: var was_catching := catch_time > 0.0 catch_time = maxf(catch_time - delta, 0.0) if was_catching and catch_time == 0.0: released.emit() queue_redraw() if _beam_time > 0.0: _beam_time -= delta if _beam_time <= 0.0: _beams.clear() queue_redraw() if _laser_time > 0.0: _laser_time -= delta _laser_cooldown -= delta if _laser_cooldown <= 0.0: _laser_cooldown = LASER_INTERVAL _fire_lasers() if _laser_time <= 0.0: queue_redraw() ## Two beams straight up from the paddle's ends; each damages the first segment it meets. func _fire_lasers() -> void: _beams.clear() var space := get_world_2d().direct_space_state for side in [-1.0, 1.0]: var from := global_position + Vector2(side * (half_width() - 4.0), -Params.PADDLE_HEIGHT / 2.0) var query := PhysicsRayQueryParameters2D.create(from, Vector2(from.x, 0.0), Params.LAYER_BLOCKS) var result := space.intersect_ray(query) var end: Vector2 = result.position if result else Vector2(from.x, 0.0) if result and result.collider is Block: (result.collider as Block).damage() _beams.append(to_local(from)) _beams.append(to_local(end)) _beam_time = LASER_FLASH queue_redraw() func _set_width(value: Width) -> void: width = value _shape.size = Vector2((Params.PADDLE_WIDTH + Params.PADDLE_HEIGHT) * SCALE[width], Params.PADDLE_HEIGHT) _move_to(position.x) queue_redraw() func _move_to(x: float) -> void: position.x = clampf(x, half_width(), _screen_width - half_width()) func _draw() -> void: var half := half_width() var cap := Params.PADDLE_HEIGHT / 2.0 # A caught-ball paddle turns green. var fill := Params.COLOR_GREEN if catch_time > 0.0 else Params.COLOR_GREY draw_line(Vector2(-half, 0), Vector2(half, 0), fill, Params.PADDLE_HEIGHT, true) draw_circle(Vector2(-half, 0), cap, fill, true, -1.0, true) draw_circle(Vector2(half, 0), cap, fill, true, -1.0, true) if _laser_time > 0.0: for side in [-1.0, 1.0]: draw_rect(Rect2(side * (half - 4.0) - 3.0, -cap - 6.0, 6.0, 6.0), Params.COLOR_RED) if not _beams.is_empty(): draw_multiline(_beams, Params.COLOR_RED, 3.0, true)