8127c787f8
Interval dropped from 1.0s +- 0.6 to 0.4s +- 0.25 (about 2.5 bullets per second instead of 1), which measured 13 drops in 6 seconds versus 6 before. bullet.gd gained stop_on_obstacle (default false): the probe raycast is disabled, so rain bullets fall straight through terrain and ropes and are only recycled by their lifetime or by dropping past despawn_y. Verified a bullet falling from y=190 goes through the floor at y=225 and despawns at y=340. Also picks up the editor changes: the rope tiles were removed from level 3 and a few nodes gained unique ids.
87 lines
2.4 KiB
GDScript
87 lines
2.4 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)
|
|
## 撞到地形/绳子之类的障碍就消失。
|
|
## 关掉(默认)的话子弹会**穿过一切**,只靠存活时间和出界来回收
|
|
@export var stop_on_obstacle: bool = false
|
|
## 掉到这个世界 y 以下就自动回收(默认在关卡底部之外)
|
|
@export var despawn_y: float = 340.0
|
|
|
|
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
|
|
if _probe != null:
|
|
_probe.enabled = stop_on_obstacle
|
|
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 or global_position.y > despawn_y:
|
|
queue_free()
|
|
return
|
|
|
|
var step := direction * speed * delta
|
|
if stop_on_obstacle and _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()
|