I'm adding a collision shape using Physics2DServer
like this:
Physics2DServer.body_add_shape(_body, _shape, Transform2D.IDENTITY, collision_disabled)
based on this solution
but I can't see what the shape visually is,
so is there a way to draw (and move it according to the body) in tool mode?
This took a litte figuring out, but it is actually simpler than I expected. The first insight is that Shape2D
already has a draw
method, so we can do something like this:
Godot 3
shape.draw(get_canvas_item(), Color.darkblue)
Godot 4
shape.draw(get_canvas_item(), Color.DARK_BLUE)
Or using whatever color you prefer.
Meaning that we can make a generic solution, instead of dealing with every kind of shape.
We can take advantage of _draw
for that. And to invalidate it, we can call update
(Godot 3) or queue_redraw
(Godot 4) every time it changes.
By the way, Resource
has a "changed" signal that should be emitted by all build-in resources (you have to do it manually for custom ones) when their state changes (if you find one that does not do this, please report it).
So we have this:
Godot 3
tool
extends Node2D
export var shape:Shape2D setget set_shape
func _draw() -> void:
if not Engine.editor_hint:
return
if shape == null:
return
shape.draw(get_canvas_item(), Color.darkblue)
func set_shape(new_value:Shape2D) -> void:
if shape == new_value:
return
if shape != null and shape.is_connected("changed", self, "update"):
shape.disconnect("changed", self, "update")
shape = new_value
if shape != null and not shape.is_connected("changed", self, "update"):
shape.connect("changed", self, "update")
update()
Godot 4
@tool
extends Node2D
@export var shape:Shape2D:
set(new_value):
if shape == new_value:
return
if shape != null and shape.changed.is_connected(queue_redraw):
shape.changed.disconnect(queue_redraw)
shape = new_value
if shape != null and not shape.changed.is_connected(queue_redraw):
shape.changed.connect(queue_redraw)
queue_redraw()
func _draw() -> void:
if not Engine.is_editor_hint():
return
if shape == null:
return
shape.draw(get_canvas_item(), Color.DARK_BLUE)
The drawback is that we cannot configure it draw the Shape2D
at a custom position (it will be draw at the origin on the local coordinates of the Node2D
). And, no, we cannot cheat on that.
However, we can create a canvas item via the VisualServer
(Godot 3) or RenderingServer
(Godot 4) to position it. This setup might look familiar:
Godot 3
var canvas_item:RID
var invalid_rid:RID
func _enter_tree() -> void:
canvas_item = VisualServer.canvas_item_create()
VisualServer.canvas_item_set_parent(canvas_item, get_canvas_item())
func _exit_tree() -> void:
VisualServer.canvas_item_clear(canvas_item)
canvas_item = invalid_rid
func _draw() -> void:
if not Engine.editor_hint:
return
if shape == null:
return
shape.draw(canvas_item, Color.darkblue)
Godot 4
var canvas_item:RID
func _enter_tree() -> void:
canvas_item = RenderingServer.canvas_item_create()
RenderingServer.canvas_item_set_parent(canvas_item, get_canvas_item())
func _exit_tree() -> void:
RenderingServer.canvas_item_clear(canvas_item)
canvas_item = RID()
func _draw() -> void:
if not Engine.is_editor_hint():
return
if shape == null:
return
shape.draw(canvas_item, Color.DARK_BLUE)
And to move it, we can set its transform, like this:
Godot 3
export var offset:Vector2 setget set_offset
func set_offset(new_value:Vector2) -> void:
if offset == new_value:
return
offset = new_value
VisualServer.canvas_item_set_transform(
canvas_item,
Transform2D.IDENTITY.translated(offset)
)
Godot 4
@export var offset:Vector2:
set(new_value):
if offset == new_value:
return
offset = new_value
RenderingServer.canvas_item_set_transform(
canvas_item,
Transform2D.IDENTITY.translated(offset)
)