0d2bccc3a9
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.
80 lines
2.1 KiB
GDScript
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()
|