paratate/systems/bullets/bullet.gd

150 lines
3.9 KiB
GDScript

@tool
@icon("bullet.svg")
class_name Bullet
extends Area2D
## Emitted whenever a bullet is recycled.
signal recycled()
# ## The number of bullets to allocate at startup.
#const INITIAL_ALLOCATED_BULLETS: int = 2000
## Texture to draw for the bullet.
@export var base_graphic: AnimationStrip = null:
set(value):
base_graphic = value
queue_redraw()
_update_visibility_notifier()
@export var color: Color
@export var overlay_graphic: AnimationStrip = null:
set(value):
overlay_graphic = value
queue_redraw()
## Size of the bullet's collision box.
@export var hitbox_size: Vector2i = Vector2i.ZERO:
set(value):
hitbox_size = value
if not _hitbox_shapes.has(hitbox_size):
var new_shape = RectangleShape2D.new()
new_shape.size = hitbox_size
_hitbox_shapes[hitbox_size] = new_shape
_hitbox.shape = _hitbox_shapes[hitbox_size]
## The direction that the bullet is travelling.
@export_custom(0, "direction") var direction: Vector2 = Vector2.RIGHT
## If [code]true[/code], the bullet will always rotate to face toward [member direction].
@export var face_direction: bool = false
## The amount of time in seconds that the bullet has existed.
var time_elapsed: float = 0.0
## Whether the bullet has already been grazed by the player.
var grazed: bool = false
#static var _cached_bullets: Array[Bullet] = []
static var _hitbox_shapes: Dictionary[Vector2i, RectangleShape2D] = {}
var _hitbox: CollisionShape2D = CollisionShape2D.new()
## Returns a new [Bullet], which may be sourced from the cached bullets.
@warning_ignore("shadowed_variable")
static func create(
preset: BulletPreset,
direction: Vector2 = Vector2.RIGHT,
) -> Bullet:
#var bullet: Bullet = _cached_bullets.pop_back()
#if not bullet:
#bullet = Bullet.new()
var bullet := Bullet.new()
if not preset.base_graphics.is_empty():
var index = randi() % preset.base_graphics.size()
bullet.base_graphic = preset.base_graphics[index]
bullet.overlay_graphic = preset.overlay_graphics[index]
if not preset.colors.is_empty():
bullet.color = preset.colors.pick_random()
bullet.hitbox_size = preset.hitbox_size
bullet.face_direction = preset.face_direction
bullet.direction = direction
bullet.time_elapsed = 0.0
bullet.grazed = false
return bullet
## Removes the bullet from the scene tree and returns it to the bullet cache to be
## re-used later.
func recycle() -> void:
#if is_inside_tree():
#get_parent().remove_child(self)
#_cached_bullets.append(self)
queue_free()
recycled.emit()
#static func _static_init() -> void:
#for _i in INITIAL_ALLOCATED_BULLETS:
#_cached_bullets.append(Bullet.new())
func _init() -> void:
monitoring = false
collision_layer = 1 << 3
collision_mask = 0
add_to_group(&"bullets")
_hitbox.debug_color.a = 0.0
add_child(_hitbox)
func _enter_tree() -> void:
_update_visibility_notifier()
func _physics_process(_delta: float) -> void:
if not Engine.is_editor_hint():
queue_redraw()
if face_direction:
rotation = direction.angle()
func _draw() -> void:
if base_graphic:
base_graphic.draw(self, time_elapsed, color)
if overlay_graphic:
overlay_graphic.draw(self, time_elapsed, Color.WHITE)
func _get_configuration_warnings() -> PackedStringArray:
return []
# sets the canvas item up to notify when it leaves the screen
# this essentially mimics `VisibleOnScreenNotifier` without an additional node
func _update_visibility_notifier() -> void:
if not base_graphic or not base_graphic.texture or not is_inside_tree() or Engine.is_editor_hint():
return
# the visibility rect is set to twice the size of the texture to add a little margin
# (func(): pass) is the cleanest way i could think of to have a callback that does nothing
RenderingServer.canvas_item_set_visibility_notifier(
get_canvas_item(), true,
Rect2(-base_graphic.texture.get_size(), base_graphic.texture.get_size() * 2.0),
(func(): pass), _on_screen_exited
)
# called when the bullet leaves the screen.
func _on_screen_exited() -> void:
recycle()