Smooth Afterimage effect on moving part, can't seem to figure it out

First post here, so apologies if anything’s a little rusty!

So I’ve been trying to create a unique afterimage-like effect for a moving, unanchored part. The effect I intended on creating was a group of multiple carbon copy clones of said part that copied every movement and rotation the part made with about a second of delay, in smaller increments for each clone.
I’ve tried using AlignPosition, which did not give the intended effect I was looking for, as it had a lot of drag in its alignment to the main part.

CFrame seems to be what I’m after but I still couldn’t get it to work how I envisioned. The code below almost works for me, but the first clone always renders in front of the the main part for some reason, predicting the movements of the main part in a way.
I’m also not entirely sure how to add delays between the parts properly.

local part = script.Parent

script.Disabled = true

local follower = part:Clone()
follower.Parent = part
follower.Position = part.Position
follower.CanCollide = false
follower.Anchored = true

local follower2 = follower:Clone()
follower2.Parent = part
follower2.Position = part.Position
follower2.CanCollide = false
follower2.Anchored = true

while true do
	follower.CFrame = part.CFrame:ToWorldSpace()
	task.wait(.01)
	follower2.CFrame = follower.CFrame:ToWorldSpace()
end

Any help is super appreciated I’m still learning a lot so there’s something super silly I probably missed haha.

4 Likes

use Heartbeat, save cframe of the part into a table

local history = {}
table.insert(history, part.CFrame)

because the Heartbeat is relatively stable, we can let the follower1 to set its cframe from say 4 beats before

follower1.CFrame = history[#history - 4]
follower2.CFrame = history[#history - 8]

you have to make sure the index is in range of the history

also remove some history from the beginning so the array dont take memory endlessly

3 Likes

I’m not entirely sure I’m doing this correctly but I tried adding your code samples into mine but I got an error at follower.CFrame = history[#history - 4]
I’m also a little new on some terminology, Is the Index the 4? I know the basics of heartbeat too but I am still a little lost.

1 Like

i was not at my computer to check. but i’m trying to record the cframes in array

history = { cframe1, cframe2, cframe3, cframe4, cframe5 } -- for example, after 5 records
print(#history) -- it should print 5
print(#history - 4) -- it should print 1
-- history[#history - 4] = history[1] = 4 cframes before the current time, so
follower.CFrame = history[#history - 4] -- set follower to be the cframe of part in the past
2 Likes
local part = script.Parent
local followers = {}

local followerAmount = 2 -- how much followers you want, recommended max 4-5
local smoothness = 0.8 -- 0.00-1.00, lower = smoother

function createFollower(base) -- creates a follower based on the base part
	local fwr = base:Clone()
	fwr.Anchored = true
	fwr.CanCollide = false
	fwr.Parent = base
	return fwr
end

for i = 1, followerAmount do -- creates the followers and puts them on table
	followers[i] = createFollower((i==1 and part) or followers[i-1])
end

game["Run Service"].Heartbeat:Connect(function(delta)) -- main func
	for index, follower in followers do
		if not (follower:IsA("BasePart") and follower.Parent and follower.Parent:IsA("BasePart")) then -- check if the follower is valid
			followers[index] = nil
			continue
		end
		follower.CFrame = follower.CFrame:Lerp(follower.Parent.CFrame, smoothness) -- smooth effect
	end
end

i recommend running this on a local script
you can also just set an script’s RunContext to client and put it under the part you want

2 Likes

Even with this understanding im still experiencing the error, does it have something to do with the fact the history table is called before any cframes are listed?

  01:19:31.996  Unable to assign property CFrame. CoordinateFrame expected, got nil  -  Server - CFrame:24```
1 Like

yes. because when at first few frames, the table is empty or not enough records,
so history[#history - 4] = history[ <= 0 ] and could spill out errors

we need a check

local previous = 4
if #history > previous then
  follower.CFrame = history[#history - previous]
end
2 Likes

I don’t know what happened but when I tried running this, it crashed my studio, I also had to fix a misstype but now i’m worried to try again aha

1 Like

lol mb, i wrote this in here so i didnt notice.
and i just tested it, it indeed crashes studio :skull:

Is there something I could do to get this to work? Is there some kind of infinite loop crashing Studio?

Could you also explain what’s happening here, particularly the second half, I wanna learn something from this ahah

Sorry for all these messages </3

1 Like

What you see here is what I call a “one-line conditioner”
I’ll explain;
(i==1 and part): this is checking if i is equal to 1, and if yes, it will return part, so the function would be createFollower(part)
But if i is not 1, then it will use followers[i-1], which is just i-1 (a past part that is registered already in the table, i.e. if i is 3 then it will look for followers[2]), which is expressed by the or statement, so the function would be createFollower(followers[i-1])

This is just to compress the code, but something equal to this would be

if i == 1 then
	createFollower(part)
else
	createFollower(i-1)
end
1 Like

Thank you for that explanation, It’s a lot simpler than I thought

Was there no fix to your previous script btw?

1 Like

Yeah! I just found out why, it was because when the part was being cloned, the script would be cloned and it would repeat the entire process, duplicating and well, crashing

Here’s the fixed version

local part = script.Parent
local followers = {}

local followerAmount = 2 -- how much followers you want, recommended max 4-5
local smoothness = 0.6 -- 0.00-1.00, lower = smoother
local cloneTransparency = 0.65 -- more transparency would make it look better

function createFollower(base) -- creates a follower based on the base part
	local fwr = base:Clone()
	fwr:ClearAllChildren()
	fwr.Anchored = true
	fwr.CanCollide = false
	fwr.Transparency = cloneTransparency 
	fwr.Parent = base
	return fwr
end

for i = 1, followerAmount do -- creates the followers and puts them on table
	followers[i] = createFollower((i==1 and part) or followers[i-1])
end

game["Run Service"].Heartbeat:Connect(function(delta) -- main func
	for index, follower in followers do
		if not (follower:IsA("BasePart") and follower.Parent and follower.Parent:IsA("BasePart")) then -- check if the follower is valid
			followers[index] = nil
			continue
		end
		
		follower.CFrame = follower.CFrame:Lerp(follower.Parent.CFrame, smoothness) -- smooth effect
	end
end)

OH, now that you mention it, my original script did crash for a similar reason before added the script.Disabled = true
I’m testing it now and the parts are still kinda rendering infront of the original part, I havent moved to a local script, is that why?

1 Like

I’m going to mark your second script as the solution as you’ve helped me more than I deserve, thank you so so so much for your time.
Before I finish for the night would there be any way to add some delay between the copies?

1 Like

by lowering the smoothness it will simulate more delay.

about this; its not really why but i really really recommend using a local script instead, its as easy as setting a server script’s RunContext to Client. To solve that, just add this line on the createFollower() function;

fwr.Size *= 0.99

Sorry for the Delayed response but I went to bed, I’ve tried using a local script instead, and added that fwr.Size *= 0.99 to the create function but nothing shows up?
I’m also noticing the clones sometimes take shortcuts when trying to copy the movements of the origin part, as in they dont take the direct path it took. I’ve seen something like this in a game before but i;m not sure if its super complicated.

If i’m understanding correctly, theoretically I wanna set the smoothness to 0 so each clone copies the movement of the part exactly, but then I’d have the parts have a delay between when they actually register the now, previous position of the part, but they keep a consistent time delayed between the movement.