Utilizing Line2D to Display Texture Not Working

Godot Version

4.7.stable.flathub [5b4e9cb0f] (being run on Bazzite)

Question

Hello! I am following along with CatlikeCoding’s Godot tutorial, specifically the Conveyor chapter (see: Conveyors - True Top-Down 2D ). In it, instead of using a Sprite2D child node for the sprite, they instead point followers to utilize a Line2D node, setting the Texture to their provided conveyor.png image, and changing the CanvasItem material to be a customized shader to make the image looking like moving arrows.

Unfortunately, while following the tutorial has enabled me to create a functioning, customizable set of conveyors, they are invisible, and after a few hours debugging I am at a loss. Below is what I have in my project at this time - my biggest fear is that its a version issue, as the tutorial chapter is written for Godot 4.5. Is there an obvious area that the issue may be? If there isn’t one, and this simply cannot work on 4.7, is there a suitable alternative that would be recommended?

conveyor struct

Area2D base node, with child Collisionshape2D and Line2D nodes. As displayed in code below, default node has CollisionShape2D and Line2D assigned, as well as Length set to 32 and speed to 30 by base.

CollisionSHape2D is a RectangleShape2D of size (32, 10) and Resource set Local to Scene on.

Line2D has a baseline PackedVector2Array of size 2, with default points both set to (0.0, 0.0). The width is set to 12.0. The Fill → Texture is set to the conveyor.png (a 12x12 pixel file of black + arrow), and the Texture Mode set to Tile. Under the CanvasItem → Material is set to ShaderMaterial, with Shadower conveyor.gdshader, and Local to Scene set to On.

conveyor.gd

@tool
extends Area2D


@export var collision_shape: CollisionShape2D
@export var line: Line2D

@export_range(12, 400) var length := 32 :
	set(new_length):
		length = new_length
		if line and line.points.size() == 2 and line.points[1].x != length:
			line.points[1] = Vector2(length, 0.0)
			collision_shape.position = Vector2(length * 0.5, 0.0)
			var rect := collision_shape.shape as RectangleShape2D
			rect.size = Vector2(length, 12.0)

## Conveyor speed, in pixels per second.
@export_range(0, 60) var speed := 30 :
	set(new_speed):
		speed = new_speed
		if line:
			line.material.set(
					"shader_parameter/speed",
					speed / line.texture.get_size().x
			)



var objects: Array[InteractiveObject] = []

func _physics_process(_delta: float) -> void:
	if objects.is_empty():
		set_physics_process(false)
		return
	
	var push_velocity = transform.x * speed
	for object in objects:
		object.push(push_velocity)

func _on_body_entered(body: Node2D) -> void:
	var object := body as InteractiveObject
	if object:
		objects.push_back(object)
		set_physics_process(true)


func _on_body_exited(body: Node2D) -> void:
	var index_to_remove := objects.find(body as InteractiveObject)
	if index_to_remove >= 0:
		objects.remove_at(index_to_remove)

conveyor.gdshader

shader_type canvas_item;

uniform float speed = 1.0;

varying vec4 vertex_color;

void vertex() {
	vertex_color = COLOR;
}

void fragment() {
	COLOR = texture(TEXTURE, vec2(UV.x - TIME * speed, UV.y));
	COLOR *= vertex_color;
}

Did you also set CanvasItem.texture_repeat?

@renevanderark I somehow had missed that setting. Upon doing so, it does run properly!.. Though, Only if I hard code the Line2D.Points.x values. If, for instance, I hard code the Line2D.Points[1].x value to 24, but in the map scene set the Length of the conveyor to 32, this is what it looks like on the map (the conveyor lines are moving of course).

My next guess would be that the set(new_length) is not properly running? But from looking at it, in theory it SHOULD be executing correctly.

You should not compare a floating point value like the xof a Vector2 using exact operators like == and !=.

Use this function in stead and see if that works for you:
is_equal_approx

that makes:

if line and line.points.size() == 2 and not is_equal_approx(line.points[1].x, length):

@renevanderark I updated the script accordingly, and while the functionality still works (i.e. the length of the actual conveyor updates properly), the actual sprite/visual still does not appear unless I manually hard code in the line.points[1].x

I even tried forcing the line.points[1].x to a different value when something enters the body, and while the conveyor length updated as expected, it remained invisible. It’s like there’s a disconnect between the texture and displaying when the line.points[1].x is updated dynamically. Is there maybe a call to ‘refresh’ the line.fill.texture some way (reaching for straws lol).

I think you could try adding line.queue_redraw()after updating.

I’m sure the author of the tutorial can be reached as well, right? People here on the forum will quickly miss the context, unless they’re going to do the tutorial themselves.

You’re not wrong, I did send out an email to them so awaiting a potential response. However! I did actually find a solution! It’s definitely an issue with versions I think, but basically, bc Line2D stores the point values within a PackedVector2Array, you cannot set the new point value like below:

@export_range(12, 400) var length := 32 :
	set(new_length):
		length = new_length
		if line and line.points.size() == 2 and line.points[1].x != length:
			line.points[1] = Vector2(length, 0.0)
			...

Instead, it needs to be set with the following line:

@export_range(12, 400) var length := 32 :
	set(new_length):
		length = new_length
		if line and line.points.size() == 2 and line.points[1].x != length:
			line.set_point_position(1, Vector2(length, 0.0))
			...

This works, and displays perfectly. Thank you again for helping out, renevanderark!

Did your email just arrive? :slightly_smiling_face:

Yes it did! So I adapted everything, also for other stuff beyond the point of discussion.

By the way: the rule of thumb to not directly compare floats does not apply in this context, because we’re checking if the value has been changed at all. Also, we’re working with ints that we cast to float (length is an int), and the cast always produces the exact same results for the same int.

Sure! Was running blind a bit, so grasping for straws. :wink:

Haha yep! I sent the email out and immediately had found the answer right after.

And thank you again @catlikecoding for the response and updating your tutorials! They’re really well done.