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?
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.
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?
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.