defdf202e6
Solid bullets were blocking the patrolling enemies, so the field now defaults to solid = false: each bullet is a plain StaticBody2D with no collision layer, plus an Area2D that only detects the player, so nothing is physically obstructed while contact still costs a heart. Ring radii changed from [5, 10, 15] to [3, 6, 9]. Damage is now applied every frame to whatever is overlapping, not only on body_entered, so standing inside a bullet keeps hurting instead of only the first touch. Also picks up the editor change that removed Walker_03.
143 lines
4.3 KiB
GDScript
143 lines
4.3 KiB
GDScript
extends Node2D
|
||
|
||
## 静态弹幕障碍:以自己为圆心,一圈一圈地摆上**固定不动、不会发射**的子弹。
|
||
##
|
||
## 两种形态:
|
||
## - solid = true :实心障碍,挡路、能踩上去(注意:会连小怪一起挡住)
|
||
## - solid = false:可以穿透,只做伤害判定 —— 配合 damage > 0 就是普通弹幕
|
||
##
|
||
## 落在实心地砖里、或跑出关卡范围的子弹会自动跳过。
|
||
|
||
const BULLET_SCRIPT := preload("res://scripts/bullet.gd")
|
||
|
||
@export var enabled: bool = true
|
||
## 每一圈的半径(单位:格)。默认 3 格、6 格、9 格
|
||
@export var radii_cells: Array[int] = [3, 6, 9]
|
||
## 每圈摆几颗
|
||
@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 = false
|
||
## 碰到扣几颗心。0 = 不扣血
|
||
@export var damage: int = 1
|
||
## 超出这个范围的子弹不生成(默认整个关卡 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
|
||
|
||
var _hurt_areas: Array = []
|
||
|
||
|
||
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
|
||
_hurt_areas.clear()
|
||
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
|
||
|
||
|
||
## 每帧检查一次:站在子弹里也会持续扣血
|
||
## (不能只靠 body_entered —— 玩家本来就在子弹里的话不会触发)
|
||
func _physics_process(_delta: float) -> void:
|
||
if damage <= 0 or _hurt_areas.is_empty():
|
||
return
|
||
for area in _hurt_areas:
|
||
if not is_instance_valid(area):
|
||
continue
|
||
for body in area.get_overlapping_bodies():
|
||
if body.has_method("hurt"):
|
||
body.hurt(damage)
|
||
|
||
|
||
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)
|
||
_hurt_areas.append(area)
|