Using Line2D nodes to create physics bodies in Godot


Sample project

In a previous post, I showed how to take a Polygon2D node and create a physics body out of it. As a quick followup to that post, I want to demonstrate how you can do the same thing with a Line2D node. I think this method in particular is interesting because it let’s us play with Godot’s awesome Geometry class (which I really need to do a full post on at some point). Plus, I’m a big fan of games where the player gets any sort of way creativity to interact with the world, and drawable physics objects are a great fit for that.

Static bodies

To kick things off, let’s look at how we can turn a Line2D into a static physics body. If you’ll recall how the Polygon2D static body was setup, we created our polygon in the editor and simply copied its points to a collision shape:

extends Polygon2D

func _ready():
	var collision_shape = CollisionPolygon2D.new()
	# 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
	$StaticBody2D.add_child(collision_shape)

The concept for the Line2D static body is the same thing, with one major exception. We can’t take a line, which is infinitely thin, and make a polygon out of it. We need to thicken it in some way so that we can turn it into a polygon. So how do we do that? With the offset_polyline_2d function in the Geometry class. This function takes a polyline and expands it by a given amount of pixels into one or more polygons. So to create a physics body that matches what we have drawn with the Line2D node, we can just call this function, setting the delta value to half of the Line2D’s width, and then create a CollisionPolygon2D node for each returned polygon. This results in the following scene and code:

extends Line2D

func _ready() -> void:
	var line_poly = Geometry.offset_polyline_2d(points, width / 2)
	
	# offset_polyline_2d can potentially return multiple polygons
	# so iterate through all polyons and create collision shapes from them
	for poly in line_poly:
		var col = CollisionPolygon2D.new()
		col.polygon = poly
		$StaticBody2D.add_child(col)

You can also pass values to define how to create the joins and ends of the polygon (round off the edges, keep them square, etc), but the options for this function and the Line2D node do vary a bit, so you’ll have to reference the docs to figure out what will work best for you.

var col_poly = Geometry.offset_polyline_2d(points, width / 2, Geometry.JOIN_ROUND, Geometry.END_ROUND)

Rigid bodies

To make a rigid body, it’s the same technique as what I showed before with Polygon2D nodes, we just need to first generate the buffered polygon(s) and then create a CollisionPolygon2D for each polygon generated. In fact, the code below is a direct copy of the Polygon2D code, comments and all, just with the appropriate changes to generate polygons from a Line2D and create collision shapes for each one. If you want to know more about why I did what I did here, check out the original Polygon2D post, but in short:

  • To have the Line2D node move and rotate with a rigidbody it needs to be a child of that node, so we make the scene root be the RigidBody2D node in this case.
  • The center of mass on a RigidBody2D node is determined by its origin, so the scene’s RigidBody2D node needs to be moved to the center of the drawn line, but since it’s the root node of the scene, we have to offset its children and the generated collision shapes after the move to keep everything in the right position.
  • I’m not completely happy with this implementation, but I’ve yet to find a way that satisfies these requirements that I’m particularly fond of.

extends RigidBody2D

onready var line = $line

func _ready():
	# Store the line's global position so we can reset its position after moving its parent
	var line_global_position = line.global_position
    # Generate collision polygons from the Line2D node
	var line_poly = Geometry.offset_polyline_2d(line.points, line.width / 2, Geometry.JOIN_ROUND, Geometry.END_ROUND)
	# Move the rigidbody to the center of the line, taking into account
	# any offset the Polygon2D node may have relative to the rigidbody
	var line_center = get_line_center()
	global_position += line_center + line.position
	# Move the line node to its original position
	line.global_position = line_global_position
	
	# line_poly may contain multiple polygons, so iterate over it
	for poly in line_poly:
		var collision_shape = CollisionPolygon2D.new()
		collision_shape.polygon = offset_line_points(line_center, poly)
		add_child(collision_shape)

# Offset the points of the polygon by the center of the line
func offset_line_points(center: Vector2, poly: Array) -> Array:
	var adjusted_points = []
	for point in poly:
		# 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 Line2D's position.
		adjusted_points.append(point - center)
	return adjusted_points

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

Results and final thoughts

The video below shows the results of today’s sample project, which consists of a static line body, a rigid line body, and a ball to see how everything interacts together:

We can see that we get the same results as with the Polygon2D tutorial! Static and rigid physics bodies that are generated at runtime based on Line2D nodes. The next step here would be to let the player draw their own shapes during the game to really create something fun and unique. In fact, I did something like that for one of my first game jams using Godot. Just forgive me for the terrible artwork and floaty physics!