@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 texture: Texture2D = null: set(value): texture = value queue_redraw() _update_visibility_notifier() ## 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 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( texture: Texture2D = null, hitbox_size: Vector2i = Vector2i.ZERO, direction: Vector2 = Vector2.RIGHT, face_direction: bool = false, ) -> Bullet: #var bullet: Bullet = _cached_bullets.pop_back() #if not bullet: #bullet = Bullet.new() var bullet := Bullet.new() bullet.texture = texture bullet.hitbox_size = hitbox_size bullet.direction = direction bullet.face_direction = face_direction bullet.time_elapsed = 0.0 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 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(): if face_direction: rotation = direction.angle() func _draw() -> void: if texture: draw_texture(texture, -texture.get_size() * 0.5) 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 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(-texture.get_size(), texture.get_size() * 2.0), (func(): pass), _on_screen_exited ) # called when the bullet leaves the screen. func _on_screen_exited() -> void: recycle()