You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

79 lines
3.0 KiB

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