godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.12k stars 69 forks source link

Add a Shape2D node to draw 2D shapes #1126

Open I7SOD opened 4 years ago

I7SOD commented 4 years ago

Describe the project you are working on: Checking out Game engines

Describe the problem or limitation you are having in your project: I have to create my own Sprite by myself even if I want to use a basic shape like a triangle

Describe the feature / enhancement and how it helps to overcome the problem or limitation: Add a shape node 2d to handle simple shape objects

Describe how your proposal will work, with code, pseudocode, mockups, and/or diagrams: A shape2d object that has different basic shapes

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

Is there a reason why this should be core and not an add-on in the asset library?: I don't really know I come from unity background which has it as a core object

jonbonazza commented 4 years ago

I also agree this should be in there. We have something similar for Meshes, but for 2D, there's no easy way (that I know of) to just create a simple shape Sprite.

BeayemX commented 4 years ago

Are you familiar with the Polygon2D node? https://docs.godotengine.org/en/3.2/classes/class_polygon2d.html

Or do you want to have a set of predefined polygons that you can choose from?

jonbonazza commented 4 years ago

I believe this is for the latter

I7SOD commented 4 years ago

I am familiar with the polygon node 2d @BeayemX but with the polygon node your shape is offset and gives really bad behavior. I see the polygon node as a way of creating objects like grounds, or objects that don't require precise position. And also the shape is never really accurate. There are lot of problems using the polygon node to create a shape that is used for character movement

IoneGod commented 4 years ago

I was about to create a proposal related to this then I saw this proposal so I totally agree with this proposal

Calinou commented 4 years ago

See also #112 and https://github.com/godotengine/godot/issues/6750.

BeayemX commented 4 years ago

your shape is offset

That depends on where you are drawing. If your Polygon2D contains the center of your scene, placing instances of your scene should be straight forward and work as expected.

objects that don't require precise position. And also the shape is never really accurate

You can use grid snapping in the 2D viewport to make the points' position precise or adjust the values in the inspector.

There are lot of problems using the polygon node to create a shape that is used for character movement

Like what? The Polygon2D node is just a visualization. This should not have any effect on the character movement.

Overall feedback

These proposals usually work better if we understand what you are trying to do and where the problem occurs. Things like

gives really bad behavior

are hard to relate to if we don't know your use case.

What exactly are you trying to accomplish?

What steps are you taking and where does the problem occur that you are trying to solve.

How does this proposal improve the situation?

Xrayez commented 4 years ago

godotengine/godot#37903 (available since 3.2.2 released yesterday) makes it possible to achieve visuals similar to "Visible Collision Shapes" option via script to draw any Shape2D resources, but there's no way to draw a texture over such shape alone as in Polygon2D, which is much more superior anyways I think, because you can represent any shape with it visually (even if you draw a circle, that's still approximated by series of arcs).

Xrayez commented 4 years ago

So I'm thinking that it should be made possible to add a tool to Convert Shape2D to Polygon2D as well to get the best from both worlds, either directly in the engine or as independent plugin.

IoneGod commented 4 years ago

I think it will be better as a plugin because for android developers like me I don't really see Godot allowing aab format as of now which makes the code heavier and also the feature isn't really much used at least from how I see it

I7SOD commented 4 years ago

I think it will be better as a plugin because for android developers like me I don't really see Godot allowing aab format as of now which makes the code heavier and also the feature isn't really much used at least from how I see it

@AndroidGod101 I actually disagree with you on this one because there is a 3D shape creation node why not have one for 2D, I see that as totally breaking consistency. And as of Godot 3.2.3 there will be support for android aab format (I hope ) because there have been a lot of requests for it check #342 . I don't know much about how frequently it will be used but from my Idea every game uses at least 1 shape

  1. For platformers you could use a rectangle shape to create platform and just give it a color or texture resource

  2. If you are creating a simple racing game just use circles for the tyre. But these are just a few uses on videos I have seen on Youtube where simple shapes are used to create amazing games

Calinou commented 4 years ago

