Initial Void Bastion 0.20 prototype
This commit is contained in:
commit
7362ca0f02
251 files changed
+29688
No files matched your search
@@ -0,0 +1,139 @@
|
||||
@tool
|
||||
extends RefCounted
|
||||
|
||||
# physics.autofit_collision_shape_2d — compute a CollisionShape2D sized to a
|
||||
# sibling/child Sprite2D or AnimatedSprite2D's visual bounds. Common pattern
|
||||
# that's tedious to set up by hand: you have a sprite, you want a rectangle or
|
||||
# circle collider that roughly matches it.
|
||||
#
|
||||
# Params: {
|
||||
# node_path: CollisionShape2D to fit (created if missing when create:true)
|
||||
# source: NodePath to a Sprite2D / AnimatedSprite2D (auto-detected
|
||||
# among siblings if omitted)
|
||||
# shape: "rectangle" | "circle" | "capsule" (default "rectangle")
|
||||
# margin: float pixels to shrink by (default 0)
|
||||
# create: bool — if true and node_path doesn't exist, create it as
|
||||
# a child of the source's parent. Default false.
|
||||
# }
|
||||
static func autofit_collision_shape_2d(params: Dictionary) -> Dictionary:
|
||||
var node_path: String = params.get("node_path", "")
|
||||
var source_path: String = params.get("source", "")
|
||||
var shape_type: String = params.get("shape", "rectangle")
|
||||
var margin: float = float(params.get("margin", 0.0))
|
||||
var create: bool = params.get("create", false)
|
||||
|
||||
if node_path == "":
|
||||
return _err(-32602, "missing 'node_path'")
|
||||
|
||||
var root := EditorInterface.get_edited_scene_root()
|
||||
if root == null:
|
||||
return _err(-32001, "no scene open")
|
||||
|
||||
var target: Node = root if node_path == "." else root.get_node_or_null(node_path)
|
||||
if target == null and not create:
|
||||
return _err(-32001, "node not found: %s (pass create:true to auto-create)" % node_path)
|
||||
|
||||
# Resolve the visual source.
|
||||
var source: Node = null
|
||||
if source_path != "":
|
||||
source = root.get_node_or_null(source_path)
|
||||
if source == null:
|
||||
return _err(-32001, "source not found: %s" % source_path)
|
||||
elif target != null:
|
||||
# Look among siblings for a Sprite2D / AnimatedSprite2D.
|
||||
var parent := target.get_parent()
|
||||
if parent:
|
||||
for sib in parent.get_children():
|
||||
if sib is Sprite2D or sib is AnimatedSprite2D:
|
||||
source = sib
|
||||
break
|
||||
if source == null:
|
||||
return _err(-32001, "no Sprite2D/AnimatedSprite2D source found — pass 'source'")
|
||||
|
||||
# Compute visual bounds (untransformed; CollisionShape2D inherits parent's transform).
|
||||
var bounds := _sprite_bounds(source)
|
||||
if bounds.size.x <= 0 or bounds.size.y <= 0:
|
||||
return _err(-32001, "source has no visible extents (%s)" % source.get_class())
|
||||
|
||||
# Create the CollisionShape2D if needed.
|
||||
if target == null and create:
|
||||
var inst := CollisionShape2D.new()
|
||||
inst.name = node_path.get_file() if "/" in node_path else node_path
|
||||
var parent := source.get_parent()
|
||||
parent.add_child(inst)
|
||||
inst.owner = root
|
||||
target = inst
|
||||
|
||||
if not target is CollisionShape2D:
|
||||
return _err(-32602, "target is not a CollisionShape2D: %s (%s)" % [node_path, target.get_class()])
|
||||
var cs: CollisionShape2D = target
|
||||
|
||||
# Build the requested shape sized to bounds.
|
||||
var shape_res: Shape2D
|
||||
match shape_type:
|
||||
"rectangle":
|
||||
var r := RectangleShape2D.new()
|
||||
r.size = Vector2(
|
||||
max(1.0, bounds.size.x - margin * 2.0),
|
||||
max(1.0, bounds.size.y - margin * 2.0)
|
||||
)
|
||||
shape_res = r
|
||||
"circle":
|
||||
var c := CircleShape2D.new()
|
||||
c.radius = max(1.0, min(bounds.size.x, bounds.size.y) / 2.0 - margin)
|
||||
shape_res = c
|
||||
"capsule":
|
||||
var cap := CapsuleShape2D.new()
|
||||
cap.radius = max(1.0, min(bounds.size.x, bounds.size.y) / 2.0 - margin)
|
||||
cap.height = max(cap.radius * 2.0 + 1.0, bounds.size.y - margin * 2.0)
|
||||
shape_res = cap
|
||||
_:
|
||||
return _err(-32602, "unknown shape '%s' (supported: rectangle, circle, capsule)" % shape_type)
|
||||
|
||||
cs.shape = shape_res
|
||||
# Center the CollisionShape2D on the sprite's center.
|
||||
cs.position = bounds.position + bounds.size / 2.0
|
||||
EditorInterface.mark_scene_as_unsaved()
|
||||
|
||||
return _ok({
|
||||
"node_path": String(root.get_path_to(cs)),
|
||||
"shape": shape_type,
|
||||
"size": [bounds.size.x - margin * 2.0, bounds.size.y - margin * 2.0],
|
||||
"center": [cs.position.x, cs.position.y],
|
||||
"source": String(root.get_path_to(source)),
|
||||
})
|
||||
|
||||
|
||||
static func _sprite_bounds(source: Node) -> Rect2:
|
||||
if source is Sprite2D:
|
||||
var s: Sprite2D = source
|
||||
if s.texture == null:
|
||||
return Rect2(Vector2.ZERO, Vector2.ZERO)
|
||||
var tex_size: Vector2 = s.texture.get_size()
|
||||
if s.region_enabled:
|
||||
tex_size = s.region_rect.size
|
||||
var size: Vector2 = tex_size * Vector2(abs(s.scale.x), abs(s.scale.y))
|
||||
# For centered sprites, bounds are at (-size/2, -size/2).
|
||||
var offset: Vector2 = -size / 2.0 if s.centered else Vector2.ZERO
|
||||
offset += s.offset
|
||||
return Rect2(offset, size)
|
||||
if source is AnimatedSprite2D:
|
||||
var a: AnimatedSprite2D = source
|
||||
if a.sprite_frames == null or a.animation == "":
|
||||
return Rect2(Vector2.ZERO, Vector2.ZERO)
|
||||
var tex := a.sprite_frames.get_frame_texture(a.animation, a.frame)
|
||||
if tex == null:
|
||||
return Rect2(Vector2.ZERO, Vector2.ZERO)
|
||||
var size: Vector2 = tex.get_size() * Vector2(abs(a.scale.x), abs(a.scale.y))
|
||||
var offset: Vector2 = -size / 2.0 if a.centered else Vector2.ZERO
|
||||
offset += a.offset
|
||||
return Rect2(offset, size)
|
||||
return Rect2(Vector2.ZERO, Vector2.ZERO)
|
||||
|
||||
|
||||
static func _ok(data) -> Dictionary:
|
||||
return {"data": data}
|
||||
|
||||
|
||||
static func _err(code: int, msg: String) -> Dictionary:
|
||||
return {"error": {"code": code, "message": msg}}
|
||||
Reference in new issue
Block a user