1
0

blocks.gd 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. class_name Blocks
  2. extends Node2D
  3. ## The core plus rings of rotating blocks around it, generated from the level number.
  4. ## A segment broke, by any means.
  5. signal segment_broken(block: Block)
  6. ## Something exploded (TNT or a bomb ball) at this global position.
  7. signal exploded(at: Vector2)
  8. const BLAST_SCENE := preload("res://scenes/blast.tscn")
  9. const SEGMENTS_PER_RING := 10
  10. const TNT_RADIUS := 80.0
  11. const TNT_CHAIN_DELAY := 0.12 # seconds between one TNT going off and the next
  12. var core: Core
  13. var _rings: Array[Node2D] = []
  14. var _speeds: Array[float] = []
  15. var _frozen_time := 0.0
  16. var _outer_radius := 0.0
  17. var _breakable_total := 0
  18. var _breakable_left := 0
  19. ## Builds the level's rings. Rings are made thinner when needed so they all fit within
  20. ## [param max_radius]; the ring count, and so the difficulty, stays the same.
  21. func build(level: int, max_radius: float) -> void:
  22. var layout := _layout(level)
  23. var size := minf(layout.size, (max_radius - Params.CORE_RADIUS) / layout.rings)
  24. for index in range(layout.rings - 1, -1, -1):
  25. _add_ring(level, size, index, layout.indestructible_chance)
  26. _outer_radius = Params.CORE_RADIUS + layout.rings * size
  27. _breakable_left = _breakable_total
  28. core = Core.new()
  29. add_child(core)
  30. ## Stops the rings turning for a while.
  31. func freeze(seconds: float) -> void:
  32. _frozen_time = maxf(_frozen_time, seconds)
  33. func has_unbreakable() -> bool:
  34. for ring in _rings:
  35. for block: Block in ring.get_children():
  36. if not block.is_breakable() and block.collision_layer != 0:
  37. return true
  38. return false
  39. ## Damages every breakable segment within [param radius] of [param at] (global position).
  40. func explode(at: Vector2, radius: float) -> void:
  41. var blast: Node2D = BLAST_SCENE.instantiate()
  42. blast.radius = radius
  43. blast.position = to_local(at)
  44. add_child(blast)
  45. exploded.emit(at)
  46. var query := PhysicsShapeQueryParameters2D.new()
  47. var circle := CircleShape2D.new()
  48. circle.radius = radius
  49. query.shape = circle
  50. query.transform = Transform2D(0.0, at)
  51. query.collision_mask = Params.LAYER_BLOCKS
  52. for hit in get_world_2d().direct_space_state.intersect_shape(query, 64):
  53. if hit.collider is Block:
  54. (hit.collider as Block).damage()
  55. func _physics_process(delta: float) -> void:
  56. _pull_balls(delta)
  57. if _frozen_time > 0.0:
  58. _frozen_time -= delta
  59. return
  60. for i in _rings.size():
  61. _rings[i].rotation_degrees += _speeds[i] * delta
  62. ## The fewer breakable segments are left, the harder the core pulls nearby balls in.
  63. func _pull_balls(delta: float) -> void:
  64. if _breakable_total == 0 or not is_instance_valid(core) or core.collision_layer == 0:
  65. return
  66. var strength := Params.CORE_PULL * clampf(1.0 - float(_breakable_left) / _breakable_total, 0.0, 1.0)
  67. if strength <= 0.0:
  68. return
  69. var reach := _outer_radius + Params.CORE_PULL_REACH
  70. for ball: Ball in get_tree().get_nodes_in_group("balls"):
  71. var offset := global_position - ball.global_position
  72. var dist := offset.length()
  73. if dist > 0.0 and dist < reach:
  74. ball.velocity += offset / dist * strength * delta
  75. func _on_broken(block: Block) -> void:
  76. _breakable_left -= 1
  77. segment_broken.emit(block)
  78. func _on_detonated(block: Block) -> void:
  79. var at := block.center()
  80. get_tree().create_timer(TNT_CHAIN_DELAY, false, true).timeout.connect(explode.bind(at, TNT_RADIUS))
  81. ## Ring count, ring thickness and indestructible-block chance for a level. All of them only
  82. ## ever grow with the level, so no level is easier than the one before it.
  83. static func _layout(level: int) -> Dictionary:
  84. return {
  85. rings = Params.ring_count(level),
  86. size = Params.RING_SIZE,
  87. indestructible_chance = Params.indestructible_chance(level),
  88. }
  89. ## Picks the kind of one segment. Special kinds unlock every 50 levels; their share of
  90. ## the segments grows with the level, and the newest kind is twice as likely as the others.
  91. static func _segment_kind(level: int, indestructible_chance: float) -> Block.Kind:
  92. if indestructible_chance >= randf():
  93. return Block.Kind.INDESTRUCTIBLE
  94. var unlocked: Array[Block.Kind] = []
  95. for kind: Block.Kind in Block.KINDS:
  96. var unlock: int = Block.KINDS[kind].unlock
  97. if unlock > 0 and unlock <= level:
  98. unlocked.append(kind)
  99. if unlocked.is_empty() or randf() >= Params.special_segment_share(level):
  100. return Block.Kind.NORMAL
  101. var weights: Array[float] = []
  102. for kind in unlocked:
  103. weights.append(2.0 if kind == unlocked[-1] else 1.0)
  104. var roll: float = randf() * weights.reduce(func(sum: float, w: float) -> float: return sum + w, 0.0)
  105. for i in unlocked.size():
  106. roll -= weights[i]
  107. if roll <= 0.0:
  108. return unlocked[i]
  109. return unlocked[-1]
  110. func _add_ring(level: int, size: float, index: int, indestructible_chance: float) -> void:
  111. var inner := index * size + Params.CORE_RADIUS
  112. var outer := (index + 1) * size + Params.CORE_RADIUS
  113. # Evenly spaced segment boundaries, each jittered so segments have uneven lengths.
  114. var step := TAU / SEGMENTS_PER_RING
  115. var bounds: Array[float] = []
  116. for i in SEGMENTS_PER_RING:
  117. bounds.append(step * i + randf_range(-1.0, 1.0) * 0.45 * step)
  118. bounds.append(bounds[0] + TAU)
  119. var ring := Node2D.new()
  120. for i in SEGMENTS_PER_RING:
  121. var block := Block.new(inner, outer, bounds[i], bounds[i + 1], _segment_kind(level, indestructible_chance))
  122. block.detonated.connect(_on_detonated)
  123. block.broken.connect(_on_broken)
  124. if block.is_breakable():
  125. _breakable_total += 1
  126. ring.add_child(block)
  127. add_child(ring)
  128. _rings.append(ring)
  129. var max_speed := Params.ring_speed(level)
  130. _speeds.append(randf_range(-max_speed, max_speed))