1
0

block.gd 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. class_name Block
  2. extends AnimatableBody2D
  3. ## One arc-shaped segment of a ring around the core.
  4. const GAP := 4.0 # px between neighbouring tiles
  5. const CORNER := 4.0 # corner rounding radius in px
  6. const BREAK_SECONDS := 0.15 # fade-out time, after a short flash
  7. var indestructible := false
  8. var color := Params.COLOR_BLUE
  9. var _polygon := PackedVector2Array()
  10. ## 0 to 1: how far the tile is washed towards white while breaking.
  11. var _flash := 0.0:
  12. set(value):
  13. _flash = value
  14. queue_redraw()
  15. func _init(inner: float, outer: float, begin: float, end: float, is_indestructible: bool) -> void:
  16. sync_to_physics = false
  17. collision_layer = Params.LAYER_BLOCKS
  18. collision_mask = 0
  19. indestructible = is_indestructible
  20. # Collision uses the drawn outline, so the ball bounces exactly where it touches.
  21. _polygon = _round_corners(_sector(inner + GAP / 2.0, outer - GAP / 2.0, begin, end, GAP / 2.0))
  22. # Very short segments can leave a sliver too thin to split into convex pieces; skip its
  23. # collision rather than let Godot fail on it.
  24. if not Geometry2D.decompose_polygon_in_convex(_polygon).is_empty():
  25. var collision := CollisionPolygon2D.new()
  26. collision.polygon = _polygon
  27. add_child(collision)
  28. if indestructible:
  29. color = Params.COLOR_ORANGE
  30. else:
  31. color = Color8(
  32. int(102 * (randf() / 5.0 + 0.5)),
  33. int(217 * (randf() / 5.0 + 0.5)),
  34. int(239 * (randf() / 5.0 + 0.5)))
  35. ## Flashes and fades out, then frees itself. Stops colliding right away.
  36. func destroy() -> void:
  37. collision_layer = 0
  38. var tween := create_tween()
  39. tween.tween_property(self, "_flash", 0.8, BREAK_SECONDS / 3.0)
  40. tween.tween_property(self, "modulate:a", 0.0, BREAK_SECONDS)
  41. tween.parallel().tween_property(self, "scale", Vector2.ONE * 1.04, BREAK_SECONDS)
  42. tween.tween_callback(queue_free)
  43. func _draw() -> void:
  44. if _polygon.size() < 3:
  45. return # segment too short to show at all
  46. var fill := color.lerp(Color.WHITE, _flash)
  47. draw_colored_polygon(_polygon, fill)
  48. # Polygons have no antialiasing of their own; a smooth outline softens the edge.
  49. var outline := _polygon.duplicate()
  50. outline.append(_polygon[0])
  51. draw_polyline(outline, fill, 1.0, true)
  52. ## Ring segment between two radii. [param gap] trims each side by that many pixels,
  53. ## so the space between neighbours has the same width at every radius. An edge too short
  54. ## to trim collapses to a point, turning the segment into a wedge.
  55. static func _sector(inner: float, outer: float, begin: float, end: float, gap: float) -> PackedVector2Array:
  56. var steps := maxi(2, int((end - begin) * sqrt(outer) / 1.5))
  57. return _trimmed_arc(outer, begin, end, gap, steps) + _trimmed_arc(inner, end, begin, -gap, steps)
  58. static func _trimmed_arc(radius: float, from: float, to: float, gap: float, steps: int) -> PackedVector2Array:
  59. var trim := gap / radius
  60. if (to - from - 2.0 * trim) * signf(to - from) <= 0.0:
  61. var middle := (from + to) / 2.0
  62. return PackedVector2Array([Vector2(sin(middle), cos(middle)) * radius])
  63. return _arc(radius, from + trim, to - trim, steps)
  64. ## Shrinks then regrows the polygon with round joins, which rounds its corners.
  65. static func _round_corners(polygon: PackedVector2Array) -> PackedVector2Array:
  66. var shrunk := Geometry2D.offset_polygon(polygon, -CORNER)
  67. if shrunk.is_empty():
  68. return polygon
  69. var rounded := Geometry2D.offset_polygon(shrunk[0], CORNER, Geometry2D.JOIN_ROUND)
  70. return polygon if rounded.is_empty() else _without_duplicates(rounded[0])
  71. ## offset_polygon can emit repeated points; zero-length edges break convex decomposition.
  72. static func _without_duplicates(polygon: PackedVector2Array) -> PackedVector2Array:
  73. var result := PackedVector2Array()
  74. for point in polygon:
  75. if result.is_empty() or not point.is_equal_approx(result[-1]):
  76. result.append(point)
  77. if result.size() > 1 and result[0].is_equal_approx(result[-1]):
  78. result.remove_at(result.size() - 1)
  79. return result
  80. static func _arc(radius: float, from: float, to: float, steps: int) -> PackedVector2Array:
  81. var points := PackedVector2Array()
  82. for i in steps + 1:
  83. var angle := lerpf(from, to, float(i) / steps)
  84. points.append(Vector2(sin(angle), cos(angle)) * radius)
  85. return points