Hi, Learning this PathFollow 2D tutorial Enemies Follow a Path & Spawn

Copy/Paste codes to 3D works except the new spawn object just show up and not moving

How to fix this code to work for 3D? Thanks

Tree: Spatial -Path --PathFollow

Path Code:

extends Path

var timer = 0
var spawnTime = 1
var follower = preload("res://PathFollow.tscn")

func _process(delta):
	timer = timer + delta
	
	if (timer > spawnTime):
		var newFollower = follower.instance()
		add_child(newFollower)
		timer = 0

PathFollow Code:

extends PathFollow

export var runSpeed = 0.5

func _process(delta):
	set_offset(get_offset() + runSpeed * delta)
	
	if(loop == false and get_unit_offset() == 1):
		queue_free()

I would try adding print(get_offset()) right after line 6 in the path follow, as then you can see if the offset is being set correctly. I think what might be going on is either the call to set_offset isn't working for some reason, or the value change is so small that it is moving at a crawl. Either way, printing the value should hopefully help shed some light on what might be going on.

Yea I created a new project and copy/paste the codes from this post and it worked..lol

Another thing, can I have 2 or more path and have the original obj randomly continue to different path everytime it reaches the end? How would the code be?, Thanks

You could use multiple paths and PathFollow nodes and have them loosely linked together in code, similar to the approach I described in this thread.

That is the thread that I've been looking as well but as a total beginner and not seeing his setup it is kind of difficult to "see" where/how to put the codes.

If you don't mind to walk me over with your code on my simple tree setup (or should I make a new thread?)

My simple tree: Spatial -Path --PathFollow ---MeshInstance -Path2 Camera

Your code:

# In the Path3D node
# =================
extends Path3D

var path_follow : PathFollow
# a reference to the next Path3D
# you will need to connect them in the editor if using an exported NodePath like below
export (NodePath) var next_path_nodepath = null
var next_path = null

func _ready():
    path_follow = get_node("PathFollow")
    if (next_path_nodepath != null):
        next_path = get_node(next_path_nodepath)
# =================

# In the train
# ==========
var current_path = null
const MOVE_SPEED = 2.0

# You'll need go somehow, depending on your project, get the first path that you start on and assign it to current path.

func _process(delta):
    # example - move the train by moving the path follow
    if (current_path != null):
        # go down the path:
        current_path.path_follow.offset += delta * MOVE_SPEED
        # move to the path follow position
        global_transform.origin = current_path.path_follow.global_transform.origin

        # are we at the end of the track?
        if (current_path.path_follow.unit_offset >= 1):
            # is there another path?
            if (current_path.next_path != null):
                # set that as the current path (assumes a path ends exactly where a new one starts
                current_path = current_path.next_path
                # set the path follow to the start
                current_path.path_follow.unit_offset = 0
                # move to the start position
                global_transform.origin = current_path.path_follow.global_transform.origin
            # if there is not another path, then just stop moving
            else:
                current_path = null
# ==========

-"Path3D" script would be attached to Path and "Train" would be attached to PathFollow? -How would the script change to work for my tree setup?

Thanks

@Gowydot said: That is the thread that I've been looking as well but as a total beginner and not seeing his setup it is kind of difficult to "see" where/how to put the codes.

If you don't mind to walk me over with your code on my simple tree setup (or should I make a new thread?)

Sure. The discussion can be on this thread or a new one, whatever works best for you works for me!

My simple tree: Spatial -Path --PathFollow ---MeshInstance -Path2 Camera

-"Path3D" script would be attached to Path and "Train" would be attached to PathFollow? -How would the script change to work for my tree setup?

Thanks

The code I wrote would need to be attached to each Path, so Path and Path2. This would be the code from lines 1 to 15. Both Path nodes will need this script, as well as both path nodes needing a PathFollow node.

Once you have the scripts attached to the Path nodes, you will need to go to the Godot editor and select the node. In the inspector there should be a new NodePath variable called next path nodepath, which you will need to assign. Since you have two paths, you will likely want to assign the next path in Path to Path2 and then the next path in Path2 to Path, making a loop.

Then, you need to have a Spatial node that is a child of your root, but not a child of either Path node. This will be the "Train" and will need the code from lines 17 to 45. This "Train" spatial will be the visuals that actually follow the path, so to visualize it you will likely want to put a MeshInstance on it so you can see it during runtime.

