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)