Using Polygon2D nodes to create physics bodies in Godot

Sample project

Tilemaps are the bread and butter of both side-scrolling and top-down 2D games as they let developers quickly lay out levels with artwork, physics, and pathfinding all in one go. But they do have a downside in that they are fixed to a grid that is, in many cases, quite obvious. To get around this, a developer might use a smaller tilemap grid size so they can add in finer details, use tiles that are functionally identical to others but have different artwork, or layer tiles to provide variety. But sometimes, what you really want is something so custom that it doesn’t fit neatly within the limitations of a tilemap. This is where the Polygon2D node can come in handy. While this node is often used to simply draw polygons on the screen, I’d like to use this post to discuss another use for them I’ve discovered: using them to create custom physics bodies. My primary interest is in using the Polygon2D node to create custom level geometry, so a static body is good enough for me, but for the sake of completeness I’ll also take a brief look at modifying the technique for rigid bodies.

The Polygon2D node

First things first, what is the Polygon2D node? It’s a node that lets you draw a polygon in 2D space (created either programmatically or in the editor) on the screen, supporting some nice features such as anti-aliasing, skeletal animation, and textures. In a basic use case, it might look something like below.

That’s a shape that I’ve drawn in the editor and will render identically in-game. With the flat color I’ve used, it’s not much to look at, unless you’re going for a minimalistic style, but since you can texture Polygon2Ds, you can end up with something much more serviceable.

Curious about how I got the texture to repeat on the polygon? Just enable Repeat in the texture import settings.

And while Polygon2D nodes are a great resource for drawing custom shapes in the game, I found my interest pointing towards how I could use them in a physics context so that I could draw a polygon in the editor and have it act as a physically simulated object in the game. It turns out it’s quite easy if you’re wanting to use it as a static physics object (ie one that doesn’t move and instead just provides a collision shape for other objects to hit), but a bit trickier to get it to work as a dynamic object, such as a rigid body.

Project setup

To test this, I set up a simple project containing three nodes:

  • A ball node, which is just a rigid body with a sprite on it to test my physics bodies with
  • A polygon_static_body scene to demonstrate how to use the Polygon2D node with a StaticBody2D node
  • A polygon_rigid_body scene to demonstrate this technique with a Rigidbody2D node

I’ve also gone ahead and drawn a polygon for both Polygon2D nodes using the built in editor tools and applied the styling I want to them. Let’s now take a look at each scene and see how they work.

The static body scene

The polygon_static_body scene is quite simple, containing a root Polygon2D node and a child StaticBody2D node. While I could instantiate the StaticBody2D node at runtime, I’ve added it in the editor to make it easier to configure. The script on the parent node is quite simple:

extends Polygon2D

func _ready():
	var collision_shape =
	# Polygon2D.polygon contains a list of all vertices on the drawn polygon
	# so just copy those points over to the new collision shape
	collision_shape.polygon = polygon

All I do is create a new CollisionPolygon2D node, copy the points of the Polygon2D node over to it, and add this new collision shape as a child of the scene’s static body. And that’s enough to have this scene behave how I want it to.

The rigid body scene

The concept behind the polygon_rigid_body scene is the same as on the static body scene, but there are a couple of issues to deal with that the static body scene doesn’t have:

  • The center of mass on a Rigidbody2D is determined by its origin, meaning I need to move the Rigidbody2D to the center of the drawn polygon without also moving the collision shape or Polygon2D node
  • The Polygon2D node needs to move and rotate with the rigid body node, meaning it needs to either be the scene parent and then get reparented to the rigidbody at runtime, or it needs to be created as a child of the rigid body from the beginning

For the sake of simplicity, I opted to just make it a child of the Rigidbody2D node. It can still be drawn in the editor this way, you just have to right click on the scene and check “Editable children” to make it appear. Unfortunately, this will expose all of the children of the rigid body parent, which means it will clog up the scene tree faster, but I think this allows for a simpler and arguably better approach than re-parenting at runtime, which involves swapping node positions relative to one another and requires at least one frame of inconsistency (since we can’t swap node positions until the tree is settled).

The code for the scene is below:

extends RigidBody2D

onready var polygon = $Polygon2D

func _ready():
	# Store the polygon's global position so we can reset its position after moving its parent
	var polygon_global_position = polygon.global_position
	# Move the rigidbody to the center of the polygon, taking into account
	# any offset the Polygon2D node may have relative to the rigidbody
	var polygon_center = get_polygon_center()
	global_position += polygon_center + polygon.position
	# Move the polygon node to its original position
	polygon.global_position = polygon_global_position
	var collision_shape =
	collision_shape.polygon = offset_polygon_points(polygon_center)

# Offset the points of the polygon by the center of the polygon
func offset_polygon_points(center: Vector2):
	var adjusted_points = []
	for point in polygon.polygon:
		# Moving the collision shape itself doesn't seem to work as well as offsetting the polygon points
		# for putting the collision shape in the right position after moving the rigidbody.
		# Therefore, to have the collision shape appear where drawn, subtract the polygon center from each point
		# to move the point by the amount the rigidbody was moved relative to the original Polygon2D's position.
		adjusted_points.append(point - center)
	return adjusted_points

# A simple weighted average of all points of the polygon to find the center
func get_polygon_center():
	var center_weight = polygon.polygon.size()
	var center = Vector2(0, 0)
	for point in polygon.polygon:
		center.x += point.x / center_weight
		center.y += point.y / center_weight
	return center

The code for this scene is a bit more involved. I first calculate the center of the drawn polygon and move the rigid body to that location (including the Polygon2D node’s relative position since the points of the polygon won’t take that offset into account when moved to the new collision shape) while also taking into account its own position in the world. Since this node is the parent of the scene, moving it will also move the Polygon2D node, so I make sure to remember where the polygon was originally located in the world and reset its position so that it will draw in the correct position.

For the collision shape, I shift the drawn polygon by the amount calculated in get_polygon_center() before applying it to the collision shape. This seems to work better than using unshifted points and moving the collision shape after the fact. I’m sure there’s other ways to do what I’ve done here, but this method works and isn’t too sloppy, so I’ll stick with it for now.

Results and final thoughts

The video below shows what happens when I run the scene:

Everything works as expected! I’m able to draw static and dynamic polygons in the editor and have them become physics bodies in the game. As previously mentioned, I’m not convinced the rigid body scene couldn’t be programmed differently, but I’m also not as interested in using this method for dynamic physics objects so I’m going to leave it as is for now.

If you’re interested in exploring this method further, I’d recommend checking out this plugin that allows you to add textured borders to Polygon2D nodes, really letting you make something sleek and sexy.