Files
sijcdusmode/scripts/bullet.gd
T
sijcdu 0d2bccc3a9 Monsters are now unkillable and fire a fixed bullet pattern; add periodic lasers
Removed the stomp-kill from walker.gd: landing on an enemy now just costs a heart like any other contact, so the only way past is to weave through the pattern or use the key throw + W warp. Enemies fire a fan of bullets every fire_interval whose directions are computed only from the facing and the exported spread, never from the player, so every volley traces the identical path and can be memorised. Bullets are code-generated circles (no art needed), die on hitting terrain via a probe raycast, and are freed after their lifetime. New laser prefab cycles wait -> blinking warmup -> firing, and only damages during the firing phase.
2026-10-07 22:12:14 +08:00

80 lines
2.1 KiB
GDScript

extends Area2D
## 弹幕子弹:直线飞行,碰到玩家扣血,撞到地形或超时消失。
## 贴图是代码生成的实心圆,不需要额外美术素材。
@export var speed: float = 70.0
@export var damage: int = 1
@export var lifetime: float = 8.0
@export var radius: int = 3
@export var bullet_color: Color = Color(1.0, 0.35, 0.7)
var direction: Vector2 = Vector2.LEFT
var _age: float = 0.0
var _probe: RayCast2D = null
static var _cache: Dictionary = {}
## 生成(并缓存)一个实心圆贴图
static func circle_texture(r: int, color: Color) -> ImageTexture:
var key := "%d|%s" % [r, color.to_html()]
if _cache.has(key):
return _cache[key]
var size: int = r * 2 + 1
var img := Image.create_empty(size, size, false, Image.FORMAT_RGBA8)
img.fill(Color(0.0, 0.0, 0.0, 0.0))
var limit := float(r * r) + float(r) * 0.6
for y in size:
for x in size:
var dx := float(x - r)
var dy := float(y - r)
if dx * dx + dy * dy <= limit:
img.set_pixel(x, y, color)
var tex := ImageTexture.create_from_image(img)
_cache[key] = tex
return tex
func _ready() -> void:
add_to_group("bullet")
var sprite := get_node_or_null("Sprite") as Sprite2D
if sprite != null:
sprite.texture = circle_texture(radius, bullet_color)
var hit := get_node_or_null("HitBox") as CollisionShape2D
if hit != null and hit.shape is CircleShape2D:
var circle := (hit.shape as CircleShape2D).duplicate() as CircleShape2D
circle.radius = float(radius)
hit.shape = circle
_probe = get_node_or_null("Probe") as RayCast2D
body_entered.connect(_on_body_entered)
func launch(dir: Vector2, spd: float = -1.0) -> void:
direction = dir.normalized()
if spd > 0.0:
speed = spd
func _physics_process(delta: float) -> void:
_age += delta
if _age >= lifetime:
queue_free()
return
var step := direction * speed * delta
if _probe != null:
_probe.target_position = step * 1.5
_probe.force_raycast_update()
if _probe.is_colliding():
queue_free()
return
position += step
func _on_body_entered(body: Node2D) -> void:
if body.has_method("hurt"):
body.hurt(damage)
queue_free()