Before the code will work however, you will need to assign current_path to the first Path node, so you will want to do something like this in _ready in the train script:

func _ready():
	current_path = get_parent().get_node("Path")

Then the code should work, in that it will go down the first path, and after reaching the end, will go to the next path.

Got it working finally! Thank you!

Although how can I make the mesh(under KinematicBody) to rotate along the path like when it was under pathfollow?

@Gowydot said: Got it working finally! Thank you!

Although how can I make the mesh(under KinematicBody) to rotate along the path like when it was under pathfollow?

Awesome! I’m glad you got it working!

For the rotation, I think you can do something like this in the “train”, right after line 30 (where it sets the global transform origin):

# line 30:
global_transform.origin = current_path.path_follow.global_transform.origin
# new line
rotation = current_path.path_follow.rotation

Thank you so much for your help!

A little question (last one I swear lol). Can this pathfollow "train" be stopped and continued, while on the path, by other control (such as a traffic light for example, but I plan to work on it much later)

Yeah, stopping and starting the "train" should be easy. Line 28 (current_path.path_follow.offset += delta * MOVE_SPEED) is what moves it down the path, so adding a condition before it should allow you to control whether it's moving or not.

20 days later

Ok so I thought I got this Pathfollow 3D thing but after a few failed tests I end up with more questions.

1)I stop/start train by adding an Area then signal Move_Speed to 0 when body entered . But when trying to make it slow down to stop with this code:

func _on_Area_body_entered(body):
	MOVE_SPEED = lerp(5, 0, 0.9)

Why does this just slow it down and not stop?

2)How do you add more trains so they would follow one another? I've tried duplicate it/attach new copied script with different speed but the duplicated train keep disappear after a loop or two

==I've also found another Pathfollow method that I want to test in 3D but I got stuck trying to understand it. Please have a look at links

reddit tread short video

3) On StationEntrance script (0:34), Please help me figure out the end part of emit_signal code

Thank you

Lerp is just like an average. So if you did this:

lerp(10, 0, 0.8)

The result would be 2 (80% from 10 to 0), and always 2, never changing.

An easy way to slow things down would be to do this:

MOVE_SPEED *= 0.98

This acts like a simple version of friction. You can adjust the number to match what you want (always less than 1, so you could try 0.99 or 0.97 or even 0.995 or whatever).

@Gowydot said: Ok so I thought I got this Pathfollow 3D thing but after a few failed tests I end up with more questions.

1)I stop/start train by adding an Area then signal Move_Speed to 0 when body entered . But when trying to make it slow down to stop with this code:

func _on_Area_body_entered(body):
	MOVE_SPEED = lerp(5, 0, 0.9)

Why does this just slow it down and not stop?

See @cybereality's answer, as it explains the situation really well and provides a solution :smile:

2)How do you add more trains so they would follow one another? I've tried duplicate it/attach new copied script with different speed but the duplicated train keep disappear after a loop or two

You'll need to adjust the script so each train has it's own PathFollow3D node, OR you will need to have each train cache the current_path.path_follow.offset variable. Here's an example for the caching:

# Add around line 4:
var cache_offset = 0

# Replace line 28 (in _process) with the following:
var cache_offset += delta * MOVE_SPEED
current_path.path_follow.offset = cache_offset

# Around line 41
cache_offset = 0

Then each train will track it's own offset on the path, which should allow you to have multiple at once.

==I've also found another Pathfollow method that I want to test in 3D but I got stuck trying to understand it. Please have a look at links

reddit tread short video

3) On StationEntrance script (0:34), Please help me figure out the end part of emit_signal code

Thank you

Not sure I understand the issue, sorry. Can you share the code you are using or explain exactly what you are looking to do? If you want to emit a signal, you can use something like emit_signal("Signal_Name_Here") in your code.

Thanks all for answering. I would like to explain a little clearer on what I aim to do.

This Pathfollow setup would be like a street traffic system ("Trains" are cars). A street light would enable an Area collision which when the car hit it will slowly come to stop. A Timer will then disable this Area collision and cars would move again. (Each car would also be able to detect eachother <= but I haven't got around to try this part yet)

1) This is a traffic light stop part. I've tried MOVE_SPEED *= 0.98 (and lerp) but result is just slow down but not to a stop. I could only changed to =0 to make it completely stop

2) This code adjustment works and I got multiple cars to run on same path. But then when one car hit the traffic Area all of them would stop (even though I tried separate the script / connect signal to each of them)

Overall, now I am not sure if using Area to signal change to car's move_speed is the way to go?

3) Part of his "StationEnterance" code is not showing, I'm trying to figure it out. (at StationEnterance's emit_signal part) =also pls ignore the wrong Entrance spelling lol=

Rail -Path_1 --Train -Path_2 --StationEnterance --StationExit

Rail: extends Node2D


func _on_changeTrainRail(rail,train):
	var newRail:Path2D = get_node("Path_"+String(rail))
	var newOffset = newRail.curve.get_closet_offset(train.get_node("KinematicBody2D").global.position-newRail.global_position)
	train.get_parent().remove_child(train)
	newRail.add_child(train)
	train.offset = newOffset

StationEnterance: extends Area2D


export(int) var STATION_ID
signal changeTrainRail

func _ready():
	randomize()

func _on_StationEnterance_body_entered(body):
	if !body.get_parent().atStation:
		if randf()>0.5:
			body.get_parent().atStation = true
			emit_signal("changeTrainRail",STATION_ID,body.get_pa  <== Missing part
			print("arriving at station")
		else:
			print("ignoring station")

@Gowydot said: Thanks all for answering. I would like to explain a little clearer on what I aim to do.

This Pathfollow setup would be like a street traffic system ("Trains" are cars). A street light would enable an Area collision which when the car hit it will slowly come to stop. A Timer will then disable this Area collision and cars would move again. (Each car would also be able to detect eachother <= but I haven't got around to try this part yet)

1) This is a traffic light stop part. I've tried MOVE_SPEED *= 0.98 (and lerp) but result is just slow down but not to a stop. I could only changed to =0 to make it completely stop

Maybe try something like this:

MOVE_SPEED *= 0.95
if (MOVE_SPEED <= 0.1):
	MOVE_SPEED = 0

MOVE_SPEED will need to be a variable (var) and not a constant (const) for it to work. I don't think Godot will let you modify a constant, but it just occurred to me that it could be contributing to the issue.

2) This code adjustment works and I got multiple cars to run on same path. But then when one car hit the traffic Area all of them would stop (even though I tried separate the script / connect signal to each of them)

Overall, now I am not sure if using Area to signal change to car's move_speed is the way to go?

Hmm, strange. Is the signal connected to all of them? It could be that the signal is being emitted and sent to all the trains, rather than just the one that collided. A simple way to fix this is to pass the train that collided as well and then check it in a condition. Something like this, for example:

# =========
# on the area
signal on_car_stop(car_to_stop)

func on_body_enter(other):
	emit_signal("on_car_stop", other)
# =========

# =========
# On the car
# (the function connected to the signal)
func on_car_stop(car_to_stop):
	if car_to_stop != self:
		return
	else:
		print ("Stop the car!")
# =========

The other way to handle it, which might be slightly more efficient, is to have the area call a function on the car when it collides. Something like this:

# =========
# on the area

func on_body_enter(other):
	if other.has_method("on_car_stop"):
		other.on_car_stop()
# =========

# =========
# On the car
func on_car_stop():
	print ("Stop the car!")
# =========

3) Part of his "StationEnterance" code is not showing, I'm trying to figure it out. (at StationEnterance's emit_signal part) =also pls ignore the wrong Entrance spelling lol=

What I might try doing is calling a function directly instead of emitting a signal, since you have a reference to the train/car you want to change. Something like this, for example:

# =========
# Rail (no changes needed, I think)
func _on_changeTrainRail(rail,train):
	var newRail:Path2D = get_node("Path_"+String(rail))
	var newOffset = newRail.curve.get_closet_offset(train.get_node("KinematicBody2D").global.position-newRail.global_position)
	train.get_parent().remove_child(train)
	newRail.add_child(train)
	train.offset = newOffset
# =========

# =========
# StationEnterance
func _on_StationEnterance_body_entered(body):
	if !body.get_parent().atStation:
		if randf()>0.5:
			body.get_parent().atStation = true
			# (I'm assuming the body.get_parent() is the rail. If not, you will need a reference to the rail and then can replace "body" with the rail reference)
			body.get_parent().__on_changeTrainRail(STATION_ID, body.get_parent())
			print("arriving at station")
		else:
			print("ignoring station")
# =========

I'm not totally sure it will work though, but that is what I would try.

1) I've already changed const to var ('cause Godot won't let me modify const) though I'm still unable to make it slow down to stop. Is it because on_Area_Enter only activates move_speed only once per contact? I notice the car changes speed down each time it touches the Area

2) I got it working with the first example code so thank you so much!

3) I've also figured out the Reddit's random path2d switch code and able to convert to 3d. I will link the example files for ppl who might be interested (I need to register github first lol) here: https://github.com/j3du/Godot-PathFollow-2D-3D-Random-Path-Switch

@Gowydot said: 1) I've already changed const to var ('cause Godot won't let me modify const) though I'm still unable to make it slow down to stop. Is it because on_Area_Enter only activates move_speed only once per contact? I notice the car changes speed down each time it touches the Area

Ah, that might be it. If the code is in the on_Area_Enter, then it would only be called on the first overlap with the car/train. I would make a boolean and then set it to true once the body has touched the area, and then in _process reduce the movement speed until it's close to zero.

2) I got it working with the first example code so thank you so much! 3) I've also figured out the Reddit's random path2d switch code and able to convert to 3d. I will link the example files for ppl who might be interested (I need to register github first lol)

Awesome :smile:

I would make a boolean and then set it to true once the body has touched the area, and then in _process reduce the movement speed until it's close to zero.

I'm sorry, could you show me how it's done on my code?

Traffic Area

extends Area

func _on_body_entered(other):
	emit_signal("on_car_stop", other)
func _on_body_exited(other):
	emit_signal("on_car_start", other)

Car

extends KinematicBody

var current_path = null
export var MOVE_SPEED = 5
var cache_offset = 0

func _ready():
	current_path = get_parent().get_node("Path")

func _process(delta):
	
	if (current_path != null):
		cache_offset += delta * MOVE_SPEED
		current_path.path_follow.offset = cache_offset
		global_transform.origin = current_path.path_follow.global_transform.origin
		rotation = current_path.path_follow.rotation

		if (current_path.path_follow.unit_offset >= .99):
			if (current_path.next_path != null):
				current_path = current_path.next_path
				current_path.path_follow.unit_offset = 0
				global_transform.origin = current_path.path_follow.global_transform.origin
			else:
				current_path = null
			cache_offset = 0
		
func _on_car_stop(car_to_stop):
	if car_to_stop != self:
		return
	else:
		print ("Stop the car!")
		#MOVE_SPEED = 0

func _on_car_start(car_to_start):
	if car_to_start != self:
		return
	else:
		print ("Start the car!")
		MOVE_SPEED = 5

@Gowydot said:

I would make a boolean and then set it to true once the body has touched the area, and then in _process reduce the movement speed until it's close to zero.

I'm sorry, could you show me how it's done on my code?

Sure! Try something like this in the car script:

extends KinematicBody

var current_path = null
export var MOVE_SPEED = 5
var cache_offset = 0
# New boolean to track if we need to stop the car
var slow_to_stop = false

func _ready():
	current_path = get_parent().get_node("Path")
	

func _process(delta):
	if (current_path != null):
		cache_offset += delta * MOVE_SPEED
		current_path.path_follow.offset = cache_offset
		global_transform.origin = current_path.path_follow.global_transform.origin
		rotation = current_path.path_follow.rotation
		
		if (current_path.path_follow.unit_offset >= .99):
			if (current_path.next_path != null):
				current_path = current_path.next_path
				current_path.path_follow.unit_offset = 0
				global_transform.origin = current_path.path_follow.global_transform.origin
			else:
				current_path = null
			cache_offset = 0
	# New code - slows the car to a stop
	if (slow_to_stop == true):
		MOVE_SPEED = MOVE_SPEED * 0.98
		if (MOVE_SPEED <= 0.1):
			MOVE_SPEED = 0

func _on_car_stop(car_to_stop):
	if car_to_stop != self:
		return
	else:
		print ("Stop the car!")
		# Tell the car to start slowing down
		slow_to_stop = true

func _on_car_start(car_to_start):
	if car_to_start != self:
		return
	else:
		print ("Start the car!")
		MOVE_SPEED = 5
		# Tell the car to stop slowing down
		slow_to_stop = false

Thank you! It works. ( I see now what I did wrong was trying to put MOVE_SPEED code in if (current_path != null):)