godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.16k stars 97 forks source link

Add singletons for 2D and 3D debug drawing #5196

Open Calinou opened 2 years ago

Calinou commented 2 years ago

Related to https://github.com/godotengine/godot-proposals/issues/112 and https://github.com/godotengine/godot-proposals/issues/4364. This proposal's text is taken from one of my comments on #112.

Describe the project you are working on

The Godot editor :slightly_smiling_face:

Describe the problem or limitation you are having in your project

While working on game logic, you often need to visualize things such as:

While Godot already has options for visible collision shapes, raycasts and navigation, this doesn't always suffice. These options also tend to lack flexibility in terms of color and style customization, although it's probably better to address this specifically.

Still, for use cases that don't involve physics or navigation, a different solution is needed.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

Add singletons for low-level 2D and 3D drawing (lines, shapes, etc). The goal is to allow drawing anything quickly, from anywhere. There is no requirement to use a dedicated function like _draw(), or having to draw from a script that extends from a specific node type.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

Here are some requirements I can think of. Note that these requirements are geared towards convenience over absolute performance or flexibility:

General requirements

2D debug drawing functions

3D debug drawing functions

If this enhancement will not be used often, can it be worked around with a few lines of script?

No, especially in 3D where _draw() can't be used.

Is there a reason why this should be core and not an add-on in the asset library?

I'd suggest prototyping this with an add-on first, so that you can get an idea for possible shortcomings that may come along with development.

Once this is done (and the add-on proves popular enough in real world usage), we can consider rewriting it in core for better performance and engine integration.

YuriSizov commented 2 years ago

I think there is a benefit in a generic "debug draw" method for various Variant types. This would allow to have a quick and simple way to draw base data, like bounding boxes and vectors, without going too deep into magically being able to debug draw every node in Godot.

The proposed low-level methods are also useful to construct your own complex debug drawing behavior, but won't be as handy in my opinion. We can have both, we should have both. But I think it's important to have a relatively high-level method here. In a complex situation a high-level method can still be used as a building block. E.g. if you want to visualize some position of a node you'd be able to feed it to the method, debug_draw(node.position).

I'm not in favor of a too high-level method, that would work like debug_draw(node). This would be complicated to maintain and won't fit everyone's expectations ever. I think supporting only Variant types is a good middle ground.

Zireael07 commented 2 years ago

For optimal visibility on all kinds of backgrounds, everything gets an automatic outline of shadow or some sort. For geometry, this is done by drawing a second time with a slight offset (relative to width) and a darker color (or brighter if the base color is dark).

<3 <3 <3

Also nitpick: when tinkering with my own workarounds, I found it difficult to specify a 3D line's width, IIRC the point size parameter is ignored on most GPUs... So how do you default to 0.01 unit?

EDIT: There ARE already several addons around doing debug drawing, off the top of my head I know three, but none are complete.

Calinou commented 2 years ago

So how do you default to 0.01 unit?

It would default to 0.01 units in 3D space, which means the line's effective on-screen width would vary depending on distance. It may be possible to cancel this out with a shader, but at the same time, having a variable width depending on distance from the camera may be desired.

SilencedPerson commented 2 years ago

Question, do any Built-in Nodes use _draw() ?

YuriSizov commented 2 years ago

Question, do any Built-in Nodes use _draw() ?

It doesn't exist in engine code. In engine code NOTIFICATION_DRAW is used.

golddotasksquestions commented 2 years ago

I still think this is the right direction, but does not nearly go far enough. There is hardly any difference between this proposal and using _draw() in an Autoload.

Debugging should be a single short line. Let me debug draw objects like this debug_draw(object) or like this Debug.draw(object)!

I have written a long and extensive comment on how that might work in practice here.

Yes, for this to work we have to decide for the user how these debug visualizations look by default (details could be adjusted in a Project Setting). But the benefit of speed and convenience using sensible defaults would outweight customization a million times.

If users don't need convenience but want customization and control over their debug visualization looks, they can already use _draw() in an Autoload with almost exactly the complexity and behaviour as it is proposed here. All this proposal saves them from doing is spending 2 seconds to add a Node to Autoload. All the other boilerplate code (getting points, defining colors, setting sizes), they would still have to write just the same with this proposal.

If we had debug_draw(object) or Debug.draw(object) all this boilerplate code would be gone for the user and they could focus on finding the error rather than some point in some mesh they have still have to write a custom function for so it's a different color than the one from the previous object ... just so they could finally get to visually debugging ...

