extends CharacterBody2D ## 球:未发射时停在挡板上,空格发射后在场地内反弹。 ## ## 反弹规则: ## - 撞墙/砖块:镜面反射(用 collision.get_normal()) ## - 撞挡板:按击中挡板的横向偏移换算反弹角度——挡板中心=竖直反弹, ## 越靠边缘反弹角越大(打砖块手感的核心)。 ## - 球速随击砖次数缓慢上升,到 max_speed 封顶。 signal lost ## 球出界 signal brick_hit(brick) ## 命中砖块(用于计连击/提速) @export var speed: float = 400.0 ## 初始球速 @export var max_speed: float = 700.0 ## 球速上限 @export var speed_gain_per_hit: float = 4.0 ## 每击碎一砖后提速 @export var spawn_offset_y: float = -24.0 ## 球相对挡板的垂直偏移 @export var launch_angle_deg: float = 60.0 ## 初始发射角度 @export var max_bounce_angle_deg: float = 75.0 ## 挡板最大反弹角(中心绝对值) @onready var _paddle: CharacterBody2D = get_parent().get_node("Paddle") var _launched: bool = false var _current_speed: float = 0.0 func _ready() -> void: _current_speed = speed velocity = Vector2.ZERO func _physics_process(_delta: float) -> void: if not _launched: # 跟随挡板 position.x = _paddle.position.x position.y = _paddle.position.y + spawn_offset_y if Input.is_key_pressed(KEY_SPACE): _launch() return # 已发射:每帧移动并检测碰撞 var motion: Vector2 = velocity * _delta var collision: KinematicCollision2D = move_and_collide(motion) if collision: var body := collision.get_collider() # 命中挡板:按击中位置算反弹角度 if body == _paddle: _bounce_off_paddle(collision) else: # 撞墙/砖块:镜面反射 velocity = velocity.bounce(collision.get_normal()) velocity.x += randf_range(-5.0, 5.0) # 命中砖块:转给 main 并提速 if body and body.is_in_group("bricks"): brick_hit.emit(body) _current_speed = min(_current_speed + speed_gain_per_hit, max_speed) velocity = velocity.normalized() * _current_speed func _bounce_off_paddle(collision: KinematicCollision2D) -> void: # 球相对挡板中心的横向偏移,归一化到 [-1, 1] # 挡板加宽时,半宽从 paddle 实时读 var half_paddle: float = _paddle.get_half_width() if _paddle.has_method("get_half_width") else 48.0 var offset: float = clamp( (position.x - _paddle.position.x) / half_paddle, -1.0, 1.0 ) # 偏移 → 反弹角(0 = 竖直,max_bounce_angle_deg = 边缘) var angle_rad: float = deg_to_rad(offset * max_bounce_angle_deg) # 球只能向上飞(-Y 方向) velocity = Vector2(sin(angle_rad), -cos(angle_rad)) * _current_speed func _launch() -> void: _launched = true var rad: float = deg_to_rad(launch_angle_deg) var dir_x: float = [-1.0, 1.0][randi() % 2] velocity = Vector2(cos(rad) * dir_x, -sin(rad)) * _current_speed func reset_to_paddle() -> void: _launched = false _current_speed = speed velocity = Vector2.ZERO position.x = _paddle.position.x position.y = _paddle.position.y + spawn_offset_y