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
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
|
|
|