- Created ROADMAP.md outlining core features, implementation phases, and detailed breakdown for the Stickman Sandbox Builder project. - Introduced plans for dynamic vector props with `PropBlock` specification, including reusable components and utility functions for prop creation. - Developed a vector terrain system plan detailing the reusable `TerrainBlock` component and associated utility functions for geometry handling. - Implemented `PhysicsTestHarness` scene for testing physics interactions with dynamic props and terrain. - Added scripts for `PropBlock`, `PropUtils`, `TerrainBlock`, and `TerrainUtils` to support dynamic prop creation and terrain management.
160 lines
5.4 KiB
GDScript
160 lines
5.4 KiB
GDScript
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"])
|