@AndroidGod101 Please don't derail the discussion with unrelated proposals. Generally, we want to review proposals individually, not in relation to others.

I7SOD commented 4 years ago

So do we have a go on this proposal?

Calinou commented 4 years ago

@I7SOD We don't really have a formal go/no go process (yet). That said, I think we need a bit more discussion to see how we should actually implement this feature. There's rarely only one way to go about implementing something.

Xrayez commented 4 years ago

your shape is offset

That depends on where you are drawing. If your Polygon2D contains the center of your scene, placing instances of your scene should be straight forward and work as expected.

One of the issues is likely godotengine/godot#12353, but Polygon2D is only visual, unless you set the collision shape from Polygon2D.polygon property directly via code.

Xrayez commented 4 years ago

I'm writing VisualShape2D node in a module I'm developing: GoostGD/goost#2, can this implement the proposal @I7SOD?

A shape2d object that has different basic shapes

You can create your own basic shapes by editing ConvexPolygonShape2D resources and saving that to disk to be reused on per-project basis. Of course there are existing CircleShape2D and similar which are already builtin.

I7SOD commented 4 years ago

@Xrayez Yeah that should solve this proposal well-done on the hardwork 🙂

Xrayez commented 4 years ago

Thanks, and yeah do not get confused, the class I've made won't be part of the Godot out of the box, but I can make a pull request directly in Godot if core developers find the implementation useful, but don't expect much given previous decisions.

The reason for making this in C++ is that I'm reusing quite a bunch of core functionality provided by Godot, but which is not exposed to scripting (like AbstractPolygon2DEditorPlugin, get_tree()->get_debug_collisions_color()) etc.

I7SOD commented 4 years ago

Thanks, and yeah do not get confused, the class I've made won't be part of the Godot out of the box, but I can make a pull request directly in Godot if core developers find the implementation useful, but don't expect much given previous decisions.

I totally understand The reason for making this in C++ is that I'm reusing quite a bunch of core functionality provided by Godot, but which is not exposed to scripting (like AbstractPolygon2DEditorPlugin, get_tree()->get_debug_collisions_color()) etc.

Okay that's cool this should bring a conclusion to this proposal. Again well done

Xrayez commented 4 years ago

As I'm thinking about it, my particular implementation is quite similar to CollisionShape2D in a way that both share and expose Shape2D resource, and the VisualShape2D allows to specify any color for drawing, and the shape can be synced with use_parent_shape (from CollisionShape2D etc).

So, it might be safe to say that, in most cases, the need to draw shapes comes from the need to draw physics bodies without using sprites, and the shape perfectly represents it.

So, in order to possibly address this issue on the core level, it would be just nice for the CollisionShape2D to:

