Adding collision detection to Line2D + CollisionPolygon2D?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Sylvain22

Hi,

I’m new to Godot. Using v3.2.1
I’m working on game which is a Qix engine / Gals Panic like clone.
It is a 2D games with a cursor drawing rectilinear shape revealing an hidden background.
So reducing the area where a monster is moving.

I need to create a trail for my player (yellow on the screenshot). I’m thinking of using a Line2D with an associated StaticBody2D with a CollisionShape2D. So I added the CollisionPolygon2D at runtime matching the points of the line.

scene nodes

And the gdscript, that will set the shape at runtime:

func _ready():	
	# copy line points to a new polygon
	var pol : PoolVector2Array
	pol.append_array(points)
	# recopy all points back to origin
	var n = points.size()
	for i in range(n-1, 0, -1):
		pol.append(points[i])
	$StaticBody2D/CollisionPolygon2D.polygon = pol

Is it the way to go or should I use other object?

So I’m answering to myself. :wink:

Such “flat” polygon collision shape generate error’s message in the debugger. But the collision was working as it seems.

But I finally needed a more fine grained control over the collision of this shape (The tail). So I redesigned the tail as follow:

tail object desing

The tail is basically a Line2D with an empty StaticBoby2D

The collision shape is a now a list of Segment2D managed by the following code. To avoid collision with the player, I added some code to reduce the size of the last two segment. Because I still use collision detection with the player as we are not allowed cross the tail.

The code is not yet optimized, and update all segment each time, but in the gameplay only the last two only need to be updated.

func update_collision_shape():
	var n = points.size()
	if n < 2:
		# waiting for a 2nd point to create a segment
		return

	# replace all segments (for now, TODO: optimize the update)
	segments.clear()
	for i in range(n-1):
		segments.append(create_segment(points[i], points[i+1]))
		
	reduce_last_segment()
	
	# re-add all segments: delete old childs and recreate all
	clear_all_shape(true)
	for s in segments:
			$col.add_child(s)

And the helpers functions (a lot of variables are exposed for debugger easy access):

func clear_all_shape(keep_segments = false):
	for c in $col.get_children():
		c.queue_free()
	if not keep_segments:
		segments.clear()

func create_segment(p1 : Vector2, p2 : Vector2, shorten : int = 0) -> CollisionShape2D:
	var collision = CollisionShape2D.new()
	collision.shape = SegmentShape2D.new()
	collision.shape.a = p1
	collision.shape.b = p2

	if shorten > 0:
		reduce_segment(collision, shorten)
	return collision
	
func reduce_segment(seg : CollisionShape2D, shorten : int):
	var v = (seg.shape.b  - seg.shape.a).normalized()
	# we calculated the length of the segment, reducing at max of its length
	var d = seg.shape.a.distance_to(seg.shape.b)
	var delta = min(d, shorten)
	var b = seg.shape.b
	b -= v * delta
	seg.shape.b = b

func reduce_last_segment(shorten : int = -1) -> int:
	if points.size() < 2 or segments.size() == 0:
		return 0
		
	if shorten < 0:
		# default value
		shorten = last_segment_reduction
		
	# r count the number of segment modified
	var r = 0
	# last point is the player, to avoid collision we shorten
	# this last segment, or even the previous one on corner
	var d = points[-2].distance_to(points[-1])
	if d < shorten:
		# skip last segment too short
		segments.pop_back()
		r += 1
		
		# we also reduce previous segment, as are still to near
		if segments.size() > 0:
			reduce_segment(segments[-1], shorten - d)
			r += 1
	else:
		# we shorten the last segment only
		reduce_segment(segments[-1], shorten)
		r += 1
	return r

And the runtime the node tree result is as follow:
tail attached to playground at runtime

And it looks like:

So now the player can collide with the tail and is stopped. The player’s collision in the same layer as the tail is the small 5 px circle shape. Collision between player and tail are disabled while rewinding on the tail.

I will evolve as I discover more on Godot and as I need to handle more use case.

Suggestions are welcome. :slight_smile:

Regards,
Sylvain.

Sylvain22 | 2020-05-08 04:52

sorry, just want to know what is the last segment reduction here.

func reduce_last_segment(shorten : int = -1) -> int:
    if points.size() < 2 or segments.size() == 0:
        return 0

    if shorten < 0:
        # default value
        shorten = last_segment_reduction

EXSPIRAVIT_1104 | 2022-04-07 00:24

Hello,

Such a long time ago… I did not finish the project, nor worked on it since 2020.

OK. So its just a variable of the module I defined in the script

# will be initilized by the play in set_players_info()
var last_segment_reduction : int  = 0
# [...]
# it is  also assigned with this small setter
func set_players_info(player_distance):
	last_segment_reduction = player_distance

Only those 3 matches in the code.

I don’t really remember how it works without looking at the code more deeply.

I hope it helped you at some point. :wink:

EDIT:

oh, this value is for auto-reducing the last segment size so it doesn’t self collide to with the player itself.

You see on the screenshot, in yellow the tail, the ruby is the player, I squared in white the space I made, and you see in green the collision shape that are used.

So there’s a small gap based on the player size, to avoid some self collision to be detected. But if the trace / tail loop, the central player’s dot (circle) will collide in the tail’s segment.

Regards,
Sylvain.

Sylvain22 | 2022-05-06 06:01