Zireael07 commented 2 years ago

@golddotasksquestions:

But the benefit of speed and convenience using sensible defaults would outweight customization a million times.

And how do you propose those "sensible defaults" would be arrived at? Unless they're extremely simple things like what visible collision shapes does, there is no way to predict what the user means when he wants debug_draw(object) especially when the object/scene is something more complex. Let's say I call this on my FPS character or my racer car. Do I want the collision? The raycasts? The spherecast? Speed/velocity as numbers? As vectors? The waypoint they're at? The state/mode the AI is in? There's NO way to set "sensible defaults" imho.

they can already use _draw() in an Autoload with almost exactly the complexity and behaviour as it is proposed here

No, they can't (at least NOT in 3D). In 2D, yes, this proposal is essentially what you say, but for 3D there are NO functions to draw even a straight line, let alone a sphere or a ray (arrow)

golddotasksquestions commented 2 years ago

@Zireael07

And how do you propose those "sensible defaults" would be arrived at?

Exactly how we arrive at any other decision: Someone makes a proposal, other users comment and discuss their concerns or express their support.

Unless they're extremely simple things like what visible collision shapes does, there is no way to predict what the user means when he wants debug_draw(object)

If you seen my comment on how I would propose this solution to work, and following comments discussing it, you might have also seen me saying: Exactly like print(variant), debug_draw(variant) or Debug.draw(variant) would work by making general assumptions, which are suppose to fill common, generally appropriate needs(some of which I illustrated in my comment), and not trying to predicting every possible user debugging desire, trying to fill all needs of all users.

You can make the same argument about Scene Unique Names or many other Godot features. Often they are not for everyone in all situations, but for most cases for most people they make things easier and more convenient and therefore improve the overall efficiency to use Godot.

If this debug_draw(variant) or Debug.draw(variant) method would cover only 80% of your daily most common visual debugging needs

(like: visualizing vectors with arrow heads to indicate direction, AABB with origin, Rect2 and origin of Control nodes, Collision shapes and origins of bodies and areas, print Strings and Number to a screen overlay, accept arrays of variants to allow to debug draw groups and children ... these are just the examples I reflecting my most common needs needs, I would have hoped others would comment their typical most common needs),

then most people in most cases would have their needs met and we would only rarely have to write lot's of boilerplate code to do our visual debugging.

I don't know about you, but I'm often too lazy to write all this custom code and push it out for later "when I really need it", just because it's so much setup just to make a damn vector conveniently show up on screen. And therefore often go without have any visual debugging for way too long.

especially when the object/scene is something more complex.

You have to think of it like just like print(scene) vs print(node) vs print(value). All of this works. It does not matter how complex the thing is you are trying to print. I'm saying we need the same, just visually.

Let's say I call this on my FPS character or my racer car. Do I want the collision? The raycasts? The spherecast? Speed/velocity as numbers? As vectors? The waypoint they're at? The state/mode the AI is in? There's NO way to set "sensible defaults" imho.

When you call print(MyFPSCharacterScene), you would not expect it to magically guess what you care about either, would you? No, the print method has something I call "sensible defaults": It assumes what is relevant information and that's it, that's what you get. That what you always get. It will print the scene name, the node type and it's unique number. That's it. No need to guess what the user wants, because this assumption is made in the implementation (maybe some of it could be slightly customizable/configurable in the Project Settings, idk).

The key point is that you can write one line like

Debug.draw(MyFPSCharacterScene.velocity)

and it would automatically 2D overlay draw on screen an origin point, a line, an arrow head, maybe be name of the property and it's owner name as well plus give it all a unique color and outline.

Compare this with the solution in this proposal:

var random_color = Color(rand_rage(0,255), rand_rage(0,255), rand_rage(0,255), 1.0)
var velocity_start_point = cam.unproject_position(MyFPSCharacterScene.global_transform.origin)
var velocity_end_point = cam.unproject_position(MyFPSCharacterScene.global_transform.origin + MyFPSCharacterScene.velocity)
var arrow_head size = 5.0
var arrow_start_point = velocity_end_point + (velocity_end_point.direction_to(velocity_start_point ) * arrow_head_size)
DebugDraw.line(velocity_start_point, velocity_end_point, 0, random_color, 2)
DebugDraw.circle(4, 0, -1, 0, random_color, true)
DebugDraw.arrow(arrow_start_point, velocity_end_point, 0, random_color, 2)

That's just one property. If you have multiple, you would have to write your own custom function with the current proposal. This is going to be way more verbose still. The workflow and effort for the user is almost identical to using _draw() in an Autoload.

In my proposed approach, you could simply use an array as argument, carrying any property or object you might want to debug draw:

var debug_draw_array = [fps_char.velocity, fps_char.shape, fps_char.state, WayPointManager.current_waypoint]
Debug.draw(debug_draw_array)

Or easily and simply draw everything you marked in your scene for debug drawing:

var fps_char_debug_nodes = get_tree().get_nodes_in_group("fps_char_debug_draw")
Debug.draw(fps_char_debug_nodes)

If that's not enough, write custom debrug draw code either using _draw() or Calinous proposed methods.

YuriSizov commented 2 years ago

Print outputting the name of an object and its sequential ID is not in the same league as guessing what is relevant for individual nodes or groups of nodes in visual debugging. Most of the time printing a complex object is useless, and you need to print its properties instead. Which is @Zireael07's point. The user needs to decide what to print or debug draw, because the user knows what is relevant in a complex object.

In the parity sense, the result of printing a complex object is equal to adding a 2d or 3d label at the position of that object with its name and ID in visual debug, nothing more.

golddotasksquestions commented 2 years ago

Most of the time printing a complex object is useless, and you need to print its properties instead.

Yes exactly. So why are we talking about it? Just like print($MyConplexObject) most of the time is useless, so would be Debug.draw($MyConpexObject) be useless most of the time. Just like print($MyComplexObject.property) is more useful, Debug.draw($MyComplexObject.property) would be more useful.

Sometimes printing an object is very useful though. For example print(KinematicCollision) or Debug.draw(Control)

If you are trying to print a complex scene, what is actually printed is the root node. The DebugDraw singleton could do the same.

Also this feature could start small and get expanded over time if there is demand. Anything that allows faster visual debugging with less coding or thinking is an improvement imho.

YuriSizov commented 2 years ago

I guess this plugin should be mentioned as one point of reference: https://github.com/DmitriySalnikov/godot_debug_draw_3d

Zireael07 commented 2 years ago

Wasn't aware of this one, thanks for sharing - it'll be extremely useful until this proposal is realized

Sulter commented 2 years ago

Just wanted to add that I completely agree with what @golddotasksquestions proposes. It feels much more natural to have debug_draw's for all built-in types, instead of just simple primitive types, which would just add a tiny bit of syntax sugar on top of the current _draw(). Have there been any counter-arguments that haven't been refuted yet, or is it just a question of drawing up a new proposal?

visuallization commented 1 year ago

Oh, I would very much appreciate debug.draw capability in godot for 3D!

Calinou commented 1 year ago

@visuallization Please don't bump issues without contributing significant new information. Use the :+1: reaction button on the first post instead.

Calinou commented 1 year ago

I still think this is the right direction, but does not nearly go far enough. There is hardly any difference between this proposal and using _draw() in an Autoload.

Debugging should be a single short line. Let me debug draw objects like this debug_draw(object) or like this Debug.draw(object)!

See also https://github.com/godotengine/godot-proposals/issues/5533#issuecomment-1465115868, which is an approach I tend to like more for this kind of stuff.

Zireael07 commented 1 year ago

Whichever method/approach is chosen, something needs to be done sooner rather than later, as it's one of the biggest warts Godot has compared to competitors such as Unity

nkrisc commented 1 year ago

I like the idea of Debug.draw(object), but in addition it would also be nice if there was a related _draw_debug virtual method defined on Object that would be called by Debug.draw on the object, if overridden, so that if you do have some complex custom object you can define how you'd like it drawn. This way if you wanted to use data from the instance when drawing the debug shapes, you can define that in the class and not muddy up your debug calls elsewhere.

EikoBiko commented 1 year ago

New user of Godot, moving from Unity I'm in total agreeance that this should probably be a priority in some sense. My opinion is that a simple and flexible solution now that can be extended is preferable to one that comes with all kinds of bells and whistles later. Especially considering that the first instance of this conversation was issue #112 , which as of this month, will have been 4 whole years ago.

This feature will be used constantly and was the very first thing I looked for when coming to Godot. Could all of this circular discussion be avoided with an implemented solution to just draw a line on screen using an expression, graphing calculator style? Extremely flexible and straightforward. Bundle a few basic functions that draws circles and lines, then people can combine those to make more complicated shapes. Even if a graphing calculator type solution isn't feasible, even just a simple draw line solution would allow people to at least hit the ground running with rudimentary shapes for debugging.

golddotasksquestions commented 1 year ago

@EikoBiko

Could all of this circular discussion be avoided with an implemented solution to just draw a line on screen using an expression, graphing calculator style? Extremely flexible and straightforward. Bundle a few basic functions that draws circles and lines, then people can combine those to make more complicated shapes.

This already exists. It's called "Custom drawing" in Godot: https://docs.godotengine.org/en/stable/tutorials/2d/custom_drawing_in_2d.html See all the "draw..." methods in the CanvasItem class: https://docs.godotengine.org/en/stable/classes/class_canvasitem.html

There are also free plugins available if you want something right now.

Imo the goal of this proposal should be to make a built-in version of debug draw significantly more user friendly than custom drawing or any of those plugins.

Calinou commented 1 year ago

This already exists. It's called "Custom drawing" in Godot:

This is only available in 2D, not 3D.

golddotasksquestions commented 1 year ago

The only difference for 3D is looping through any 3D points and unproject their position with a single method call. Given how verbose custom drawing already is, these few extra lines are really neglectable.

PrecisionRender commented 1 year ago

I'm interested in implementing this functionality, however, there still seems to be no clear decision regarding the desired implementation.

Personally, I don't get what the big deal is regarding boilerplate code with regards to debug shapes. In no other engine can you feed in a raw GameObject, for example, into a debug draw function and expect it to spit out useful information, if it even works at all. In most other engines, you can only draw primitive types such as lines, text, AABBs, etc. I've never had any issues with this approach.

Frankly, if one is too lazy to write 3 extra lines of code to debug their game... I'm not sure game development is for them. In the grand scheme of things, 3 lines of debug code is such a drop in the bucket and would be miles better than what we have now.

It's my personal opinion that we should implement this in the way @Calinou originally suggested. Most if not all debug behavior present in other engines will be added as a result. Anything higher level like what @golddotasksquestions suggests should be delegated to an addon.

Speaking from the perspective of a user, one-size-fits-all solutions will only cause confusion and frustration. If I was to type Debug.draw(MyFPSCharacterScene.velocity), how would I know what to expect it will do? Even if I know what it will do, what if I want the arrow to be a slightly different color or size than the default? Maybe I want some extra lines representing desired velocity, etc. If I understand this suggested approach correctly, even with this "intelligent" solution, boilerplate code is still required in almost all non-10-minute-prototype use cases. Debug drawing is a highly specialized portion of debugging that should be left up to the user to decide the best way to display on screen.

Therefore, I think implementing drawing Varaints is a bad idea. Why not just use DebugDraw2D.draw_string(Varaint.to_string())?

Finally, take this post as me sharing my opinion. I have zero emotional involvement in this. Feel free to pick apart my arguments if you feel they're inadequate. However, if anyone expects this to be implemented soon, a consensus for the intended implementation needs to be reached.

Calinou commented 1 year ago

The only difference for 3D is looping through any 3D points and unproject their position with a single method call. Given how verbose custom drawing already is, these few extra lines are really neglectable.

If you do it this way, custom drawing won't be depth-aware. You won't be able to draw behind solid or transparent geometry. This is suitable for some use cases, but not all the use cases you'd want to use 3D debug drawing for.

golddotasksquestions commented 1 year ago

The point of debugging info is to be able to see and read the debugging info. 2D is always rendered on top of 3D for a good reason. In what scenario is it useful to have the debug info hidden behind geometry?

Personally I can't think of any. If however someone does, and they really want some debug graphic/info hidden in 3D space, there is still the option to use immediate geometry for 3D lines, Label3D and primitive shapes.

PrecisionRender commented 1 year ago

The point of debugging info is to be able to see and read the debugging info. 2D is always rendered on top of 3D for a good reason. In what scenario is it useful to have the debug info hidden behind geometry?

Personally I can't think of any. If however someone does, and they really want some debug graphic/info hidden in 3D space, there is still the option to use immediate geometry for 3D lines, Label3D and primitive shapes.

This makes zero sense. The 3D debug drawing/gizmos should work the same way as the collision debug gizmos. This is how it works in every other 3D engine I've used. If you want something drawn on top of the screen, use 2D gizmos. Otherwise, use 3D. (Or use on_top, but the existence of this variable indicates that it's not the only way to render 3D debug gizmos)

If the only counter-arguement in favor of the one-size-fits-all solution is that it saves a few lines of code here and there and a dislike on my post, I'm going to start experimenting with an implementation for the functionality in the original post.

aXu-AP commented 1 year ago

I think string drawing function should also have a version, which doesn't take position as a parameter, but prints the text starting from upper-left (or right if rtl) corner line by line downwards. This, like the other debug draw functions, should also support duration. Duration = -1 (process) would be good for monitoring real-time values (eg. fps counter and nodes' properties). Longer duration texts would work more like console printing.

If I understand it right, the order of nodes running _process() is always the same so text would stay in place from frame to frame as long as tree doesn't change (for _physics_process the order might be indeterministic if multi-threading is turned on and generally would mix badly with process timed prints).

This is IMHO needed, because for moving objects, it's hard to read debug text if it moves with the object/camera.

PrecisionRender commented 1 year ago

I think string drawing function should also have a version, which doesn't take position as a parameter, but prints the text starting from upper-left (or right if rtl) corner line by line downwards. This, like the other debug draw functions, should also support duration. Duration = -1 (process) would be good for monitoring real-time values (eg. fps counter and nodes' properties). Longer duration texts would work more like console printing.

If I understand it right, the order of nodes running _process() is always the same so text would stay in place from frame to frame as long as tree doesn't change (for _physics_process the order might be indeterministic if multi-threading is turned on and generally would mix badly with process timed prints).

This is IMHO needed, because for moving objects, it's hard to read debug text if it moves with the object/camera.

I support this, however it's decided to be implemented. Unreal does this in a similar way.

image

Or we could turn this into it's own interface, as seen in https://github.com/godotengine/godot-proposals/issues/7091#issuecomment-1718159842

aXu-AP commented 1 year ago

I don't know how it works in Unreal, but traditional style console interface doesn't allow for updating info realtime, doesn't it? What I'm suggesting is that you could set text to disappear on the next frame (to reprint it with new info).

Calinou commented 1 year ago

, but traditional style console interface doesn't allow for updating info realtime, doesn't it? What I'm suggesting is that you could set text to disappear on the next frame (to reprint it with new info).

If you set the console length to the number of lines you're printing every frame, you can effectively have text at a fixed position that updates in real-time. :stuck_out_tongue:

TrickyFatCat commented 12 months ago

Here is an addon for Godot 4, which has a decent implementation of such debug draw and it would be great to have it in the Godot by default.

Implementing a debug draw will defiantly make Godot a better engine for many developers, as debug functionality is a vital tool while working on games.

Made by @DmitriySalnikov

SirPigeonz commented 12 months ago

https://github.com/godotengine/godot-proposals/issues/6857 and https://github.com/godotengine/godot-proposals/issues/2072 are related to this proposal I think, because they could be solved by this one?

Basically makes debug draws more controllable by users I guess.

TrickyFatCat commented 12 months ago

@SirPigeonz not only controllable but easy to use without spending much time creating a basic setup. For example, using a previously mentioned plugin I can easily visualize and debug the path between given points.

@tool
extends Node

@export var target_points : Array[Node3D] = []

func _process(delta: float) -> void:
    if target_points.is_empty():
        return

    var path : PackedVector3Array = []

    for point in target_points:
        if !point:
            continue

        path.append(point.position)
        DebugDraw3D.draw_arrow_path(path, Color.RED, 0.25, true, delta)

In this case, I spent more time thinking about what I want to debug, rather than creating a setup for this.

eobet commented 3 months ago

Moving to Godot from Unreal I was very surprised that I couldn't find built in debug drawing.

In Unreal, any raycast has debug visualization, and built-in you get visualization on the line trace and the hit location.

And I haven't used Unity for a decade, but I seem to remember being able to just add a checkbox in the inspector for a curve to get it debug drawn, but not in a build...

Calinou commented 3 months ago

And I haven't used Unity for a decade, but I seem to remember being able to just add a checkbox in the inspector for a curve to get it debug drawn, but not in a build...

Visible Path nodes are available in both 2D and 3D since Godot 4.2 (enable Debug > Visible Paths in the editor then run the project). Per-Path color customization isn't available yet, but there's a PR for it in 3D: https://github.com/godotengine/godot/pull/82321

cmann1 commented 2 months ago

A small addition is that I think there should be an option to allow these to not be stripped from release builds. There could be situations where you may want to still be able to create debug visuals for users, e.g. modding or level editing.