Some features/bug-fix can be ported to Godot (like godotengine/godot#21394), as VisualShape2D in GoostGD/goost#2 does allow editing polygon-based shapes already (with some limitations), and I could probably work on that as well.

EDIT: On the other hand, you'd have to make similar thing for CollisionPolygon2D, and perhaps some other node which would use a Shape2D in the future (other than physics), so might not be worth it. Creating a dedicated class for visualizing shapes is probably the best idea, but that means adding another class in Godot, which is bloat.

Xrayez commented 4 years ago

Some discussion happened regarding being able to edit polygon-based shapes through CollisionShape2D node alone in godotengine/godot#21394, but reduz is not so keen implementing this kind of thing for physics-only stuff, which kinda makes sense, but not in the relation to this proposal (which likely won't be implemented in core anyways), so providing a warning instead: godotengine/godot#40091.

Being able to override the collision debug color and bypassing "Visible Collision Shapes" option per each node would still be useful I think, but it's sorta already possible to do via script given godotengine/godot#37903. The only issue left is poor usability for beginners to figure out on how to actually do this.

Xrayez commented 4 years ago

This is now implemented in Goost: GoostGD/goost#2, so the proposal can be closed, unless other Godot core developers would like this feature to be available directly in the engine of course. 😉

There are still some usability issues as described in the proposal I've made for Godot: #1157, but that's not high priority and can be solved on the module level in any case.

It is indeed possible to make this work with GDScript as well and make a simple plugin out of it, but I don't see the point remaking what's already available in core, and I just personally prefer this kind of feature to be closer to core. Goost provides some useful geometry methods which could also enhance the experience further. I invite people to checkout the VisualShape2D class and perhaps if more people could find it useful, it can be merged to Godot eventually.

I7SOD commented 4 years ago

Okay thanks for the feedback I will see what I can do from my end to make this work

DeadlyLampshade commented 3 years ago

This rather simple script allows you to draw children CollisionShape2D's, but considering how versatile such an object would be for prototyping, I can't help but support the idea of a Shape2D node.


func get_child_shapes():
    var array = []
    for child in get_children():
        if child is CollisionShape2D:
            array.append(child)
    return array 

func _draw():
    for child in get_child_shapes():
        draw_set_transform_matrix(child.transform)
        child.shape.draw(get_canvas_item(), Color.white)```
cgbeutler commented 3 years ago

Small detail on implementation. It would be nice if the "change type" behavior existed for shapes in the same way it exists for CollisionPolygon2D and Polygon2D. Then you could take a CollisionShape2D, duplicate it, change it's type to VisualShape2D, and the resource within would maintained.

Currently, if you take a CollisionPolygon2D and "change type" it to a Polygon2D, the shape is preserved and the polygon is rendered. This makes it easy to create a polygon then duplicate the node and change the type to get collisions and visual representation. I think the data being maintained is mainly due to the fact that the properties are named in a way that that both types can match up during the conversion. Not sure if Shape2D could be used as-is, cuz it looks like it automatically loads itself up to the physics server. If just drawing stuff, you may not want that side effect...

cgbeutler commented 3 years ago

For those who need workaround. I made a gist for a ShapePolygon2D. image

This subclass of Polygon2D can take a Shape2D and will unwrap it into the Polygon2D's polygon field. It's not perfect, but I'm sure you could tweak it to your needs.

https://gist.github.com/cgbeutler/9d4134e7937b211927f969c0ad3b01f0

Xrayez commented 3 years ago

I've also been working on other stuff in the meantime, and come up with boolean nodes in 2D thingy: goostengine/goost#39. PolyNode2D is basically like a base Polygon2D node which is geared towards dynamic shaping (more like CSG nodes in 3D). For now, Goost has PolyCircle2D and PolyRectangle2D nodes as basic building blocks, but even those can be converted to PolyNode2Ds to modify individual vertices (transformed or not). Those can be further used as collision shapes via PolyCollisionShape2D node with PolyNode2D as children defining the final shape!

Btw, you can download precompiled Godot+Goost binaries if you'd like to give those classes a try.

Calinou commented 2 years ago

My AntialiasedLine2D add-on now features an AntialiasedRegularPolygon2D node, which can be used to draw antialiased circles with or without an outline.

YuriSizov commented 2 years ago

There seems to be a lot of support for this, so we discussed it a bit in a review meeting. We weren't swayed much either way, but we wouldn't oppose it either. One issue we have presently that would hurt a feature like that is poor antialiasing support for 2D. @Geometror said to be working to an improved MSAA support, so with that this feature may actually be feasible.

So, in short, PRs are cautiously welcome!

chris-gassner commented 2 years ago

I'm pretty new to godot but just from a prototyping perspective an easy way to create circle shapes would be great. I'm not sure other shapes are really needed because those can be done with Polygon2D but something where you can just create a polygon with x number of sides would be nice to have. I just assumed it would be easy to create a simple circle because I've seen CircleShape2D for collisions.

golddotasksquestions commented 1 year ago

Just want to add some currently available workarounds (as of Godot 3.5.2 stable and Godot 4.1.1 stable)

Using Polygon2D: Has already been mentioned in this thread, requires the user to write some math code to generate the geometric shapes or draw them by hand. Anything is possible, but it's not user friendly, especially for geometric primitives.

Using Meshinstance2D: Works ok for basic Primitives like circles, squares, rectangles, triangles, pill shapes. However since these are not 2D but 3D primitive meshes, they are not optimized for 2D use. Their default sizes match the 3D unit (0.5 to 2) which is rendered as 0.5 to 2 pixel in 2D, so the user always has to adjust parameters just to see the shape. Some shapes are not rendered in the expected orientation, there is no option to rotate the shape. For example the TorusMesh renders as Pill, the PlaneMesh not at all (it is rotated in side view). If you add a Texture to any shape other than the Quad, it has a seam right in the middle splitting the texture and there is no way to shift UVs. Even though you can add a material and a texture to the albedo, it won't show up.

Using Sprite2D with GradientTexture2D as texture: Works ok for simple squares, rectangles and circles. However it is also rather tedious and quite fiddly to set up. Figuring out what settings to do what and get them right is not intuitive or fun at all.

All of these are Resources though, so they can be saved and reused. However because they are resources, they also have to be made unique every time, which is very easy to forget or miss and can easily ruin a lot of work.

Edit Note: For Godot 3.X there are also some plugins: Primitives2D, using AntialiasedRegularPolygon2D from the Antialiased Line2D plugin.

Personally I think the best solution would be just to add actual specific 2D primitive shapes to the MeshInstance2D node and remove all these 3D shapes which just come with unnecessary overhead, not the right setting options for 2D etc.

Calinou commented 1 year ago

Has already been mentioned in this thread, requires the user to write some math code to generate the geometric shapes or draw them by hand. Anything is possible, but it's not user friendly, especially for geometric primitives.

There's an add-on for it: https://github.com/godot-extended-libraries/godot-antialiased-line2d

It's not updated for 4.x yet, but I plan to update it at some point.

Personally I think the best solution would be just to add actual specific 2D primitive shapes to the MeshInstance2D node and remove all these 3D shapes which just come with unnecessary overhead, not the right setting options for 2D etc.

This would require creating a Mesh2D base type, which is fine in theory. However, in practice, this would also require changing the MeshInstance2D's mesh property to only allow using Mesh2D resources, which is a compatibility-breaking change.

golddotasksquestions commented 1 year ago

MeshInstance2D's mesh property to only allow using Mesh2D resources, which is a compatibility-breaking change.

Good point! The 3D Meshes could be kept beside the 2D Meshes until the next major release, but marked as depreciated.

However in my 5 years being on the community channels I have never seen anyone talk about MeshInstance2D node. I don't think many people are using it as of yet. I believe this is also partially due to the obscurity of the node, and the fact that when you add a shape you don't see it.

aXu-AP commented 10 months ago

This is my proposal for implementing this (or close enough):

ShapeTexture2D Similiar to other procedural textures like GradientTexture2D and NoiseTexture2D. You may ask, why a texture not a separate node with vector/polygon output? Main use cases I believe we need this are 1) Placeholder for prototyping, intended to be changed later 2) For use with particles, where simple textures go a long way. Both cases are better handled with texture-based approach. Additionally, making simple shapes with Polygon2D is quite trivial, even with tool scripts. The ShapeTexture2D would generate a texture with specified colored shape with transparent background, with optional border. The shape can be any regular polygon or star (and circle is a polygon with enough vertices).

API Following properties would be exposed:

// Same default size as with GradientTexture2D, big enough for decent fidelity, small enough not to fill the screen for low-res projects.
// Changing non-uniformly will squish the shape, to allow shapes like a diamond ♦ or Asteroids-type spaceship.
int width = 64 
int height = 64

// Amount of points. 3 for triangle, 4 for rectangle, max out for circle.
int points = 3
// Allow for rotating the shape.
float rotation_degrees = 0.0

// Adds additional vertices inbetween each vertex for a star shape.
bool is_star = false
// How far towards the center inner vertices go if is_star is enabled. 0 = no insetting, 1 = center.
float star_inset = 0.5

Color fill_color = Color.WHITE

Color border_color = Color.BLACK
float border_width = 0.0
// Similiar to Line2D's joint_mode, has 3 options: sharp, bevel or round.
LineJointMode joint_mode = LINE_JOINT_SHARP

Default values will give a white triangle. I think this api would be flexible enough for the feature to be useful, while being simple. If we want something even simpler, border and star shape features could be cut, resulting a very barebones version. If we want fancier, allow for textured fill and border, and rotating the inner vertices separately. But I think my suggestion is the sweet, godoty spot.

I can give it a try, shouldn't be too hard since GradientTexture serves as a good template.

Calinou commented 10 months ago

I'd say drawing the shape as a texture defeats the point of this proposal, which is to get vector-based drawing that scales to any resolution, including run-time changes (e.g. animation/zoom). A texture can't be perfectly scaled to arbitrary sizes without having to regenerate it every time its scale on screen changes, which is very slow for large sizes.

Drawing the shape as a MSDF texture on the other hand could be interesting, but it will have limitations that standard line drawing won't have (such as not being able to give the line a texture – it'll have to be a single plain color).

aXu-AP commented 10 months ago

The OP didn't specify which method should be used, also it didn't offer any concrete use cases (out of "I have to create my own Sprite by myself even if I want to use a basic shape like a triangle"). Kind of every post afterwards assumes that polygon it is, and OP answered that it would solve the issue, but then again, that doesn't doesn't exclude texture-based approach.

While I do see the benefits in vector-based approach and the precision it comes with, the actual solution depends on use cases. MSDF would definitely be an interesting approach, but again, for use cases I have in mind it's not very suitable: For placeholder objects you want something that you can do drop-in replacement - MSDF requires a custom shader so it doesn't fit the bill. For particle textures, the performance overhead which comes with MSDF might pose a problem (I don't have any data on this, so feel free to prove me wrong).

If vector-based solution is more useful for others, I propose a similiar API but with small changes. I think both approaches have their benefits and use cases, so they could both be considered:

Shape2D Inherits Node2D.

// Amount of points. 3 for triangle, 4 for rectangle, max out for circle.
int points = 3

// Distance of points from shape center.
float radius = 32.0
// Squishes the shape along one axis.
float aspect = 1.0
// Allow for rotating the shape.
// Points will be rotated before stretching with aspect, which allows for more control (for rotating the resulting shape, use Node2D's rotation).
// Also expose radian version for scripting like existing nodes.
float shape_rotation_degrees = 0.0
// Changes the pivot point.
Vector2 offset = Vector2(0, 0)

// Adds additional vertices inbetween each vertex for a star shape.
bool is_star = false
// How far towards the center inner vertices go if is_star is enabled. 0 = no insetting, 1 = center.
float star_inset = 0.5

Color fill_color = Color.WHITE
Texture2D fill_texture = null
// TILE or STRETCH
TextureMode fill_texture_mode = TILE

Color border_color = Color.BLACK
Texture2D border_texture = null
TextureMode border_texture_mode = TILE
float border_width = 0.0

// Beveling of corners in pixels.
float bevel_size = 0.0
float bevel_quality = 1.0
bool anti_aliasing

In contrast to my previous proposal, this version has texturing options, since they would be trivial to add and likely matches the needs of use cases where vector is preferable over textures. Also replaced joint mode with bevel, which is more flexible and doesn't require adding an enum. Amount of vertices to use in beveling is determined by angle ie. changing points, star_inset or bevel_size adjusts the amount automatically. An alternative is to use already existing LineBuilder and expose same properties as Line2D does, but this option isn't very flexible, and doesn't allow for converting the border to Polygon2D (see below).

Also add Shape2DEditorPlugin, which would add menu options:

MewPurPur commented 10 months ago

@Calinou What do you mean by "vector-based"? If you mean something like Line2D (internally using a Mesh2D), then imho we don't need a whole node for this, we should instead add some utility over Polygon2D and Line2D (like in the toolbar) to make them configurable like regular shapes. I think ShapeTexture2D is a superb implementation, because it can be used for stuff like particles, which often need these sorts of simple shapes, and you can also easily add it for stuff like Sprite2D too if you need a simple placeholder.

image

aXu-AP commented 10 months ago

@MewPurPur Polygon2D utility is one way to go around this, but I think animatability is a valid point for a separate node. You can't animate options from a toolbar. And these solutions aren't mutually exclusive either, since they have somewhat different use cases.

For anybody interested in texture-based ShapeTexture2D, grab yourself an editor build from my pr godotengine/godot#84587 (you can find them from Checks tab and Artifacts from right side) and see if it fits your needs (wouldn't recommend using it in your project however!). Few notes: points amount isn't limited*, so you can accidentally freeze the editor for some time if you rise the amount too high. Rounded corners aren't yet available, and changing aspect stretches the borders as well. I have plans how to fix these, but feedback is already welcome 😊

*It seems that editor doesn't support property hint range "min,max,or_greater" for ints, it just removes the limit altogether. I wanted to limit the points when dragging the slider, but allow for manual input of bigger numbers...

Calinou commented 10 months ago

@Calinou What do you mean by "vector-based"? If you mean something like Line2D (internally using a Mesh2D), then imho we don't need a whole node for this, we should instead add some utility over Polygon2D and Line2D (like in the toolbar) to make them configurable like regular shapes.

That makes sense, but the functionality needs to be exposed in a way it can be used at runtime too (so it's not restricted to editor usage). This could be in the form of a make_regular_polygon(size: Vector2, points: int, star_inset: float) method that is part of Polygon2D and Line2D.

Few notes: points amount isn't limited*, so you can accidentally freeze the editor for some time if you rise the amount too high. Rounded corners aren't yet available, and changing aspect stretches the borders as well. I have plans how to fix these, but feedback is already welcome 😊

*It seems that editor doesn't support property hint range "min,max,or_greater" for ints, it just removes the limit altogether. I wanted to limit the points when dragging the slider, but allow for manual input of bigger numbers...

I suggest using a standard min,max where max is something like 256. This should be plenty already even for large circle textures. If you allow manual input of larger numbers, users will be able to freeze the editor regardless. You could also internally clamp the number of sides based on the texture's largest dimension (there's no point in having more sides than there are pixels on one dimension).

omniuni commented 2 days ago

Based on other areas of Godot and the editor itself, it should be possible to allow Polygon2D to draw the other polygons, such as those available in CollisionShape2D. The engine is fully able to render these shapes in debug mode, so it should be relatively trivial to allow them to be a visual item.

Ideally, Shape2D should include all of the shapes available in CollisionShape2D, allowing it to also potentially replace Polygon2D. This means shapes like Rectangle, Circle, Capsule, and Segment at least.

Minimally, in additional to color and gradient fills, outline color and width should be added as options as well.

Future additions could include toggles for scaling the outline and fill (gradient) when the shape itself is scaled.

It would also be very cool to add a toggle for whether the fill follows rotation or not, allowing for the creation of vector shapes that keep the gradient oriented to create an illusion of shadow, for example.

Polygon2D already allows nesting of other Polygon2D, so this would start to create a foundation of vector graphics within Godot.

Although adding more detailed vector items such as Bezier curves would be a huge lift, these basic shapes are already part of Godot's rendering pipeline, so adding them should be (relatively) trivial.

It would also be nice to allow these shapes to have a physics toggle, OR to enable these same visual options for CollisionShape2D.

Although simple, I believe this could begin to add powerful drawing capabilities that have the potential to grow to a point of enabling some incredible vector-based games in Godot!

omniuni commented 2 days ago

The above comment was copied from my proposal before I realized that this open issue was already created. I wanted, however, to specifically add that given the engine's existing ability to draw these shapes and outlines per collision debugging, this is specifically for use directly within the core engine, not as an extension.

This is especially the case, considering that Goost does not appear to be actively maintained, so is in unacceptable solution for Godot 4.x.