Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- extends Node2D
- class_name BulletHellSpawner
- export (Array, Image) var spawning_frames := []
- export (Array, Image) var frames := []
- export (Array, Image) var destroying_frames := []
- export (Texture) var shadow_image : Texture
- export (float) var image_change_offset := 0.2
- export (bool) var apply_animation_variance := false
- export (int) var speed := 200
- export (float) var max_lifetime := 10.0
- export (float) var default_elevation := 15.0
- onready var origin := get_node("Origin") as Position2D
- onready var shared_area := get_node("BulletSharedArea") as Area2D
- onready var navigator = get_node("/root/BaseNavigator")
- onready var max_spawning_frames := spawning_frames.size()
- onready var max_images := frames.size()
- onready var max_destroying_frames := destroying_frames.size()
- var bullets := []
- var destroying_bullets := []
- var is_queued_for_destruction := false
- var bounding_box : Rect2
- var circle_shape : RID
- var z_index_follow : Node2D
- var max_bullets := 3000
- var custom_speed := 0
- # ================================ Lifecycle ================================ #
- func _ready() -> void:
- var _map = ResourceProvider.current_map()
- _map.connect("map_cleaned", self, "_on_map_cleaned")
- func _process(delta: float) -> void:
- var bullets_queued_for_destruction := []
- for i in range(0, bullets.size()):
- # Check if bullet is out of bounds and should be destroyed
- var bullet := bullets[i] as Bullet
- if _bullet_is_out_of_bounds(bullet):
- bullet.reset_before_destruction()
- bullets_queued_for_destruction.append(bullet)
- continue
- var normalized_motion = bullet.movement_vector.normalized()
- var bullet_speed = speed
- if bullet.custom_speed > 0.0:
- bullet_speed = bullet.custom_speed
- elif custom_speed > 0:
- bullet_speed = custom_speed
- var offset : Vector2 = normalized_motion * bullet_speed * delta
- bullet.current_position += offset
- _assign_layer_to_bullet(bullet)
- bullet.animation_lifetime += delta
- bullet.lifetime += delta
- for bullet in bullets_queued_for_destruction:
- _destroy_bullet(bullet)
- bullets_queued_for_destruction.clear()
- for bullet in destroying_bullets:
- bullet.lifetime += delta
- bullet.animation_lifetime += delta
- func _physics_process(delta: float) -> void:
- _apply_movement()
- # ================================= Public ================================== #
- # Updates the global custom speed used for bullets, provided that they don't
- # define a custom speed by themselves
- func set_custom_speed(_custom_speed: int) -> void:
- custom_speed = _custom_speed
- # Updates the enclosing rect that determines how far the bullets can travel
- func set_bounding_box(box: Rect2) -> void:
- bounding_box = box
- # Updates the coordinates where new bullets will spawn
- func set_custom_origin(custom_origin: Vector2) -> void:
- origin.global_position = custom_origin
- # Spawns a new bullet and returns a reference to the created instance
- func spawn_bullet(
- i_movement: Vector2, custom_speed := 0.0, _starting_pos = null) -> Bullet:
- # If the max is reached, start deleting older bullets
- if bullets.size() >= max_bullets:
- _destroy_bullet(bullets[0])
- # Don't spawn bullets if the object is queued for destruction
- if is_queued_for_destruction:
- return null
- var bullet : Bullet = Bullet.new()
- bullet.movement_vector = i_movement
- bullet.custom_speed = custom_speed
- if _starting_pos == null:
- bullet.current_position = origin.position
- else:
- bullet.current_position = _starting_pos
- if apply_animation_variance:
- bullet.animation_variance = randf() * image_change_offset
- _configure_collision_for_bullet(bullet)
- _inject_spawner_data(bullet)
- bullets.append(bullet)
- return bullet
- # Injects a bullet created with the data inside the provided dictionary. Note
- # that this bullet will skip any assignment logic set by `spawn_bullet`.
- func inject_bullet(data: Dictionary) -> void:
- var bullet = Bullet.new()
- for prop in data:
- bullet.set(prop, data[prop])
- _configure_collision_for_bullet(bullet)
- _inject_spawner_data(bullet)
- bullets.append(bullet)
- # Injects a bullet with the provided data to the destroying bullets array
- func inject_destroying_bullet(data: Dictionary) -> void:
- var bullet = Bullet.new()
- for prop in data:
- bullet.set(prop, data[prop])
- _inject_spawner_data(bullet)
- destroying_bullets.append(bullet)
- # Requests the BulletHellSpawner to be destroyed.
- # Note that destruction does not happen as soon as this method is called, instead
- # the spawner will wait until there are no more bullets or destroying bullets
- func request_destruction() -> void:
- is_queued_for_destruction = true
- # Reverts a destruction queue, normally used when another entity needs to keep
- # firing the same kind of bullets that this spawner manages, so the instance is
- # recycled
- func cancel_destruction_request() -> void:
- is_queued_for_destruction = false
- # Removes all managed bullets and destroying bullets, alongside any resources
- # they are using.
- func force_remove_all_bullets() -> void:
- _clean_canvas()
- destroying_bullets.clear()
- # ================================= Private ================================= #
- func _configure_collision_for_bullet(bullet: Bullet) -> void:
- var used_transform : Transform2D = Transform2D(0, position)
- used_transform.origin = bullet.current_position
- var _circle_shape = _generate_shape()
- Physics2DServer.area_add_shape(
- shared_area.get_rid(), _circle_shape, used_transform
- )
- bullet.shape_id = _circle_shape
- func _apply_movement() -> void:
- # Mock area movement to force collision detection
- shared_area.position = shared_area.position
- var used_transform := Transform2D()
- if (
- is_queued_for_destruction and bullets.empty() and
- destroying_bullets.empty()
- ):
- queue_free()
- for i in range(0, bullets.size()):
- var bullet = bullets[i] as Bullet
- used_transform.origin = bullet.current_position
- Physics2DServer.area_set_shape_transform(
- shared_area.get_rid(), i, used_transform
- )
- # Perform required cleanup for the specified bullet
- func _destroy_bullet(bullet: Bullet, instant_destroy := false) -> void:
- if bullet.shape_id != null:
- Physics2DServer.free_rid(bullet.shape_id)
- if bullet.area_id != null:
- Physics2DServer.free_rid(bullet.area_id)
- if bullet.particle_emitter_instance != null:
- bullet.particle_scene_path = ""
- bullet.particle_emitter_instance.request_destruction()
- bullet.particle_emitter_instance = null
- bullets.erase(bullet)
- if !destroying_frames.empty() and !instant_destroy:
- destroying_bullets.append(bullet)
- # Perform any cleanup logic
- func _clean_canvas() -> void:
- for bullet in bullets:
- _destroy_bullet(bullet, true)
- bullets.clear()
- func _generate_shape() -> RID:
- var _shape_id = Physics2DServer.circle_shape_create()
- Physics2DServer.shape_set_data(
- _shape_id, frames[0].get_size().x / 2.0
- )
- return _shape_id
- func _bullet_is_out_of_bounds(bullet: Bullet) -> bool:
- return (
- (
- bounding_box != null and
- !bounding_box.has_point(bullet.current_position)
- ) or
- bullet.lifetime >= max_lifetime
- )
- func _assign_layer_to_bullet(bullet: Bullet) -> void:
- var _player = ResourceProvider.get_player_battle()
- var _enemy = ResourceProvider.current_map().enemy
- var _bullet_bottom := _get_bullet_sprite_base(bullet)
- var player_pos : Vector2 = _player.get_base_position()
- var enemy_pos : Vector2 = _enemy.get_base_position()
- if _bullet_bottom < player_pos.y and _bullet_bottom < enemy_pos.y:
- bullet.layer = "BackLayer"
- elif _bullet_bottom > player_pos.y and _bullet_bottom > enemy_pos.y:
- bullet.layer = "TopLayer"
- else:
- bullet.layer = "MidLayer"
- func _inject_spawner_data(bullet: Bullet) -> void:
- bullet.spawner_properties = {
- "spawning_frames": spawning_frames,
- "frames": frames,
- "destroying_frames": destroying_frames,
- "image_change_offset": image_change_offset,
- "speed": speed,
- "max_lifetime": max_lifetime,
- "default_elevation": default_elevation,
- "shadow_image": shadow_image,
- "spawner_ref": weakref(self)
- }
- func _get_bullet_sprite_base(bullet: Bullet) -> float:
- var _sprite_offset : float = frames[bullet.image_offset].get_size().y / 2.0
- return bullet.current_position.y + _sprite_offset + default_elevation
- # ================================ Callbacks ================================ #
- #func _on_navigation_will_start() -> void:
- # _clean_canvas()
- func _on_map_cleaned() -> void:
- _clean_canvas()
- # ================================ Internal ================================ #
- class Bullet extends Reference:
- # Both are RIDs, but the type is not set to allow them to be nullable
- var area_id = null
- var shape_id = null
- var movement_vector : Vector2
- var current_position : Vector2
- var lifetime : float = 0.0
- var animation_lifetime : float = 0.0
- var image_offset : int = 0
- var layer : String = "front"
- var animation_variance : float = 0.0
- var played_spawning_frames : bool = false
- var custom_speed := 0.0
- var particle_scene_path := ""
- var particle_emitter_instance : DynamicParticleEmitter = null
- var spawner_properties := {}
- func move_to_next_frame() -> void:
- image_offset += 1
- animation_lifetime = 0.0
- func reset_animation_frames() -> void:
- image_offset = 0
- func attach_particles(scene_path: String) -> void:
- particle_scene_path = scene_path
- func reset_before_destruction() -> void:
- lifetime = 0.0
- animation_lifetime = 0.0
- image_offset = 0
- func request_destruction() -> void:
- var spawner = spawner_properties["spawner_ref"].get_ref()
- if spawner != null:
- spawner.destroying_bullets.erase(self)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement