2

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?

cak3_lover
  • 1,440
  • 5
  • 26
  • Is this about designing the shape or debugging the shape? I guess that, since you are talking about a tool mode, you want a gizmo to create the shape. So the simplest solution would be to create a `CollisionObject2D` in the editor and on initialization take the shape from it. – Theraot Apr 04 '22 at 11:28
  • Otherwise, we are talking about making custom gizmos… Perhaps these are a start: [Drawing in Viewport in Godot: Plugin Tutorial 1](https://www.youtube.com/watch?v=nSqaIY-eJm0), [Intro to plugin Creation in Godot 3 (tutorial by Pigdev)](https://www.youtube.com/watch?v=kSC_m9yy9w0). [Intro to Plugins in Godot - #collab with GDQuest](https://www.youtube.com/watch?v=z_eETiQAChg) - yes, those only make an editable rectangle (and not even a shape, making the shape is the easy part). You can imagine making gizmos for each kind of shape. I cannot put the time to make those for an answer right now. – Theraot Apr 04 '22 at 11:30
  • well I thought of copying the value from a child `CollisionObject2D` but I didn't want to create and delete CollisionObject2D again & again and as per your second comment I'm trying to avoid having to make a plugin (seems overkill), I'm guessing maybe I could use `VisualServer` to draw some shapes? feel free to write an answer whenever / if possible, if not then I'll find a work around (and all the best with what you're doing :)) – cak3_lover Apr 04 '22 at 12:30
  • 1
    If all the shapes are the same, you could reuse it, instead of creating a lot of `CollisionObject2D`. In fact, I remind you that we managed to share a shape between an area and a body. Edit: of course you would not see them in the editor, but for means to create the shape, it is good. – Theraot Apr 04 '22 at 12:57

1 Answers1

4

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)
        )
Theraot
  • 31,890
  • 5
  • 57
  • 86