Top-level Line2D is not being captured/rendered by its CanvasGroup parent

Godot Version

Godot4.5

Question

Hi everyone,

I’m struggling with a rendering issue in Godot 4.5. I have a Line2D used as a character tail(I am trying to achieve an effect similar to Madeline’s hair in Celeste). To make its movement independent, I’ve set its top_level property to true.

I want to add an outline to this tail using a CanvasGroup with a shader, but the CanvasGroup seems to ignore the Line2D entirely.

The Setup:

  • Player (CharacterBody2D)

    • CanvasGroup (Has an outline shader)

      • Line2D (top_level = true, scripted to update points in global space)

The Issue: Even though the Line2D is a child of the CanvasGroup in the scene tree, the CanvasGroup does not seem to “capture” it.

If I apply a simple shader to the CanvasGroup (like COLOR = vec4(1,0,0,1)), the Line2D remains its original color and doesn’t get affected.

What I’ve tried:

  • Toggling top_level on the CanvasGroup itself.

  • Increasing Fit Margin to a very high value.

Question: Is there a way to force a CanvasGroup to include and render a top_level child? Or is this a known limitation of how CanvasGroup calculates its bounding box?

If this isn’t possible, what would be the best alternative to apply a screen-space outline shader to a node that needs to stay independent of its parent’s transform?

Thanks for the help!

top_level setting is usually the last resort for me, rarely there is a true need for it, especially because it has its drawbacks.

In your case, I don’t really see the reason why you’d need to have it as top_level. If this is meant to be a character’s tail - it should be attached to the character. What’s wrong with that?

I hope the tail appears to be dragged by the character. If the tail inherits the coordinates of the character, then the tail will remain stationary when the character moves, which looks very strange and stiff. Perhaps you could take a look at Madeline’s hair in Celeste.

I’m a new user so I can’t attach a video, sorry about that.

wait I can do it

Isn’t one end of the tail attached to the body though? Then it’s your anchor point, which always stays in the same place, and you just calculate all the other points of the Line2D.

I don’t know how you exactly calculate the tail movement, so I quickly scribbled my own just to showcase something that you can do it without the top_level.

extends Line2D


var idle_points: PackedVector2Array
var last_position: Vector2


func _ready() -> void:
	idle_points = points


func _process(delta: float) -> void:
	var movement: Vector2 = global_position - last_position
	var cumm_movement: Vector2 = Vector2.ZERO
	for index in points.size():
		cumm_movement = movement
		points[index] -= cumm_movement * (index / float(points.size()))
		points[index] = lerp(points[index], idle_points[index], delta * 1.0)
		
	last_position = global_position


OK, I see. Actually I can just calculate the relative position instead of setting it as top_level. Thank you for inspiration!
Still wondering, whether the CanvasGroup could possibly capture a child with top_level?

1 Like

Yes, that was exactly my whole point.

Making something top_level basically disconnects it from its parent in many ways. Even something as simple as modulate of the parent stops being distributed to the child when you turn on top_level, so I don’t think this can or should be done this way.