From 35174b67abe910d9f929cbdd6d49434f60caefff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?sijcdu=E7=9A=84=E6=B8=B8=E6=88=8F?= Date: Wed, 7 Oct 2026 22:29:12 +0800 Subject: [PATCH] Replace the firing bullet emitter with static bullet obstacles ringed around (12,12) The user did not want anything firing: the bullets should simply exist in the air as fixed obstacles. bullet_ring (the emitter) is deleted and bullet_field takes its place. bullet_field generates concentric rings of frozen bullets centred on itself, radii given in cells (default 5/10/15), and skips bullets that fall outside the level bounds or land inside solid tiles so nothing is buried in the terrain. Each bullet is a StaticBody2D on the world layer, so it blocks the player and can be stood on; damage defaults to 0 (pure obstacle) and can be raised to turn them into hazards. --- scenes/level2.tscn | 8 +- scenes/prefabs/enemies/bullet_field.tscn | 7 ++ scenes/prefabs/enemies/bullet_ring.tscn | 9 -- scripts/bullet_field.gd | 126 +++++++++++++++++++++++ scripts/bullet_ring.gd | 76 -------------- 5 files changed, 136 insertions(+), 90 deletions(-) create mode 100644 scenes/prefabs/enemies/bullet_field.tscn delete mode 100644 scenes/prefabs/enemies/bullet_ring.tscn create mode 100644 scripts/bullet_field.gd delete mode 100644 scripts/bullet_ring.gd diff --git a/scenes/level2.tscn b/scenes/level2.tscn index 1583167..3ab348a 100644 --- a/scenes/level2.tscn +++ b/scenes/level2.tscn @@ -19,7 +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"] +[ext_resource type="PackedScene" path="res://scenes/prefabs/enemies/bullet_field.tscn" id="20_field"] [node name="Main" type="Node2D" unique_id=2061129075] @@ -249,10 +249,8 @@ 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="BulletField_01" parent="Level/PrefabInstances/Enemies" instance=ExtResource("20_field")] +position = Vector2(234, 234) [node name="Interactables" type="Node2D" parent="Level/PrefabInstances" unique_id=769532488] diff --git a/scenes/prefabs/enemies/bullet_field.tscn b/scenes/prefabs/enemies/bullet_field.tscn new file mode 100644 index 0000000..40f0183 --- /dev/null +++ b/scenes/prefabs/enemies/bullet_field.tscn @@ -0,0 +1,7 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://scripts/bullet_field.gd" id="1_field"] + +[node name="BulletField" type="Node2D"] +z_index = 5 +script = ExtResource("1_field") diff --git a/scenes/prefabs/enemies/bullet_ring.tscn b/scenes/prefabs/enemies/bullet_ring.tscn deleted file mode 100644 index 7efdc67..0000000 --- a/scenes/prefabs/enemies/bullet_ring.tscn +++ /dev/null @@ -1,9 +0,0 @@ -[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_field.gd b/scripts/bullet_field.gd new file mode 100644 index 0000000..51ddc51 --- /dev/null +++ b/scripts/bullet_field.gd @@ -0,0 +1,126 @@ +extends Node2D + +## 静态弹幕障碍:以自己为圆心,一圈一圈地摆上**固定不动、不会发射**的子弹。 +## 默认只是纯障碍(实心,撞得到、能踩上去),把 damage 调大于 0 就变成碰到扣血。 +## 落在实心地砖里的子弹会自动跳过(不然会埋在地里)。 + +const BULLET_SCRIPT := preload("res://scripts/bullet.gd") + +@export var enabled: bool = true +## 每一圈的半径(单位:格)。默认 5 格、10 格、15 格 +@export var radii_cells: Array[int] = [5, 10, 15] +## 每圈摆几颗 +@export var per_ring: int = 20 +## 一格多少像素(图块是 18) +@export var cell_size: float = 18.0 +## 起始角度偏移(度),用来微调子弹摆在哪个方向 +@export var angle_offset_deg: float = 0.0 +## 子弹半径 +@export var bullet_radius: int = 3 +@export var bullet_color: Color = Color(1.0, 0.45, 0.85) +## 实心障碍:玩家撞得到、也能踩上去 +@export var solid: bool = true +## 碰到扣几颗心。0 = 只是障碍不扣血 +@export var damage: int = 0 +## 超出这个范围的子弹不生成(默认整个关卡 468x270) +@export var bounds: Rect2 = Rect2(0, 0, 468, 270) +## 最多铺几圈 +@export var max_rings: int = 3 +## 跳过埋在地砖里的子弹 +@export var skip_solid_ground: bool = true + +var made: int = 0 +var skipped_out: int = 0 +var skipped_ground: int = 0 + + +func _ready() -> void: + if not enabled: + return + # 等一帧,确保 TileMapLayer 都已经就绪 + _build.call_deferred() + + +func _build() -> void: + var layers := _tile_layers() + var tex := BULLET_SCRIPT.circle_texture(bullet_radius, bullet_color) + made = 0 + skipped_out = 0 + skipped_ground = 0 + for i in mini(radii_cells.size(), max_rings): + var radius: float = float(radii_cells[i]) * cell_size + for k in per_ring: + var ang := deg_to_rad(angle_offset_deg + 360.0 * float(k) / float(per_ring)) + var pos := global_position + Vector2(cos(ang), sin(ang)) * radius + if not bounds.has_point(pos): + skipped_out += 1 + continue + if skip_solid_ground and _in_solid(layers, pos): + skipped_ground += 1 + continue + _make(pos, tex) + made += 1 + + +func _tile_layers() -> Array: + var root: Node = owner if owner != null else get_tree().current_scene + if root == null: + return [] + return root.find_children("*", "TileMapLayer", true, false) + + +## 这个点是不是埋在实心地砖里 +func _in_solid(layers: Array, pos: Vector2) -> bool: + for l in layers: + var cell: Vector2i = l.local_to_map(l.to_local(pos)) + var sid: int = l.get_cell_source_id(cell) + if sid == -1: + continue + var source := l.tile_set.get_source(sid) as TileSetAtlasSource + if source == null: + continue + var data: TileData = source.get_tile_data(l.get_cell_atlas_coords(cell), 0) + if data != null and data.get_collision_polygons_count(0) > 0: + return true + return false + + +func _make(pos: Vector2, tex: Texture2D) -> void: + var body := StaticBody2D.new() + if solid: + body.collision_layer = 1 + body.collision_mask = 1 + else: + body.collision_layer = 0 + body.collision_mask = 0 + add_child(body) + body.global_position = pos + + var sprite := Sprite2D.new() + sprite.texture = tex + sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + body.add_child(sprite) + + if solid: + var shape := CollisionShape2D.new() + var circle := CircleShape2D.new() + circle.radius = float(bullet_radius) + shape.shape = circle + body.add_child(shape) + + if damage > 0: + var area := Area2D.new() + area.collision_layer = 4 + area.collision_mask = 2 + var hit := CollisionShape2D.new() + var circle2 := CircleShape2D.new() + circle2.radius = float(bullet_radius) + 1.0 + hit.shape = circle2 + area.add_child(hit) + body.add_child(area) + area.body_entered.connect(_on_hit) + + +func _on_hit(body: Node2D) -> void: + if body.has_method("hurt"): + body.hurt(damage) diff --git a/scripts/bullet_ring.gd b/scripts/bullet_ring.gd deleted file mode 100644 index f722748..0000000 --- a/scripts/bullet_ring.gd +++ /dev/null @@ -1,76 +0,0 @@ -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)