135 lines
3.7 KiB
GDScript
135 lines
3.7 KiB
GDScript
@tool
|
|
@icon("bullet.svg")
|
|
class_name Bullet
|
|
extends Area2D
|
|
|
|
|
|
## Emitted whenever a bullet is recycled.
|
|
signal recycled()
|
|
|
|
|
|
# cached shapes for each hitbox size
|
|
static var _hitbox_shapes: Dictionary[Vector2i, RectangleShape2D] = {}
|
|
|
|
|
|
## Base graphic/animation to display, modulated by [member color].
|
|
@export var base_graphic: AnimationStrip = null:
|
|
set(value):
|
|
base_graphic = value
|
|
queue_redraw()
|
|
_update_visibility_notifier()
|
|
|
|
## The color that [member base_graphic] will be modulated by.
|
|
@export var color: Color
|
|
|
|
## Extra graphic drawn over [member base_graphic] and not modulated by [member 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
|
|
|
|
|
|
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.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
|
|
|
|
|
|
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 []
|
|
|
|
|
|
## Removes the bullet from the scene tree and returns it to the bullet cache to be
|
|
## re-used later.
|
|
func recycle() -> void:
|
|
queue_free()
|
|
recycled.emit()
|
|
|
|
|
|
# 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()
|