Well yea it’s possible, you just need the core math that makes it all work
I can offer some “bare bones” code that I think is the basis. I did have to think through this for a bit and test to make sure it does what I think it does. Because it does rely on CFrames, because you can use CFrames to offset things relative to a specific orientation.
But again, there are different ways to do a lightning effect, and some don’t require CFrames.
Going back to your image, you have 2 different “lines”. You have a black line which you could call your “reference line” (you call it the raycast, because that’s what it is). But you would call it the reference line because your lightning will be “based off” that black line; It’s all relative to the black line. The red line is your actual lightning, but the way you get it is by taking your “reference line” and applying offsets to specific points on the reference line. It could be a wide arc, it could be random numbers to give it more noise. But they are all offsets at the end of the day. You are just adding an “offset” to your “reference”.
But also, most crucially, your “reference line” could be at any angle, so you can’t always just offset things the normal way. You have to make your offsets in a “transformed space”. That is where CFrames come in. They allow you to move things in a specific reference frame.
So you need to first make a CFrame which is at “point A” and it’s LookVector is pointing towards “point B”
local aimCFrame = CFrame.lookAt(startPos,endPos)
This code produces a CFrame that is pointing towards B, from A.
With this CFrame, you can then apply offsets based on the CFrame’s “RightVector” and “UpVector” which will allow you to create whatever shape you want that follows the “reference line”.
This is some sample code which demonstrates the math:
local startPos = workspace.p1.Position
local endPos = workspace.p2.Position
local distance = (endPos-startPos).Magnitude
function CreatePart(pos)
local p = Instance.new("Part")
p.Parent = workspace
p.Anchored = true
p.Size = Vector3.one
p.Position = pos
end
local aimCFrame = CFrame.lookAt(startPos,endPos)
local sampleReduction = 3
distance = math.floor(distance)/sampleReduction
for i=1, distance do
local t = i/distance
local xOffset = math.sin(t*math.pi)*8 + math.random() * 5
local yOffset = math.random() * 5
local forward = aimCFrame.Position + aimCFrame.LookVector * i * sampleReduction -- determines position of each point along the reference line
local rightShift = aimCFrame.RightVector * xOffset -- horizontal offset
local upShift = aimCFrame.UpVector * yOffset -- vertical offset
CreatePart(forward + rightShift + upShift)
end
You can test it by creating two parts and having this script refer to them as the start position and end position for the lightning. And it will creates new parts that would represent the “vertices” for your lightning. So this could be applied to a series of Beam objects, or you could use parts as the lines that make up the lightning. But tbh, I think it would be easier to use Beam objects.
The last thing that needs to be said about this code is that, if you want to customize the shape of the lightning as it moves from point A to B. You would change the variables “xOffset” and “yOffset” to be set to whatever values you want.
You can see in my case, I used math.sin to produce a “wide arc”, but you could use other math functions to achieve the same thing. And you can also see that I use math.random() to add some noise to the shape as well. But you could use perlin noise if you wanted.
Whatever equation you can think up to get whatever shape you want can be replaced for xOffset and yOffset.
The code below that is necesary for making sure your lightning shapes are offset in the right direction, depending on where A and B are.
And yea, there are other ways this code could be improved as well. As the distance between A and B get’s bigger, it will use more parts. But that might be bad for performance. You you’d want to make it so the points that are used is fixed.