+2 votes

I am aware of the option to create it manually using the "Create CollisionPolygon2D Sibling" option. However, I will be using procedurally generated images and cannot do it manually. I also cannot find a way to do it using an array of numbers or by getting the transparent parts of the image. How can I do it using a script?

in Engine by (39 points)

2 Answers

+3 votes
Best answer
func _create_collision_polygon():
    var bm = BitMap.new()
    bm.create_from_image_alpha(texture.get_data())
    var rect = Rect2(position.x, position.y, texture.get_width(), texture.get_height())
    var my_array = bm.opaque_to_polygons(rect)
    var my_polygon = Polygon2D.new()
    my_polygon.set_polygons(my_array)
    get_parent().get_node("CollisionPolygon2D").set_polygon(my_polygon.polygons[0])

Put this in a script attached to the sprite where the sprite has a StaticBody2D as its parent and its sibling is a CollisionPolygon2D. Make sure the sprite is not centered.

NOTE: This collision is not perfect and seems to be slightly incorrect on some parts. This can be fixed by replacing var my_array = bm.opaque_to_polygons(rect) with var my_array = bm.opaque_to_polygons(rect, 0.0001), although the second argument could probably be replaced with some other small number.

EDIT: Here is a way to do it when the image is made of multiple parts (the StaticBody2D node does not need to have a CollisionPolygon2D manually added):

func _create_collision_polygon():
    var bm = BitMap.new()
    bm.create_from_image_alpha(texture.get_data())
    var rect = Rect2(position.x, position.y, texture.get_width(), texture.get_height())
    var my_array = bm.opaque_to_polygons(rect, 0.0001)
    var my_polygon = Polygon2D.new()
    my_polygon.set_polygons(my_array)
    for i in range(my_polygon.polygons.size()):
        var my_collision = CollisionPolygon2D.new()
        my_collision.set_polygon(my_polygon.polygons[i])
        my_collision.scale = scale
        get_parent().call_deferred("add_child", my_collision)

EDIT2: And here is how I made it work with centered sprites:

func _create_collision_polygon():
        var bm = BitMap.new()
        bm.create_from_image_alpha(texture.get_data())
        var rect = Rect2(position.x, position.y, texture.get_width(), texture.get_height())
        var my_array = bm.opaque_to_polygons(rect, 0.0001)
        var my_polygon = Polygon2D.new()
        my_polygon.set_polygons(my_array)
        var offsetX = 0
        var offsetY = 0
        if (texture.get_width() % 2 != 0):
            offsetX = 1
        if (texture.get_height() % 2 != 0):
            offsetY = 1
        for i in range(my_polygon.polygons.size()):
            var my_collision = CollisionPolygon2D.new()
            my_collision.set_polygon(my_polygon.polygons[i])
            my_collision.position -= Vector2((texture.get_width() / 2) + offsetX, (texture.get_height() / 2) + offsetY) * scale.x
            my_collision.scale = scale
            get_parent().call_deferred("add_child", my_collision)
by (39 points)
edited by

Is there a way to export the results of this to a saved file, like a tscn file. I have large maps that I would like to pre-compile.

+1 vote

I think there is some room to experiment with BitMap: https://docs.godotengine.org/en/3.1/classes/class_bitmap.html

You could create a BitMap by iterating pixels of the image and setting to true bits that you consider "opaque". Then, use the opaque_to_polygons function, which appears to do what you want.
Beware I never used this, I just found it by luck some day and found those neat functions. It's not documented so you might have to do some digging.

Other ways could involve marching squares, or just looking at Godot's source code to see how it generates the polygon from the image.

by (27,595 points)

I attempted to add the shape using this code in a script attached to my sprite:

func _create_collision_polygon():
    var bm = BitMap.new()
    bm.create_from_image_alpha(texture.get_data())
    var rect = Rect2(0, 0, texture.get_width(), texture.get_height())
    var my_array = bm.opaque_to_polygons(rect)
    var my_polygon = Polygon2D.new()
    my_polygon.set_polygons(my_array)
    get_parent().get_node("CollisionPolygon2D").set_polygon(my_polygon.polygon)

I also tried replacing my_polygon.polygon with my_polygon.polygons and my_polygon.uv and neither worked.

Are there any mistakes? Also, do you have any suggestions for how to modify it to get it to work?

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.