class_name PropUtils extends RefCounted ## PropUtils - Static factory for dynamic vector props. ## ## Primitive generators (box, ball, plank, triangle) return shape-payload ## dictionaries with default dimensions and color themes. spawn_prop instantiates ## a PropBlock, applies the payload + a physics material preset, sets an initial ## velocity, and adds it to a container. Custom polygon points are sanitized ## through TerrainUtils.sanitize_points(). # --------------------------------------------------------------------------- # Preloaded dependencies (resolved directly, independent of the global class # registry, so this script compiles even when the editor's class cache is stale) # --------------------------------------------------------------------------- const PropBlockScript := preload("res://scripts/prop_block.gd") const TerrainUtilsScript := preload("res://scripts/terrain_utils.gd") # --------------------------------------------------------------------------- # Color themes # --------------------------------------------------------------------------- const WOOD_FILL := Color(0.55, 0.38, 0.2, 1.0) const WOOD_OUTLINE := Color(0.25, 0.16, 0.06, 1.0) const RUBBER_FILL := Color(0.9, 0.2, 0.2, 1.0) const RUBBER_OUTLINE := Color(0.35, 0.05, 0.05, 1.0) const METAL_FILL := Color(0.5, 0.55, 0.6, 1.0) const METAL_OUTLINE := Color(0.15, 0.18, 0.22, 1.0) const CARDBOARD_FILL := Color(0.85, 0.72, 0.45, 1.0) const CARDBOARD_OUTLINE := Color(0.4, 0.32, 0.18, 1.0) const DEFAULT_OUTLINE_WIDTH: float = 2.0 # --------------------------------------------------------------------------- # Primitive generators # --------------------------------------------------------------------------- static func create_box( size: Vector2 = Vector2(48.0, 48.0), fill_color: Color = WOOD_FILL, outline_color: Color = WOOD_OUTLINE, outline_width: float = DEFAULT_OUTLINE_WIDTH ) -> Dictionary: var half := size * 0.5 return { "type": PropBlockScript.ShapeType.POLYGON, "points": PackedVector2Array([ Vector2(-half.x, -half.y), Vector2(half.x, -half.y), Vector2(half.x, half.y), Vector2(-half.x, half.y), ]), "fill_color": fill_color, "outline_color": outline_color, "outline_width": outline_width, } static func create_ball( radius: float = 24.0, fill_color: Color = RUBBER_FILL, outline_color: Color = RUBBER_OUTLINE, outline_width: float = DEFAULT_OUTLINE_WIDTH ) -> Dictionary: return { "type": PropBlockScript.ShapeType.CIRCLE, "radius": radius, "fill_color": fill_color, "outline_color": outline_color, "outline_width": outline_width, } static func create_plank( length: float = 160.0, thickness: float = 16.0, fill_color: Color = METAL_FILL, outline_color: Color = METAL_OUTLINE, outline_width: float = DEFAULT_OUTLINE_WIDTH ) -> Dictionary: var half_length := length * 0.5 var half_thickness := thickness * 0.5 return { "type": PropBlockScript.ShapeType.POLYGON, "points": PackedVector2Array([ Vector2(-half_length, -half_thickness), Vector2(half_length, -half_thickness), Vector2(half_length, half_thickness), Vector2(-half_length, half_thickness), ]), "fill_color": fill_color, "outline_color": outline_color, "outline_width": outline_width, } static func create_triangle( base: float = 56.0, height: float = 48.0, fill_color: Color = CARDBOARD_FILL, outline_color: Color = CARDBOARD_OUTLINE, outline_width: float = DEFAULT_OUTLINE_WIDTH ) -> Dictionary: var half_base := base * 0.5 var half_height := height * 0.5 return { "type": PropBlockScript.ShapeType.POLYGON, "points": PackedVector2Array([ Vector2(-half_base, half_height), Vector2(half_base, half_height), Vector2(0.0, -half_height), ]), "fill_color": fill_color, "outline_color": outline_color, "outline_width": outline_width, } # --------------------------------------------------------------------------- # Factory spawner # --------------------------------------------------------------------------- ## Instantiate a PropBlock from a shape payload and material preset, set an ## initial velocity, and add it to `container`. Returns the spawned prop. static func spawn_prop( container: Node, position: Vector2, shape_payload: Dictionary, material_preset: int = PropBlockScript.MaterialPreset.WOOD, initial_velocity: Vector2 = Vector2.ZERO ) -> PropBlockScript: var prop: PropBlockScript = PropBlockScript.new() prop.name = "PropBlock" prop.position = position prop.material_preset = material_preset _apply_shape_payload(prop, shape_payload) container.add_child(prop) if initial_velocity != Vector2.ZERO: prop.linear_velocity = initial_velocity return prop # --------------------------------------------------------------------------- # Internal helpers # --------------------------------------------------------------------------- static func _apply_shape_payload(prop: PropBlockScript, payload: Dictionary) -> void: var shape_type: int = int(payload.get("type", PropBlockScript.ShapeType.POLYGON)) prop.shape_type = shape_type if shape_type == PropBlockScript.ShapeType.CIRCLE: prop.radius = float(payload.get("radius", 24.0)) else: var raw_points: PackedVector2Array = payload.get("points", PackedVector2Array()) prop.polygon_points = TerrainUtilsScript.sanitize_points(raw_points) if payload.has("fill_color"): prop.fill_color = payload["fill_color"] if payload.has("outline_color"): prop.outline_color = payload["outline_color"] if payload.has("outline_width"): prop.outline_width = float(payload["outline_width"])