diff --git a/scenes/level2.tscn b/scenes/level2.tscn index 93d5107..1583167 100644 --- a/scenes/level2.tscn +++ b/scenes/level2.tscn @@ -19,6 +19,7 @@ [ext_resource type="PackedScene" path="res://scenes/prefabs/collectibles/diamond.tscn" id="17_icuov"] [ext_resource type="PackedScene" path="res://scenes/prefabs/enemies/walker.tscn" id="18_walker"] [ext_resource type="PackedScene" path="res://scenes/prefabs/enemies/laser.tscn" id="19_laser"] +[ext_resource type="PackedScene" path="res://scenes/prefabs/enemies/bullet_ring.tscn" id="20_ring"] [node name="Main" type="Node2D" unique_id=2061129075] @@ -162,7 +163,7 @@ script = ExtResource("8_ekhvm") [node name="TerrainBase" type="TileMapLayer" parent="Level/TerrainTileMaps" unique_id=91115499] position = Vector2(9, 9) -tile_map_data = PackedByteArray("AAD//wkAAAACAAEAAAD//woAAAAEAAAAAAABAA0AAAACAAYAAAACAA0AAAAEAAUAAAACAA4AAAAEAAUAAAADAA0AAAACAAYAAAABAAoAAAACAAYAAAAAAAoAAAAEAAUAAAAAAAkAAAACAAEAAAACAAsAAAAEAAUAAAAAAA0AAAAEAAUAAAD//w0AAAAEAAEAAAAFAA0AAAAEAAUAAAAEAA4AAAAEAAUAAAADAA4AAAACAAYAAAAAAA4AAAACAAYAAAABAA4AAAACAAYAAAAHAAwAAAACAAEAAAALAAwAAAACAAEAAAAKAA0AAAAEAAUAAAAIAA4AAAAEAAUAAAALAA4AAAAEAAUAAAAMAA4AAAACAAYAAAANAA0AAAACAAYAAAAOAA0AAAAEAAUAAAAPAA0AAAAEAAUAAAAQAA0AAAAEAAUAAAARAAwAAAACAAUAAAATAAwAAAACAAUAAAAVAAsAAAACAAYAAAAWAAsAAAAEAAUAAAAUAAwAAAACAAYAAAAQAAwAAAACAAMAAAAKAA4AAAAEAAUAAAAJAA4AAAAEAAUAAAAFAA4AAAACAAYAAAAGAA4AAAAEAAUAAAAHAA4AAAAEAAUAAAANAA4AAAACAAYAAAAOAA4AAAACAAYAAAAPAA4AAAACAAYAAAASAA0AAAACAAYAAAATAA0AAAAEAAUAAAAUAA0AAAACAAYAAAAVAA0AAAACAAYAAAAWAA0AAAACAAYAAAAXAA0AAAAEAAUAAAALAA0AAAAEAAUAAAAMAA0AAAACAAYAAAAPAAwAAAACAAMAAAAXAAsAAAAEAAUAAAAYAAsAAAAEAAUAAAAZAAsAAAACAAYAAAAZAAwAAAACAAYAAAAYAAwAAAAEAAUAAAAZAA4AAAAEAAUAAAAXAA4AAAAEAAUAAAAWAA4AAAAEAAUAAAAVAA4AAAACAAYAAAAUAA4AAAACAAYAAAATAA4AAAACAAYAAAASAA4AAAACAAYAAAAYAA4AAAACAAYAAAARAA4AAAACAAYAAAAEAA0AAAACAAYAAAD//w4AAAAEAAEAAAABAAkAAAACAAEAAAAIAAwAAAACAAEAAAAOAAwAAAACAAMAAAANAAwAAAACAAMAAAAMAAwAAAACAAMAAAAUAAkAAAABAAUAAAAUAAoAAAABAAYAAAAPAAgAAAASAAQAABAWAAoAAAACAAYAAAAXAAcAAAACAAUAAAAYAAkAAAACAAYAAAAYAAoAAAACAAYAAAAXAAoAAAACAAYAAAAXAAkAAAACAAYAAAAZAAkAAAACAAYAAAAZAAoAAAACAAYAAAAQAA4AAAAEAAUAAAARAA0AAAACAAYAAAASAAwAAAACAAUAAAAVAAoAAAACAAYAAAAWAAkAAAACAAUAAAAXAAgAAAAEAAEAAAAUAAsAAAAFAAEAAAAVAAwAAAAEAAUAAAAWAAwAAAACAAYAAAAXAAwAAAAEAAUAAAAYAA0AAAACAAYAAAAZAA0AAAAEAAUAAAAAAAwAAAACAAYAAAACAAwAAAACAAYAAAADAAwAAAAEAAUAAAAEAAwAAAACAAYAAAAFAAwAAAAEAAUAAAAGAAwAAAAEAAUAAAAGAA0AAAACAAYAAAAHAA0AAAACAAYAAAAIAA0AAAAEAAUAAAAJAA0AAAACAAYAAAAJAAwAAAACAAEAAAAKAAwAAAACAAEAAAD//wwAAAAFAAAAAAD//wsAAAAEAAAAAAAAAAsAAAAEAAUAAAABAAsAAAACAAYAAAABAAwAAAACAAYAAAAYAAcAAAACAAUAAAAZAAcAAAADAAUAAAAYAAgAAAACAAYAAAAZAAgAAAACAAYAAAAVAAkAAAACAAUAAAADAAsAAAACAAYAAAAEAAsAAAACAAEAAAAFAAsAAAACAAEAAAAGAAsAAAACAAEAAAACAAoAAAACAAEAAAADAAoAAAACAAEAAAARAAcAAAAOAAcAAAASAAcAAAAOAAcAAAAQAAcAAAAOAAcAAAAPAAcAAAANAAcAAAAIAAYAAAANAAcAAAATAAcAAAAPAAcAAAAMAAYAAAAPAAcAAAALAAYAAAAOAAcAAAAKAAYAAAAOAAcAAAAJAAYAAAAOAAcAAAABAAgAAAARAAYAAAABAAcAAAAQAAUAAAABAAYAAAARAAUAAAABAAUAAAAQAAUAAAAAAAQAAAASAAIAAAABAAQAAAASAAIAAAACAAQAAAASAAIAAAD//wQAAAARAAIAAAAEAAQAAAATAAIAAAADAAQAAAASAAIAAAAAAAMAAAASAAEAAAABAAMAAAASAAEAAAACAAMAAAASAAEAAAADAAMAAAASAAAAAAAEAAMAAAATAAAAAAD//wMAAAARAAEAAAD//wIAAAARAAEAAAD//wEAAAARAAEAAAAAAAEAAAASAAEAAAAAAAIAAAASAAEAAAABAAEAAAASAAEAAAABAAIAAAASAAEAAAACAAEAAAASAAEAAAACAAIAAAASAAEAAAACAAYAAAATAAQAAAA=") +tile_map_data = PackedByteArray("AAD//wkAAAACAAEAAAD//woAAAAEAAAAAAABAA0AAAACAAYAAAACAA0AAAAEAAUAAAACAA4AAAAEAAUAAAADAA0AAAACAAYAAAABAAoAAAACAAYAAAAAAAoAAAAEAAUAAAAAAAkAAAACAAEAAAACAAsAAAAEAAUAAAAAAA0AAAAEAAUAAAD//w0AAAAEAAEAAAAFAA0AAAAEAAUAAAAEAA4AAAAEAAUAAAADAA4AAAACAAYAAAAAAA4AAAACAAYAAAABAA4AAAACAAYAAAAHAAwAAAACAAEAAAALAAwAAAACAAEAAAAKAA0AAAAEAAUAAAAIAA4AAAAEAAUAAAALAA4AAAAEAAUAAAAMAA4AAAACAAYAAAANAA0AAAACAAYAAAAOAA0AAAAEAAUAAAAPAA0AAAAEAAUAAAAQAA0AAAAEAAUAAAARAAwAAAACAAUAAAATAAwAAAACAAUAAAAVAAsAAAACAAYAAAAWAAsAAAAEAAUAAAAUAAwAAAACAAYAAAAQAAwAAAACAAMAAAAKAA4AAAAEAAUAAAAJAA4AAAAEAAUAAAAFAA4AAAACAAYAAAAGAA4AAAAEAAUAAAAHAA4AAAAEAAUAAAANAA4AAAACAAYAAAAOAA4AAAACAAYAAAAPAA4AAAACAAYAAAASAA0AAAACAAYAAAATAA0AAAAEAAUAAAAUAA0AAAACAAYAAAAVAA0AAAACAAYAAAAWAA0AAAACAAYAAAAXAA0AAAAEAAUAAAALAA0AAAAEAAUAAAAMAA0AAAACAAYAAAAPAAwAAAACAAMAAAAXAAsAAAAEAAUAAAAYAAsAAAAEAAUAAAAZAAsAAAACAAYAAAAZAAwAAAACAAYAAAAYAAwAAAAEAAUAAAAZAA4AAAAEAAUAAAAXAA4AAAAEAAUAAAAWAA4AAAAEAAUAAAAVAA4AAAACAAYAAAAUAA4AAAACAAYAAAATAA4AAAACAAYAAAASAA4AAAACAAYAAAAYAA4AAAACAAYAAAARAA4AAAACAAYAAAAEAA0AAAACAAYAAAD//w4AAAAEAAEAAAABAAkAAAACAAEAAAAIAAwAAAACAAEAAAAOAAwAAAACAAMAAAANAAwAAAACAAMAAAAMAAwAAAACAAMAAAAUAAkAAAABAAUAAAAUAAoAAAABAAYAAAAPAAgAAAASAAQAABAWAAoAAAACAAYAAAAXAAcAAAACAAUAAAAYAAkAAAACAAYAAAAYAAoAAAACAAYAAAAXAAoAAAACAAYAAAAXAAkAAAACAAYAAAAZAAkAAAACAAYAAAAZAAoAAAACAAYAAAAQAA4AAAAEAAUAAAARAA0AAAACAAYAAAASAAwAAAACAAUAAAAVAAoAAAACAAYAAAAWAAkAAAACAAUAAAAXAAgAAAAEAAEAAAAUAAsAAAAFAAEAAAAVAAwAAAAEAAUAAAAWAAwAAAACAAYAAAAXAAwAAAAEAAUAAAAYAA0AAAACAAYAAAAZAA0AAAAEAAUAAAAAAAwAAAACAAYAAAACAAwAAAACAAYAAAADAAwAAAAEAAUAAAAEAAwAAAACAAYAAAAFAAwAAAAEAAUAAAAGAAwAAAAEAAUAAAAGAA0AAAACAAYAAAAHAA0AAAACAAYAAAAIAA0AAAAEAAUAAAAJAA0AAAACAAYAAAAJAAwAAAACAAEAAAAKAAwAAAACAAEAAAD//wwAAAAFAAAAAAD//wsAAAAEAAAAAAAAAAsAAAAEAAUAAAABAAsAAAACAAYAAAABAAwAAAACAAYAAAAYAAcAAAACAAUAAAAZAAcAAAADAAUAAAAYAAgAAAACAAYAAAAZAAgAAAACAAYAAAAVAAkAAAACAAUAAAADAAsAAAACAAYAAAAEAAsAAAACAAEAAAAFAAsAAAACAAEAAAAGAAsAAAACAAEAAAACAAoAAAACAAEAAAADAAoAAAACAAEAAAARAAcAAAAOAAcAAAASAAcAAAAOAAcAAAAQAAcAAAAOAAcAAAAPAAcAAAANAAcAAAAIAAYAAAANAAcAAAATAAcAAAAPAAcAAAAMAAYAAAAPAAcAAAALAAYAAAAOAAcAAAAKAAYAAAAOAAcAAAABAAgAAAARAAYAAAABAAcAAAAQAAUAAAABAAYAAAARAAUAAAABAAUAAAAQAAUAAAAAAAQAAAASAAIAAAABAAQAAAASAAIAAAACAAQAAAASAAIAAAD//wQAAAARAAIAAAAEAAQAAAATAAIAAAADAAQAAAASAAIAAAAAAAMAAAASAAEAAAABAAMAAAASAAEAAAACAAMAAAASAAEAAAADAAMAAAASAAAAAAAEAAMAAAATAAAAAAD//wMAAAARAAEAAAD//wIAAAARAAEAAAD//wEAAAARAAEAAAAAAAEAAAASAAEAAAAAAAIAAAASAAEAAAABAAEAAAASAAEAAAABAAIAAAASAAEAAAACAAEAAAASAAEAAAACAAIAAAASAAEAAAACAAYAAAATAAQAAAAJAAYAAAAOAAcAAAA=") tile_set = ExtResource("9_nflru") [node name="TerrainDetailsA" type="TileMapLayer" parent="Level/TerrainTileMaps" unique_id=1513560262] @@ -220,15 +221,38 @@ start_direction = 1 position = Vector2(90, 198) start_direction = 1 -[node name="Laser_01" parent="Level/PrefabInstances/Enemies" instance=ExtResource("19_laser")] -position = Vector2(189, 216) +[node name="Laser_01" parent="Level/PrefabInstances/Enemies" unique_id=1298565970 instance=ExtResource("19_laser")] +position = Vector2(187, 223) rotation = -1.5707964 -[node name="Laser_02" parent="Level/PrefabInstances/Enemies" instance=ExtResource("19_laser")] -position = Vector2(297, 216) +[node name="Laser_02" parent="Level/PrefabInstances/Enemies" unique_id=1808104165 instance=ExtResource("19_laser")] +position = Vector2(298, 233) rotation = -1.5707964 -start_delay = 2.4 length = 108.0 +start_delay = 2.4 + +[node name="Laser_03" parent="Level/PrefabInstances/Enemies" unique_id=1437324170 instance=ExtResource("19_laser")] +position = Vector2(387, 137) +rotation = -1.5707964 +length = 108.0 +start_delay = 2.4 + +[node name="Laser_04" parent="Level/PrefabInstances/Enemies" unique_id=1698684283 instance=ExtResource("19_laser")] +position = Vector2(125, 125) +rotation = -1.5707964 +length = 108.0 +start_delay = 2.4 + +[node name="Laser_05" parent="Level/PrefabInstances/Enemies" unique_id=231592857 instance=ExtResource("19_laser")] +position = Vector2(251, 78) +rotation = -1.5707964 +length = 108.0 +start_delay = 2.4 + +[node name="BulletRing_01" parent="Level/PrefabInstances/Enemies" instance=ExtResource("20_ring")] +position = Vector2(234, 224) +arc_deg = 180.0 +base_angle_deg = 270.0 [node name="Interactables" type="Node2D" parent="Level/PrefabInstances" unique_id=769532488] diff --git a/scenes/prefabs/enemies/bullet.tscn b/scenes/prefabs/enemies/bullet.tscn index b4b55d2..6deaa8e 100644 --- a/scenes/prefabs/enemies/bullet.tscn +++ b/scenes/prefabs/enemies/bullet.tscn @@ -18,4 +18,5 @@ shape = SubResource("Shape_bullet") [node name="Probe" type="RayCast2D" parent="."] collision_mask = 1 +hit_from_inside = true target_position = Vector2(-12, 0) diff --git a/scenes/prefabs/enemies/bullet_ring.tscn b/scenes/prefabs/enemies/bullet_ring.tscn new file mode 100644 index 0000000..7efdc67 --- /dev/null +++ b/scenes/prefabs/enemies/bullet_ring.tscn @@ -0,0 +1,9 @@ +[gd_scene load_steps=3 format=3] + +[ext_resource type="Script" path="res://scripts/bullet_ring.gd" id="1_ring"] + +[node name="BulletRing" type="Node2D" groups=["emitter"]] +script = ExtResource("1_ring") + +[node name="Core" type="Sprite2D" parent="."] +texture_filter = 1 diff --git a/scripts/bullet.gd.uid b/scripts/bullet.gd.uid new file mode 100644 index 0000000..30335c6 --- /dev/null +++ b/scripts/bullet.gd.uid @@ -0,0 +1 @@ +uid://c4v4jls42gsyk diff --git a/scripts/bullet_ring.gd b/scripts/bullet_ring.gd new file mode 100644 index 0000000..f722748 --- /dev/null +++ b/scripts/bullet_ring.gd @@ -0,0 +1,76 @@ +extends Node2D + +## 固定弹幕源:每隔一段时间以自己为中心,按**固定角度**围一圈发射子弹。 +## 每一波的角度都算死(可选每波整体旋转),所以弹道可以背下来, +## 玩家靠走位找空隙,或者用「扔钥匙 + W 传送」直接穿过去。 + +const BULLET := preload("res://scenes/prefabs/enemies/bullet.tscn") +const BULLET_SCRIPT := preload("res://scripts/bullet.gd") + +@export var enabled: bool = true +## 发射间隔(秒) +@export var interval: float = 2.0 +## 开场后第一次发射前的延迟 +@export var start_delay: float = 1.0 +## 每波几颗 +@export var count: int = 12 +## 扇形张开角度:360 = 围一整圈 +@export var arc_deg: float = 360.0 +## 整圈的中心朝向(度):0=右 90=下 180=左 270=上 +@export var base_angle_deg: float = 0.0 +## 每发一波整体旋转多少度(0 = 弹道完全固定,可以背板) +@export var rotate_per_shot_deg: float = 0.0 +@export var bullet_speed: float = 55.0 +@export var bullet_color: Color = Color(1.0, 0.45, 0.85) +## 中心的圆点半径(只是给玩家一个视觉提示) +@export var core_radius: int = 4 + +var _timer: float = 0.0 +var _angle: float = 0.0 + + +func _ready() -> void: + add_to_group("emitter") + _timer = start_delay + _angle = base_angle_deg + var core := get_node_or_null("Core") as Sprite2D + if core != null: + core.texture = BULLET_SCRIPT.circle_texture(core_radius, bullet_color) + + +func _physics_process(delta: float) -> void: + if not enabled: + return + _timer -= delta + if _timer > 0.0: + return + _timer = maxf(interval, 0.05) + fire() + + +func fire() -> void: + var n: int = maxi(count, 1) + var base := deg_to_rad(_angle) + var full_circle := is_equal_approx(arc_deg, 360.0) + var step := deg_to_rad(arc_deg) / float(n) if full_circle else deg_to_rad(arc_deg) + for i in n: + var ang: float + if full_circle: + ang = base + step * float(i) + elif n <= 1: + ang = base + else: + ang = base + step * (float(i) / float(n - 1) - 0.5) + _spawn(ang) + if not is_zero_approx(rotate_per_shot_deg): + _angle = fmod(_angle + rotate_per_shot_deg, 360.0) + + +func _spawn(ang: float) -> void: + var bullet := BULLET.instantiate() + # 这两个属性必须在 add_child 之前设好,子弹的 _ready 会拿它们生成贴图 + bullet.bullet_color = bullet_color + bullet.speed = bullet_speed + get_parent().add_child(bullet) + bullet.global_position = global_position + bullet.launch(Vector2(cos(ang), sin(ang)), bullet_speed) diff --git a/scripts/laser.gd b/scripts/laser.gd index 6067504..a092abf 100644 --- a/scripts/laser.gd +++ b/scripts/laser.gd @@ -1,21 +1,24 @@ extends Area2D -## 固定激光:每隔一段时间循环「蓄力闪烁 -> 发射 -> 熄灭」。 -## 蓄力阶段只闪不伤人(给玩家反应时间),发射阶段碰到就扣血。 +## 激光。两种模式: +## - always_on = true(默认):**固定障碍** —— 一直亮着,碰到就扣血,只能绕开或用钥匙传送穿过去 +## - always_on = false:循环「蓄力闪烁 -> 发射 -> 熄灭」,需要卡时间通过 ## 光束从自己的原点沿 +X 方向伸出,想改朝向就转节点本身。 enum State { WAIT, WARMUP, FIRING } +## 固定障碍模式(一直亮着) +@export var always_on: bool = true @export var length: float = 90.0 @export var thickness: float = 6.0 @export var damage: int = 1 -## 蓄力(闪烁警告)时长 +## 蓄力(闪烁警告)时长,只有循环模式用 @export var warmup: float = 0.8 -## 发射持续时长 +## 发射持续时长,只有循环模式用 @export var fire_time: float = 0.35 -## 两次发射之间的间隔 +## 两次发射之间的间隔,只有循环模式用 @export var cooldown: float = 1.8 -## 开场后第一次发射前的延迟 +## 开场后第一次发射前的延迟,只有循环模式用 @export var start_delay: float = 1.0 @export var laser_color: Color = Color(1.0, 0.25, 0.25) @export var warn_color: Color = Color(1.0, 0.8, 0.4) @@ -35,7 +38,7 @@ func _ready() -> void: Vector2(0.0, -half), Vector2(length, -half), Vector2(length, half), Vector2(0.0, half)]) _beam.color = laser_color - _beam.visible = false + _beam.visible = always_on var hit := get_node_or_null("HitBox") as CollisionShape2D if hit != null: var rect := RectangleShape2D.new() @@ -45,6 +48,13 @@ func _ready() -> void: func _physics_process(delta: float) -> void: + if always_on: + if _beam != null: + _beam.visible = true + _beam.color = laser_color + _hurt_overlaps() + return + _timer += delta match _state: State.WAIT: @@ -64,11 +74,17 @@ func _physics_process(delta: float) -> void: _beam.color = laser_color _beam.visible = true State.FIRING: - for body in get_overlapping_bodies(): - if body.has_method("hurt"): - body.hurt(damage) + _hurt_overlaps() if _timer >= fire_time: _state = State.WAIT _timer = 0.0 if _beam != null: _beam.visible = false + + +## 每帧检查压在光柱里的东西(不能只靠 body_entered, +## 因为玩家可能本来就在光柱里,那样不会触发 entered) +func _hurt_overlaps() -> void: + for body in get_overlapping_bodies(): + if body.has_method("hurt"): + body.hurt(damage) diff --git a/scripts/laser.gd.uid b/scripts/laser.gd.uid new file mode 100644 index 0000000..dede0da --- /dev/null +++ b/scripts/laser.gd.uid @@ -0,0 +1 @@ +uid://de3i3dmyi8bjw diff --git a/scripts/walker.gd b/scripts/walker.gd index 65d3503..afee726 100644 --- a/scripts/walker.gd +++ b/scripts/walker.gd @@ -1,11 +1,8 @@ extends CharacterBody2D -## 小怪:在平台上左右巡逻,并且按固定节奏发射弹幕。 -## **打不死** —— 从上方踩上去也一样会被扣血。 -## 弹道方向是写死的(不追着玩家走),所以每一波的弹道完全一样, -## 玩家可以背下空隙走位躲过去,或者用「扔钥匙 + W 传送」直接穿过去。 - -const BULLET := preload("res://scenes/prefabs/enemies/bullet.tscn") +## 小怪:只在平台上左右巡逻,**不发射任何东西**,也**打不死**。 +## 作用纯粹是挡路 + 碰到扣血,逼玩家走位。 +## 弹幕交给专门的「弹幕源」节点负责。 @export_category("巡逻") @export var speed: float = 28.0 @@ -13,31 +10,12 @@ const BULLET := preload("res://scenes/prefabs/enemies/bullet.tscn") @export var start_direction: int = -1 ## 掉头后的冷却时间:防止贴着墙时每帧都掉头、结果原地抖动 @export var turn_cooldown: float = 0.25 -## 接触玩家扣几颗心 +## 速度设成 0 就变成待在原地不动的障碍 @export var damage: int = 1 -## 速度设成 0 就变成固定炮台(弹幕就完全静止可背板了) - -@export_category("弹幕") -@export var fires_bullets: bool = true -## 发射间隔(秒) -@export var fire_interval: float = 2.0 -## 开场后第一次发射前的延迟 -@export var fire_delay: float = 1.0 -## 每次发射几颗 -@export var bullet_count: int = 3 -## 扇面张开角度 -@export var spread_deg: float = 60.0 -## 扇面中心朝向(度):0=右 90=下 180=左 270=上 -@export var base_angle_deg: float = 0.0 -## 开火方向跟随巡逻朝向(打开时 base_angle_deg 只当上下偏移用) -@export var aim_along_facing: bool = true -@export var bullet_speed: float = 70.0 -@export var bullet_color: Color = Color(1.0, 0.35, 0.7) var direction: int = -1 var _turn_timer: float = 0.0 -var _fire_timer: float = 1.0 @onready var sprite: Sprite2D = $Sprite @onready var floor_probe: RayCast2D = $FloorProbe @@ -48,7 +26,6 @@ func _ready() -> void: add_to_group("enemy") direction = -1 if start_direction < 0 else 1 _apply_direction() - _fire_timer = fire_delay touch_area.body_entered.connect(_on_touch) @@ -69,33 +46,6 @@ func _physics_process(delta: float) -> void: move_and_slide() - if fires_bullets: - _fire_timer -= delta - if _fire_timer <= 0.0: - _fire_timer = maxf(fire_interval, 0.05) - fire() - - -## 按固定扇面发射一批子弹。方向是算死的,不参考玩家位置, -## 所以每一波的弹道都一样,玩家能背下来找空隙。 -func fire() -> void: - var n: int = maxi(bullet_count, 1) - var base_deg: float = base_angle_deg - if aim_along_facing: - base_deg = 0.0 if direction > 0 else 180.0 - var base := deg_to_rad(base_deg) - var spread := deg_to_rad(spread_deg) - for i in n: - var t: float = 0.0 if n == 1 else (float(i) / float(n - 1) - 0.5) - var ang := base + spread * t - var bullet := BULLET.instantiate() - # 这两个属性必须在 add_child 之前设好,子弹的 _ready 会拿它们生成贴图 - bullet.bullet_color = bullet_color - bullet.speed = bullet_speed - get_parent().add_child(bullet) - bullet.global_position = global_position + Vector2(0.0, -2.0) - bullet.launch(Vector2(cos(ang), sin(ang)), bullet_speed) - func _apply_direction() -> void: if sprite != null: