Wanted to share a "follow the leader" caterpillar script I made

I made a little follow the leader code for something and I wanted to share what I did so others don’t need to suffer. No one is probably gonna use it but imma put it out here in case anyone in the future needs reference.

function followTheLeader(dt, url, DistnSpeed, leaderObj, trainList)
	--url, url of GAMEOBJECT (not script)
	--DistnSpeed, table of maxDist, minDist, and maxSpeed
	--leaderObj, if it moves we all move. Url to this object.
	--trainList, list of party members (object urls), discluding leader

	local tl = trainList --this is like this bc its taken from another code snippet that needed me to do this
	local lo = leaderObj

     --finds out where the object is in the train. Compares this object's url to all the urls in the trainlist
	local index = 0
	for i, follower in ipairs(tl) do
		if url == follower then index = i end
	end

	if index == 0 then --[[print("ur not invited to the party lmao loser");]] return end

	local toFollow = lo
	if index > 1 then toFollow = tl[index - 1] end -- if not the second item in the train, follow the item behind it (second object follows leader)

	local tfp = go.get_position(toFollow) --tfp = position of object being followed

	local dns = DistnSpeed --reduntant code bc i didnt wanna write this variable multiple times lol
	local speed = 350

	timer.delay(dt + 0.01, false, function() --this was ran in a program running at 30fps, if you have an issue feel free to change the offset
		local tfn = go.get_position(toFollow) --tfn = new position of object being followed
		local timedist = tfn - tfp; --distance between the old and new positions of the old object. This is to detect if there was movement.
		if timedist ~= vmath.vector3(0) then -- if there was movement then...
		local myp = go.get_position() -- get object pos
		local newdist = tfn - myp --calculate distance vector
		local intdist = math.sqrt((myp.x - tfn.x)^2 + (myp.y - tfn.y)^2) -- find absolute distance using the trusty old pythagorean theorem
			if intdist < dns.minDist then -- slow down if too close
				speed = speed * intdist/dns.minDist
			elseif intdist > dns.maxDist then --speed up if too far
				speed = speed * (intdist/dns.maxDist)
			end
			if dns.maxSpeed then
				speed = math.min(speed, dns.maxSpeed) -- this is for the slow pokes out there
			end
			newdist = vmath.normalize(newdist) * dt * speed --normalize distance vector and multiply by speed and dt
			go.set(".", "position.x", myp.x + newdist.x)
			go.set(".", "position.y", myp.y + newdist.y)
                       --you can just use go.set_position, i didnt here because im using y-axis z sorting
		end
	end)
	
end

Then inside the update functions inside the scripts inside of the objects that are following:

function update(self, dt)
    local leaderObject = msg.url("/leader")
    local trainList = {--[[List of objects. I personally think its better to put this in 
    a module for multiple scripts]]}
	followTheLeader(dt, msg.url("."), {minDist = 150, maxDist = 200 --[[maxSpeed = if you want a max speed]]}, leaderObject, trainList)
end

PS: If you have a slow poke in the middle of the line, it WILL hold up that line!!

6 Likes

Also, if you want the objects to keep moving if they’re too far away from the group, change this slice of code:

timer.delay(dt + 0.01, false, function() --this was ran in a program running at 30fps, if you have an issue feel free to change the offset
		local tfn = go.get_position(toFollow) --tfn = new position of object being followed
		local timedist = tfn - tfp; --distance between the old and new positions of the old object. This is to detect if there was movement.
		if timedist ~= vmath.vector3(0) then -- if there was movement then...
		local myp = go.get_position() -- get object pos
		local newdist = tfn - myp --calculate distance vector
		local intdist = math.sqrt((myp.x - tfn.x)^2 + (myp.y - tfn.y)^2) -- find absolute distance using the trusty old pythagorean theorem
			if intdist < dns.minDist then -- slow down if too close
				speed = speed * intdist/dns.minDist
			elseif intdist > dns.maxDist then --speed up if too far
				speed = speed * (intdist/dns.maxDist)
			end
			if dns.maxSpeed then
				speed = math.min(speed, dns.maxSpeed) -- this is for the slow pokes out there
			end
			newdist = vmath.normalize(newdist) * dt * speed --normalize distance vector and multiply by speed and dt
			go.set(".", "position.x", myp.x + newdist.x)
			go.set(".", "position.y", myp.y + newdist.y)
                       --you can just use go.set_position, i didnt here because im using y-axis z sorting
		end
	end)

To this:

timer.delay(dt + 0.01, false, function() --this was ran in a program running at 30fps, if you have an issue feel free to change the offset
		local tfn = go.get_position(toFollow) --tfn = new position of object being followed
		local timedist = tfn - tfp; --distance between the old and new positions of the old object. This is to detect if there was movement.
		local myp = go.get_position() -- get object pos
		local newdist = tfn - myp --calculate distance vector
		local intdist = math.sqrt((myp.x - tfn.x)^2 + (myp.y - tfn.y)^2) -- find absolute distance using the trusty old pythagorean theorem
		if timedist ~= vmath.vector3(0) or indist > dns.maxDist then -- if there was movement
             -- Or if the distance between this object and its predecessor is too large
			if intdist < dns.minDist then -- slow down if too close
				speed = speed * intdist/dns.minDist
			elseif intdist > dns.maxDist then --speed up if too far
				speed = speed * (intdist/dns.maxDist)
			end
			if dns.maxSpeed then
				speed = math.min(speed, dns.maxSpeed) -- this is for the slow pokes out there
			end
			newdist = vmath.normalize(newdist) * dt * speed --normalize distance vector and multiply by speed and dt
			go.set(".", "position.x", myp.x + newdist.x)
			go.set(".", "position.y", myp.y + newdist.y)
                       --you can just use go.set_position, i didnt here because im using y-axis z sorting
		end
	end)
1 Like

Thanks for sharing it. What’s purpose of putting stuff inside a timer.delay?

Basically, its to wait until the next frame to detect if the leader has moved. Without the modification, they only move when the leader moves. The modification however, has them move when the leader moves OR if they are too far away from the object they’re supposed to be following.

In a later editing I add a “callback” parameter in case you need to do something with the values calculated

One bad thing about this though its that it’s fluid motion, which is good in many circumstances but sometimes its not the best, because things like clipping through player borders on objects may happen if the objects themselves aren’t given collisionobjects, and then giving them collision objects causes them to be stuck in some areas.