Advertisement
Guest User

Untitled

a guest
Feb 6th, 2020
232
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.64 KB | None | 0 0
  1. extends Node2D
  2. class_name BulletHellSpawner
  3.  
  4. export (Array, Image) var spawning_frames := []
  5. export (Array, Image) var frames := []
  6. export (Array, Image) var destroying_frames := []
  7. export (Texture) var shadow_image : Texture
  8.  
  9. export (float) var image_change_offset := 0.2
  10. export (bool) var apply_animation_variance := false
  11. export (int) var speed := 200
  12. export (float) var max_lifetime := 10.0
  13. export (float) var default_elevation := 15.0
  14.  
  15. onready var origin := get_node("Origin") as Position2D
  16. onready var shared_area := get_node("BulletSharedArea") as Area2D
  17.  
  18. onready var navigator = get_node("/root/BaseNavigator")
  19.  
  20. onready var max_spawning_frames := spawning_frames.size()
  21. onready var max_images := frames.size()
  22. onready var max_destroying_frames := destroying_frames.size()
  23.  
  24. var bullets := []
  25. var destroying_bullets := []
  26. var is_queued_for_destruction := false
  27. var bounding_box : Rect2
  28. var circle_shape : RID
  29.  
  30. var z_index_follow : Node2D
  31.  
  32. var max_bullets := 3000
  33. var custom_speed := 0
  34.  
  35. # ================================ Lifecycle ================================ #
  36.  
  37. func _ready() -> void:
  38. var _map = ResourceProvider.current_map()
  39. _map.connect("map_cleaned", self, "_on_map_cleaned")
  40.  
  41. func _process(delta: float) -> void:
  42.  
  43. var bullets_queued_for_destruction := []
  44.  
  45. for i in range(0, bullets.size()):
  46.  
  47. # Check if bullet is out of bounds and should be destroyed
  48. var bullet := bullets[i] as Bullet
  49. if _bullet_is_out_of_bounds(bullet):
  50. bullet.reset_before_destruction()
  51. bullets_queued_for_destruction.append(bullet)
  52. continue
  53.  
  54. var normalized_motion = bullet.movement_vector.normalized()
  55. var bullet_speed = speed
  56. if bullet.custom_speed > 0.0:
  57. bullet_speed = bullet.custom_speed
  58. elif custom_speed > 0:
  59. bullet_speed = custom_speed
  60.  
  61. var offset : Vector2 = normalized_motion * bullet_speed * delta
  62.  
  63. bullet.current_position += offset
  64.  
  65. _assign_layer_to_bullet(bullet)
  66.  
  67. bullet.animation_lifetime += delta
  68. bullet.lifetime += delta
  69.  
  70. for bullet in bullets_queued_for_destruction:
  71. _destroy_bullet(bullet)
  72. bullets_queued_for_destruction.clear()
  73.  
  74. for bullet in destroying_bullets:
  75. bullet.lifetime += delta
  76. bullet.animation_lifetime += delta
  77.  
  78. func _physics_process(delta: float) -> void:
  79. _apply_movement()
  80.  
  81. # ================================= Public ================================== #
  82.  
  83. # Updates the global custom speed used for bullets, provided that they don't
  84. # define a custom speed by themselves
  85. func set_custom_speed(_custom_speed: int) -> void:
  86. custom_speed = _custom_speed
  87.  
  88. # Updates the enclosing rect that determines how far the bullets can travel
  89. func set_bounding_box(box: Rect2) -> void:
  90. bounding_box = box
  91.  
  92. # Updates the coordinates where new bullets will spawn
  93. func set_custom_origin(custom_origin: Vector2) -> void:
  94. origin.global_position = custom_origin
  95.  
  96. # Spawns a new bullet and returns a reference to the created instance
  97. func spawn_bullet(
  98. i_movement: Vector2, custom_speed := 0.0, _starting_pos = null) -> Bullet:
  99.  
  100. # If the max is reached, start deleting older bullets
  101. if bullets.size() >= max_bullets:
  102. _destroy_bullet(bullets[0])
  103.  
  104. # Don't spawn bullets if the object is queued for destruction
  105. if is_queued_for_destruction:
  106. return null
  107.  
  108. var bullet : Bullet = Bullet.new()
  109. bullet.movement_vector = i_movement
  110. bullet.custom_speed = custom_speed
  111.  
  112. if _starting_pos == null:
  113. bullet.current_position = origin.position
  114. else:
  115. bullet.current_position = _starting_pos
  116.  
  117. if apply_animation_variance:
  118. bullet.animation_variance = randf() * image_change_offset
  119.  
  120. _configure_collision_for_bullet(bullet)
  121. _inject_spawner_data(bullet)
  122.  
  123. bullets.append(bullet)
  124.  
  125. return bullet
  126.  
  127. # Injects a bullet created with the data inside the provided dictionary. Note
  128. # that this bullet will skip any assignment logic set by `spawn_bullet`.
  129. func inject_bullet(data: Dictionary) -> void:
  130.  
  131. var bullet = Bullet.new()
  132. for prop in data:
  133. bullet.set(prop, data[prop])
  134.  
  135. _configure_collision_for_bullet(bullet)
  136. _inject_spawner_data(bullet)
  137.  
  138. bullets.append(bullet)
  139.  
  140. # Injects a bullet with the provided data to the destroying bullets array
  141. func inject_destroying_bullet(data: Dictionary) -> void:
  142.  
  143. var bullet = Bullet.new()
  144. for prop in data:
  145. bullet.set(prop, data[prop])
  146.  
  147. _inject_spawner_data(bullet)
  148.  
  149. destroying_bullets.append(bullet)
  150.  
  151. # Requests the BulletHellSpawner to be destroyed.
  152. # Note that destruction does not happen as soon as this method is called, instead
  153. # the spawner will wait until there are no more bullets or destroying bullets
  154. func request_destruction() -> void:
  155. is_queued_for_destruction = true
  156.  
  157. # Reverts a destruction queue, normally used when another entity needs to keep
  158. # firing the same kind of bullets that this spawner manages, so the instance is
  159. # recycled
  160. func cancel_destruction_request() -> void:
  161. is_queued_for_destruction = false
  162.  
  163. # Removes all managed bullets and destroying bullets, alongside any resources
  164. # they are using.
  165. func force_remove_all_bullets() -> void:
  166. _clean_canvas()
  167. destroying_bullets.clear()
  168.  
  169. # ================================= Private ================================= #
  170.  
  171. func _configure_collision_for_bullet(bullet: Bullet) -> void:
  172.  
  173. var used_transform : Transform2D = Transform2D(0, position)
  174. used_transform.origin = bullet.current_position
  175.  
  176. var _circle_shape = _generate_shape()
  177. Physics2DServer.area_add_shape(
  178. shared_area.get_rid(), _circle_shape, used_transform
  179. )
  180.  
  181. bullet.shape_id = _circle_shape
  182.  
  183. func _apply_movement() -> void:
  184.  
  185. # Mock area movement to force collision detection
  186. shared_area.position = shared_area.position
  187.  
  188. var used_transform := Transform2D()
  189.  
  190. if (
  191. is_queued_for_destruction and bullets.empty() and
  192. destroying_bullets.empty()
  193. ):
  194. queue_free()
  195.  
  196. for i in range(0, bullets.size()):
  197.  
  198. var bullet = bullets[i] as Bullet
  199. used_transform.origin = bullet.current_position
  200. Physics2DServer.area_set_shape_transform(
  201. shared_area.get_rid(), i, used_transform
  202. )
  203.  
  204. # Perform required cleanup for the specified bullet
  205. func _destroy_bullet(bullet: Bullet, instant_destroy := false) -> void:
  206.  
  207. if bullet.shape_id != null:
  208. Physics2DServer.free_rid(bullet.shape_id)
  209. if bullet.area_id != null:
  210. Physics2DServer.free_rid(bullet.area_id)
  211.  
  212. if bullet.particle_emitter_instance != null:
  213. bullet.particle_scene_path = ""
  214. bullet.particle_emitter_instance.request_destruction()
  215. bullet.particle_emitter_instance = null
  216.  
  217. bullets.erase(bullet)
  218.  
  219. if !destroying_frames.empty() and !instant_destroy:
  220. destroying_bullets.append(bullet)
  221.  
  222. # Perform any cleanup logic
  223. func _clean_canvas() -> void:
  224. for bullet in bullets:
  225. _destroy_bullet(bullet, true)
  226. bullets.clear()
  227.  
  228. func _generate_shape() -> RID:
  229. var _shape_id = Physics2DServer.circle_shape_create()
  230. Physics2DServer.shape_set_data(
  231. _shape_id, frames[0].get_size().x / 2.0
  232. )
  233. return _shape_id
  234.  
  235. func _bullet_is_out_of_bounds(bullet: Bullet) -> bool:
  236. return (
  237. (
  238. bounding_box != null and
  239. !bounding_box.has_point(bullet.current_position)
  240. ) or
  241. bullet.lifetime >= max_lifetime
  242. )
  243.  
  244. func _assign_layer_to_bullet(bullet: Bullet) -> void:
  245.  
  246. var _player = ResourceProvider.get_player_battle()
  247. var _enemy = ResourceProvider.current_map().enemy
  248.  
  249. var _bullet_bottom := _get_bullet_sprite_base(bullet)
  250.  
  251. var player_pos : Vector2 = _player.get_base_position()
  252. var enemy_pos : Vector2 = _enemy.get_base_position()
  253.  
  254. if _bullet_bottom < player_pos.y and _bullet_bottom < enemy_pos.y:
  255. bullet.layer = "BackLayer"
  256. elif _bullet_bottom > player_pos.y and _bullet_bottom > enemy_pos.y:
  257. bullet.layer = "TopLayer"
  258. else:
  259. bullet.layer = "MidLayer"
  260.  
  261. func _inject_spawner_data(bullet: Bullet) -> void:
  262. bullet.spawner_properties = {
  263. "spawning_frames": spawning_frames,
  264. "frames": frames,
  265. "destroying_frames": destroying_frames,
  266. "image_change_offset": image_change_offset,
  267. "speed": speed,
  268. "max_lifetime": max_lifetime,
  269. "default_elevation": default_elevation,
  270. "shadow_image": shadow_image,
  271. "spawner_ref": weakref(self)
  272. }
  273.  
  274. func _get_bullet_sprite_base(bullet: Bullet) -> float:
  275. var _sprite_offset : float = frames[bullet.image_offset].get_size().y / 2.0
  276. return bullet.current_position.y + _sprite_offset + default_elevation
  277.  
  278. # ================================ Callbacks ================================ #
  279.  
  280. #func _on_navigation_will_start() -> void:
  281. # _clean_canvas()
  282.  
  283. func _on_map_cleaned() -> void:
  284. _clean_canvas()
  285.  
  286. # ================================ Internal ================================ #
  287.  
  288. class Bullet extends Reference:
  289.  
  290. # Both are RIDs, but the type is not set to allow them to be nullable
  291. var area_id = null
  292. var shape_id = null
  293.  
  294. var movement_vector : Vector2
  295. var current_position : Vector2
  296. var lifetime : float = 0.0
  297. var animation_lifetime : float = 0.0
  298. var image_offset : int = 0
  299. var layer : String = "front"
  300. var animation_variance : float = 0.0
  301. var played_spawning_frames : bool = false
  302. var custom_speed := 0.0
  303.  
  304. var particle_scene_path := ""
  305. var particle_emitter_instance : DynamicParticleEmitter = null
  306.  
  307. var spawner_properties := {}
  308.  
  309. func move_to_next_frame() -> void:
  310. image_offset += 1
  311. animation_lifetime = 0.0
  312.  
  313. func reset_animation_frames() -> void:
  314. image_offset = 0
  315.  
  316. func attach_particles(scene_path: String) -> void:
  317. particle_scene_path = scene_path
  318.  
  319. func reset_before_destruction() -> void:
  320. lifetime = 0.0
  321. animation_lifetime = 0.0
  322. image_offset = 0
  323.  
  324. func request_destruction() -> void:
  325. var spawner = spawner_properties["spawner_ref"].get_ref()
  326. if spawner != null:
  327. spawner.destroying_bullets.erase(self)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement