godotengine / godot-proposals

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

Add `GridRect` node for displaying a two-dimensional grid (for editors/charts) #2662

Open Xrayez opened 3 years ago

Xrayez commented 3 years ago

Describe the project you are working on

Describe the problem or limitation you are having in your project

This mostly applies to editor plugins development, where you need to draw a grid for snapping/aligning purposes. Drawing a grid boils down to rendering a bunch of intercrossing lines, and this is more or less straightforward process, but may be more complex to implement depending on the feature set.

But the complexity is not really a problem, the problem is that Godot may lack a very needed feature. The problem is that I don't see anyone mentioning this, so I'd like to understand whether we do have a problem which needs to be solved (more like #2532).

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

I propose to implement a new type of Control node with the sole purpose of rendering a infinite grid in 2D. It would be up to the user to code the scrolling behavior if needed.

A grid will have:

This feature wouldn't be limited to editor development. See also plugins like https://github.com/fenix-hub/godot-engine.easy-charts which do allow to make charts for games such as City Game Studio currently developed in Godot. In-game minimaps could also benefit from basic grid subdivisions representing chunks of the game world etc.

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

image

image

In theory, the grid's background and outline colors could be styled via Theme resource.

Usage examples

image

image

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

Sure, it's totally possible.

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

This probably shouldn't be part of core, but I'd like to understand whether community would find this kind of feature useful in their real-life projects.

Yet Godot does display grids in various editors already, like AnimationTree, VisualScript, VisualShader etc, so this alone signifies that a feature like this should be very useful for plugin creators (say FiniteStateMachine/BehaviorTree plugins which are unlikely to be implemented in core, according to previous discussions).

The node name is up to discussion, of course. I picked Grid because that's basically what it would represent (or Grid2D may be just fine, but it's unlikely that the 3D part is going to have such a node, it's mostly about GUI). I did implement other class with the same Grid2D name in the past (now renamed to VariantMap) which works as a general-purpose 2D data container (which is different from Array2D), so this is also a minor reason why I'd like to discuss this.

YuriSizov commented 3 years ago

Hmm, this seems like a trivial thing to do. It would make sense to make it a part of a core if there are some non-obvious optimizations that can be done to render it more efficiently. Otherwise, it's not complex to make this with a regular control and _draw().

Also, I've actually done grids several times at this point and there have always been details that made "just grids" impractical, always requiring a specialized solution. So I'm not sure how scalable it would be.

Xrayez commented 3 years ago

Also, I've actually done grids several times at this point and there have always been details that made "just grids" impractical, always requiring a specialized solution. So I'm not sure how scalable it would be.

Can you give a concrete example? I fail to understand how and why you'd need a more specialized grid. I mean, sure, there are always specific use cases, but it's not the task to cater to specific use cases here. We're mostly talking about developing games and tools for them.

It's similar to having existing ColorRect node in core: should it have an outline, or filled with a texture modulated by specified color, or should it have rounded corners? None of these features are part of ColorRect node. Some of these properties may not even make sense in ColorRect node, that's why you shouldn't expect it to do more than it supposed to do out of the box. Same for suggested Grid node here.

Godot is seen useful for prototyping purposes by many developers as well, so I'd expect this feature to be useful for those use cases as well.

Also, please, lets make the discussion more on topic and suggest solutions to make the node useful for at least 70% of use cases. As I said, I'm not even proposing to have this in core. At most, this could be a first-party plugin. At least, we can have a productive discussion to make a third-party plugin out of this proposal.

YuriSizov commented 3 years ago

It's similar to having existing ColorRect node in core: should it have an outline, or filled with a texture modulated by specified color, or should it have rounded corners? None of these features are part of ColorRect node. Some of these properties may not even make sense in ColorRect node, that's why you shouldn't expect it to do more than it supposed to do out of the box.

Yeah, which is why there is almost no reason to use it, except to give some background to something in a lazy way. And a grid is even more particular.

How many projects require a grid for prototyping? I even feel like a generalized solution will be good only for prototyping, unlike the ColorRect you've mentioned which has its limited use in finished projects. You ask for concrete examples, but the problem is that it's very game specific. It's rarely that it's just a grid you want. You may want to work with one of the two coordinate systems (cross point or a center of a cell). You may want to skip some lines, to draw background behind some cells. Arbitrary thickness may be required for specific lines, and also non-square grids are useful. You never want just grids. You want to do something with them. Even your proposed "limitlessness" is an arbitrary requirement.

If we stop at just drawing the lines, then I see no point in it. Not for the majority, not for the 70% or 50% of Godot users. Unless, again, there is some way to optimize it in a way that is not accessible to an average user. Then, maybe, there is a reason to add it.

Also, please, lets make the discussion more on topic and suggest solutions to make the node useful for at least 70% of use cases. As I said, I'm not even proposing to have this in core. At most, this could be a first-party plugin. At least, we can have a productive discussion to make a third-party plugin out of this proposal.

Well, as you are aware discussing if it's worth adding this to the core is the purpose of this repository, so it's as on-topic as it gets.

Xrayez commented 3 years ago

@pycbouh you completely miss the point here. You talk about use cases which are specific to games in general. It's not the main goal of this proposal. The goal of this proposal is to help plugin creators to create tools for making games. If such a node is useful for in-game needs, it's only a nice byproduct.

YuriSizov commented 3 years ago

What I've said is valid for tools too. You seem to want it for a very specific case of infinite grids like in some of our editor tools. In that case, we can probably make it an editor control by refactoring existing tools and exposing it to the plugin API. But even then, drawing such simple grid is maybe too trivial to make it a control instead of just directly drawing it.

Xrayez commented 3 years ago

I understand that you'd like to continue the discussion, but unfortunately your arguments are not convincing to me, no matter how harsh that could sound, so I suggest ending our discussion.

Lets keep this open for others who may be actually interested in this discussion.

Zireael07 commented 3 years ago

If a grid was just a grid, then yes, it can be done (fairly) easily with draw() but I imagine most use cases will want at least the red/green axis lines and origin point just like in OP's last screenshot - and that is more complex to make sure it all lines up.

I can tell you I would definitely find a use for this grid in two out of three projects I have (a 3D racer and a 2D space game)

BTW "too trivial" argument strikes me as funny when we have Panel and ColorRect controls that are, well, colored rectangles so even more trivial

Jummit commented 3 years ago

This actually sounds like a good plugin idea.

BTW "too trivial" argument strikes me as funny when we have Panel and ColorRect controls that are, well, colored rectangles so even more trivial

Colored rectangles are very common, and panels are just a Control node with a StyleBox, which is even more common. Grids on the other hand are not as common.

LikeLakers2 commented 3 years ago

We already have grid-drawing code within GraphEdit, so I don't think it would be too unreasonable to suggest that we might extract that code into its own separate node.

BTW "too trivial" argument strikes me as funny when we have Panel and ColorRect controls that are, well, colored rectangles so even more trivial

Colored rectangles are very common, and panels are just a Control node with a StyleBox, which is even more common. Grids on the other hand are not as common.

I think you're misunderstanding Zireael here. They're not arguing whether Panel or ColorRect are commonly used. Rather, they're saying that making a Panel-like or ColorRect-like control from scratch would arguably be just as easy as making a Grid control from scratch. Therefore, they're saying that arguing a node is "trivial to make" as a reason for not including it makes no sense, when considering the engine already contains "trivial to make" nodes.

YuriSizov commented 3 years ago

I've already addressed why ColorRect may be an exception from the "too trivial" argument in the same comments I've made the "too trivial" argument, and even then I agreed that even ColorRects are too trivial, but are just a little bit more widespread. That's what Jummit is talking about.

As for extracting code from other controls, as I've brought up before we can make an editor only control for plugins based on what we have for other controls (and it's not just GraphEdit btw), but the task of drawing grids may still be too trivial, and would complicate built-in controls for no reason with such refactoring.

Xrayez commented 3 years ago

I'd like to point out that refactoring did occur for editor stuff in the past, like with polygon editing: godotengine/godot#11019, and I have a similar proposal for other stuff: #1157.

So, if we can do this in a way which can cover all those editor use cases in Godot in a general-purpose way, then I think it's worth it. We can also save a bunch of kilobytes from the Godot editor executables by eliminating duplicate code.

But if we also can make it as a node accessible to users outside the editor, then that would also be nice in my opinion. The usefulness of the node would only be limited by your own imagination. 😏

People do report that they lack basic editor controls to be available via script in general: #300.

LikeLakers2 commented 3 years ago

I've already addressed why ColorRect may be an exception from the "too trivial" argument in the same comments I've made the "too trivial" argument, and even then I agreed that even ColorRects are too trivial, but are just a little bit more widespread. That's what Jummit is talking about.

I actually don't see how you address it and exempt it from the "too trivial" argument. I mean, there's this line:

Yeah, which is why there is almost no reason to use it, except to give some background to something in a lazy way. And a grid is even more particular.

which seems close enough? But it hardly explains why ColorRect is an exception, only that it somehow is. Unless you're considering it an exception because it's a little more widespread already -- in which case, I would say that any node within the engine is already going to be widespread, by nature of being in the engine.


While I'm here, and have read your previous comments, I want to comment on this part:

How many projects require a grid for prototyping? I even feel like a generalized solution will be good only for prototyping, unlike the ColorRect you've mentioned which has its limited use in finished projects. You ask for concrete examples, but the problem is that it's very game specific. It's rarely that it's just a grid you want. You may want to work with one of the two coordinate systems (cross point or a center of a cell). You may want to skip some lines, to draw background behind some cells. Arbitrary thickness may be required for specific lines, and also non-square grids are useful. You never want just grids. You want to do something with them. Even your proposed "limitlessness" is an arbitrary requirement.

You seem to be over-complicating this whole grid-drawing control. One of Xrayez's comments sums up how I want to respond to this:

I fail to understand how and why you'd need a more specialized grid. I mean, sure, there are always specific use cases, but it's not the task to cater to specific use cases here. We're mostly talking about developing games and tools for them.

But if you still feel concerned about those use cases, please take some time to drum up a solution. Complaining about those issues will get us nowhere, whereas making suggestions for how to improve this proposal will inch us forward to a better result.

YuriSizov commented 3 years ago

I actually don't see how you address it and exempt it from the "too trivial" argument. I mean, there's this line <...> which seems close enough? But it hardly explains why ColorRect is an exception, only that it somehow is. Unless you're considering it an exception because it's a little more widespread already -- in which case, I would say that any node within the engine is already going to be widespread, by nature of being in the engine.

I may not have been very clear, but it's the line you've quoted and the continuation here:

I even feel like a generalized solution will be good only for prototyping, unlike the ColorRect you've mentioned which has its limited use in finished projects.

ColorRects have some general purpose use as a lazy way to add a background to something. Same with Panel control btw. This is a more common problem (hence, widespreadness) than a grid. Oh, and ColorRect is a good place to start with a screenspace shader, especially for transitions. Of course there are other ways to achieve this, but you can't deny all of this is way more prevalent in gamedev than simple infinite grids.

Like I've said, when you actually want grid in games, you likely want way more customization than we can fit in a generalized solution.

You seem to be over-complicating this whole grid-drawing control.

Like I've said, I'm talking from experience of implementing various grid structures while making actual games. These were actual work requirements for tasks and issues at hand. These are not hypotheticals that I imagine on the spot.

And I'm not "complaining". I'm saying that there is very limited usefulness to the proposed addition, and actual tasks require way more customization than it's reasonable to add to a core component. I'm saying that we don't need this in core and developers are capable of implementing this themselves within the spec they have. I don't understand why you want me to improve this proposal when I say it's useless as a part of the Engine.

Zireael07 commented 3 years ago

A grid can also serve as a background, especially for graphs (cf. Thrive, which seems to use a LOT of graphs)

LikeLakers2 commented 3 years ago

I don't understand why you want me to improve this proposal when I say it's useless as a part of the Engine.

I want you to improve this proposal, because you say this node is useless.

You've been very clear that you think it's useless because it doesn't support your game-specific use cases. We all agree: It can't do every single thing that you might want. But at the same time, it's not meant to be a one-size-fits-all solution. Godot's built-in nodes are meant to provide foundations with some commonly-used settings, to make it easier to implement your own use case with an attached script.

Which is why I'm suggesting you take some time to drum up some solutions, even if such a solution might require an attached script. Your solutions don't have to be perfect, or even fit all of the use cases that you want. It just needs to get a conversation going so we know how we might want to fix the issue.


P.S. I took some time to drum up my own solution to your problem, because I do think your problem is one worth solving:

I propose _draw_line(). This is a virtual function on Grid that will be called during _draw(), and is called once per line. It will provide parameters related to the line being drawn, such as start and end points.

When overriding this, the user is expected to return a bool, representing whether Grid should draw the line or not. If false, the node assumes that the user has drawn the line themselves (even if no draw call actually took place), and avoids drawing that line again.

As this is called from _draw(), any draw_*() functions called will be cached. Additionally, if the user exports their own variables meant to change how certain lines are handled within _draw_line(), they are expected to call update() whenever these variables change so that their settings will take effect.

Here is an example, where the user makes the lines draw at double the normal width:

func _draw_line(from: Vector2, to: Vector2) -> bool:
  draw_line(from, to, self.grid_color, 2.0)
  return false

It's not a perfect solution, but I'm not looking to start off with one. In fact, I bring up my proposed solution in hopes of starting a conversation on how we can improve and iterate upon my idea, if people feel my idea is promising. Hopefully my idea can lead to this Grid node coming out of the oven in an even more delicious state.

Xrayez commented 3 years ago
func _draw_line(from: Vector2, to: Vector2) -> bool:
  draw_line(from, to, self.grid_color, 2.0)
  return false

I think being able to override drawing per call is a nice idea. Perhaps the callback can also provide additional contextual state information to determine things such as whether a line is odd or even etc.

By the way, I've been looking at the source code and stumbled upon this method:

https://github.com/godotengine/godot/blob/0e93a1df793de92f4ec6f4bb097cfd5aa94a7157/editor/plugins/canvas_item_editor_plugin.cpp#L3046-L3116

One can clearly see the amount of code needed to make it work in a general-purpose way, so this definitely requires some design work before programming such a thing yourself! I think we could take this as a base implementation and see how it goes. The viewport node is basically the Grid node here.

There are probably two largely approaches:

Both methods could be implemented and the type could be switched if those approaches do significantly differ. The per-cell approach may be more universal in comparison.

YuriSizov commented 3 years ago

I took some time to drum up my own solution to your problem, because I do think your problem is one worth solving

I don't have a problem, and it's not up to me to improve this proposal, because it's not my proposal and I'm not in favor of it at all. But your idea is a nice addition to it nonetheless. Though I expect that this will ruin any chance for drawing optimizations, therefore making the whole idea lacking the point of implementing it in the first place.

At any rate, if you really want this proposal to be improved, this all can already be implemented as an addon to prove the concept and to polish the API.

LikeLakers2 commented 3 years ago

@pycbouh Then I would suggest spending your time on other proposals, specifically ones that you think are worthwhile. You've already made it clear (multiple times, might I add) that you're not in favor, so I don't understand why you feel the need to continue commenting here.

YuriSizov commented 3 years ago

@LikeLakers2 I only reply to the points made against my arguments and direct responses to me, like yours. Otherwise, indeed, I have nothing else to add here at this point. Please keep your suggestions to yourself, this is offtopic.

akien-mga commented 3 years ago

From reading the discussion, there seems to be a need to clarify: the proposals workflow is first and foremost to discuss additions to the core. @Xrayez's OP actually suggests that this would probably be best-suited as a plugin, and then there seems to be misunderstanding of @pycbouh's comments with regard to implementing this in core (which is what a proposal is intended for).

So here's my take on this:

Now, I'm fine with having this proposal used for brainstorming about what the API could be for such a plugin-implemented Node. I think if we all agree that doing this as a plugin is the best course, we can drop the irrelevant meta-discussion about other "simple" nodes in core and whether they should be in core or not. And most of pycbouh's critical feedback on the proposal is no longer a direct concern if this is done as a plugin - it's still useful to drive design decisions, but plugin implementers are welcome to implement what they want and it's users or lack thereof who will help ascertain whether the feature and its design are useful.

Implementing features that could be core as a plugin first to experiment with the API and use cases is always a good idea, as long as it can be done without too much trouble. I've done this with godot-logger, and I think there's a use case for making such logging feature part of the core, yet I'm still not happy with the API we came up with in this plugin and a core implementation would likely be another iteration, learning from the mistakes of the first one.


Just for fun, here's the most basic implementation of grid drawing I could whip up in a few minutes:

tool
extends Node2D

const GRID_STEP = 40
const GRID_SIZE = 20

func _draw():
    for i in range(GRID_SIZE):
        var col = Color("#aaaaaa")
        var width = 1.0
        if i == GRID_SIZE / 2:
            col = Color("#66cc66")
            width = 2.0
        draw_line(Vector2(i * GRID_STEP, 0), Vector2(i * GRID_STEP, GRID_SIZE * GRID_STEP), col, width)

    for j in range(GRID_SIZE):
        var col = Color("#aaaaaa")
        var width = 1.0
        if j == GRID_SIZE / 2:
            col = Color("#cc6666")
            width = 2.0
        draw_line(Vector2(0, j * GRID_STEP), Vector2(GRID_SIZE * GRID_STEP, j * GRID_STEP), col, width)

Screenshot_20210430_112306

Of course it's not infinite, doesn't have primary/secondary lines, metrics and origin are hardcoded, it doesn't hold any kind of metadata or other information that could actually be used to draw stuff on the grid (like a vector from one grid position to another), etc. All these are design concerns that are use-case specific and would be best assessed along the way to fix users' expressed needs.

YuriSizov commented 3 years ago

Just as some food for thought, drawing grids may be better done with shaders, especially if we need them to be infinite and smooth looking. For one, AA on draw_line is a performance killer. May not matter in apps much, but is a problem for games.

Zireael07 commented 3 years ago

Just some food for thought, many proposals that look valid initially turn out to have suprisingly easy GDScript solutions. Can we have a website or something to share snippets such as the grid one above?

Xrayez commented 3 years ago

Just as some food for thought, drawing grids may be better done with shaders, especially if we need them to be infinite and smooth looking. For one, AA on draw_line is a performance killer. May not matter in apps much, but is a problem for games.

May be overkill, unless the grid is going to be rotated (like the canvas in the painting apps) so aliasing artifacts are going to be prominent in those cases without AA...

Just some food for thought, many proposals that look valid initially turn out to have suprisingly easy GDScript solutions. Can we have a website or something to share snippets such as the grid one above?

There are websites such as https://gdscript-online.github.io/ and https://gd.tumeo.space/, but all of them are headless (nothing can be rendered). The official Web editor could further help this limitation, but it's still considered experimental...


By the way, this "proposal" can be converted into a discussion if/when we move to use this workflow as proposed in #2069. I guess neither me nor anyone else here would go into direction of whether this should be a plugin vs core debate in the first place.

Calinou commented 3 years ago

Just some food for thought, many proposals that look valid initially turn out to have suprisingly easy GDScript solutions. Can we have a website or something to share snippets such as the grid one above?

The asset library is a perfect place for this kind of stuff :slightly_smiling_face:

If its usability isn't good enough, we should improve that instead of creating yet another platform, which would further fragment the community.

Jummit commented 3 years ago

Just some food for thought, many proposals that look valid initially turn out to have suprisingly easy GDScript solutions. Can we have a website or something to share snippets such as the grid one above?

The asset library is a perfect place for this kind of stuff slightly_smiling_face

If its usability isn't good enough, we should improve that instead of creating yet another platform, which would further fragment the community.

Getting off-topic, but I'd love a snippet sharing site for Godot, I have thousands of lines of small utility functions which I think could be helpful for many. Just browsing the site would be great for learning, too.

LikeLakers2 commented 3 years ago

Just as some food for thought, drawing grids may be better done with shaders, especially if we need them to be infinite and smooth looking. For one, AA on draw_line is a performance killer. May not matter in apps much, but is a problem for games.

I thought about this a little bit, and while I do think it would have better performance with shaders (since shaders would run on the GPU), I don't actually think shaders would be a good solution here.

Namely, if this were implemented in a shader, then I don't believe we have a way to call into user-defined code. And since we can't call into user-defined code, we can't let the user implement game-specific things such as what you mentioned before (making some lines thicker, but not others; making some lines disappear; etc.).

Of course, this is assuming we want to go a similar direction as what I suggested before (a virtual function called for each line), but within a shader.

me2beats commented 3 years ago

I'm more interested in a container that would create an infinite area, scrolling and zooming, and which is drawn with a grid. Like GraphEdit. And children can be freely rearranged with or without grid snapping

I need this to create various applications and plugins such as mind map and drawing app, charts etc.

To create a mindmap app, I hope GraphEdit will be a good base, but in other cases, when I do not need to use connections between children, GraphEdit will not be useful.

Xrayez commented 3 years ago

Due to questioned usefulness of the plain Grid node by pycbouh, yeah, I guess implementing something like GraphEdit could be much useful. Of course, all these scrolling and zooming features are better implemented as opt-in features, depending on what you actually need to enable for your plugin.

But if we take the Godot's approach of "composing simple nodes to do complex stuff", those features may be already available using existing built-in nodes such as ScrollContainer to be used as parent of Grid node. Yet implementing the zooming behavior wouldn't be as trivial to do via script for less experienced users, though. Would we also need a ZoomableContainer? That actually makes some sense to me since what ScrollContainer does is actually translating child nodes, and ZoomableContainer would scale child nodes. Technically, zooming features could also be implemented as part of ScrollContainer if there's a desire to prevent bloat.

These are all ideas, mind you!

Xrayez commented 3 years ago

I have come up with an implementation which I'm personally satisfied with in goostengine/goost#80. Only constructive criticism is accepted. 🙂

You can download the editor from the GitHub Actions build artifacts in the PR above and run the example project (along with any other feature the Goost project currently provides).


By the way, I've found another project which uses a grid: https://github.com/Zylann/godot_grapher. The functions provided by proposed GridRect class could certainly help converting between grid/plot and Control rect coordinates: https://github.com/Zylann/godot_grapher/blob/12e46a5f9660fbbae765fc0a306f465be986903c/graph_view.gd#L47-L48.

Xrayez commented 3 years ago

I've released Goost 1.1 editor and export templates with GridRect node as described above, feedback welcome!