@tool class_name TerrainBlock extends StaticBody2D ## TerrainBlock - Reusable StaticBody2D vector-terrain component (Phase 1). ## ## A self-contained terrain block: a StaticBody2D root with three children built ## in code — a Polygon2D (interior fill), a Line2D (crisp vector outline, closed ## by appending the first vertex, rounded joints/caps), and a CollisionPolygon2D ## configured with BUILD_SOLIDS so concave terrain blocks collide correctly. ## Fully @tool: exported properties update the children live in the editor. # --------------------------------------------------------------------------- # Child node names # --------------------------------------------------------------------------- const POLYGON_NODE_NAME := "Polygon2D" const OUTLINE_NODE_NAME := "Outline" const COLLISION_NODE_NAME := "CollisionPolygon2D" # --------------------------------------------------------------------------- # Exported properties # --------------------------------------------------------------------------- ## The terrain polygon's vertices (local space). The unified setter pushes the ## array to the Polygon2D and CollisionPolygon2D, and closes the Line2D loop by ## appending the first vertex to the end. @export var polygon_points: PackedVector2Array = PackedVector2Array(): set(value): polygon_points = value _apply_points() ## Interior fill color (Polygon2D). @export var fill_color: Color = Color(0.25, 0.55, 0.25, 1.0): set(value): fill_color = value if _polygon != null: _polygon.color = value ## Outline color (Line2D). @export var outline_color: Color = Color(0.05, 0.10, 0.05, 1.0): set(value): outline_color = value if _outline != null: _outline.default_color = value ## Outline width in pixels (Line2D). @export var outline_width: float = 2.0: set(value): outline_width = value if _outline != null: _outline.width = value # --------------------------------------------------------------------------- # Internal node references (built in _ready, @tool-safe) # --------------------------------------------------------------------------- var _polygon: Polygon2D var _outline: Line2D var _collision: CollisionPolygon2D # --------------------------------------------------------------------------- # Lifecycle # --------------------------------------------------------------------------- func _ready() -> void: _ensure_children() _apply_points() _apply_style() # --------------------------------------------------------------------------- # Internal build / apply # --------------------------------------------------------------------------- func _ensure_children() -> void: _polygon = get_node_or_null(NodePath(POLYGON_NODE_NAME)) as Polygon2D if _polygon == null: _polygon = Polygon2D.new() _polygon.name = POLYGON_NODE_NAME add_child(_polygon) _outline = get_node_or_null(NodePath(OUTLINE_NODE_NAME)) as Line2D if _outline == null: _outline = Line2D.new() _outline.name = OUTLINE_NODE_NAME _outline.joint_mode = Line2D.LINE_JOINT_ROUND _outline.begin_cap_mode = Line2D.LINE_CAP_ROUND _outline.end_cap_mode = Line2D.LINE_CAP_ROUND add_child(_outline) _collision = get_node_or_null(NodePath(COLLISION_NODE_NAME)) as CollisionPolygon2D if _collision == null: _collision = CollisionPolygon2D.new() _collision.name = COLLISION_NODE_NAME _collision.build_mode = CollisionPolygon2D.BUILD_SOLIDS add_child(_collision) ## Pushes the current polygon_points to the Polygon2D and CollisionPolygon2D, ## and appends the first vertex to the Line2D to close the border loop. func _apply_points() -> void: if _polygon != null: _polygon.polygon = polygon_points if _collision != null: _collision.polygon = polygon_points if polygon_points.size() >= 3 else PackedVector2Array() if _outline != null: var outline_points := polygon_points.duplicate() if not outline_points.is_empty(): outline_points.append(polygon_points[0]) _outline.points = outline_points func _apply_style() -> void: if _polygon != null: _polygon.color = fill_color if _outline != null: _outline.default_color = outline_color _outline.width = outline_width