Bouncing Lightning Bolts! Grrr >:DDD

Helo, it is I, Physic owo nics and this is my mini presentation for somebody’s question
Made this cuz it’s semi-complicated to explain in Discord and might as well teach other people one way to make lightning!
Not even sure if it belongs in this category of the dev forum lol

Note: this isn’t the only way to make bouncing lightning bolts lol but this is just a tutorial

Note 2: I didn’t actually test this out lol! so don’t be surprised if it doesn’t give an accurate list

Note 3: lol also btw, the lightning bolt generation could use some work cuz it might not be random enough, and yeah I’m too lazy to edit this any farther

What we are going to be doing:

the goal is to make something like uh:

this lol! (please ignore the cute little froggos with guns, I had to put them in the pic cuz they are too yummy) :PP

ok it might look a bit weird because, tbh, it’s an absolutely horrendous example!

So the goal is to make a lightning bolt kinda following the green lines, so it gonna bounce from that wall at the back and walam! it go flying and then djiqWDIJQWIJDqwjijiq babushka!

To achieve this effect we first gotta create a base line for the relative area the lightning points? edges? corners? idk what ever they are, the base line sets the precedent for the lightning to follow. Next is to write the part of the code that actually generates the lightning bolt itself!

Step 1: Write the entire script
Ok! step number 1: copy and paste from the roblox developer wiki [insert troll face here]

local function fireLaser()
-- Set an origin and directional vector
local rayOrigin = caster.Position
local rayDirection = Vector3.new(0, -100, 0)
-- Build a "RaycastParams" object and cast the ray
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {caster.Parent}
raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
local raycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)
if raycastResult then
    local hitPart = raycastResult.Instance
    -- Check if the part resides in a folder, that it's fully visible, and not locked
    if hitPart.Parent == workspace.Tiles and hitPart.Transparency == 0 and not hitPart.Locked then
        hitPart.Locked = true
        -- Position beam ending attachment at hit point
        caster.BeamEndAttachment.Position = Vector3.new(0,hitPart.Position.Y-caster.Position.Y)+raycastResult.Position.Y, 0)
        -- Start beam and burn effect
        caster.Beam.Enabled = true
        caster.BeamEndAttachment.BurnEffect.Enabled = true
        -- After a delay, trigger tile "melt"
        wait(TILE_MELT_DELAY)
        tweenTile(hitPart, 1)
    end
end
-- Trigger next caster cycle
moveCaster()
end

you do not know how painful this is to edit, just look
:C
I hate scripting

oh yeah btw, this is a very important skill - just steal stuff people have already made once you completely know what it does and how it works, you don’t have to use up time to type it all out

Although if you are still confused on how the script entirely works, I highly highly highly highly suggest that you stop and analyze why and how the script works, it is one of the best habits to build and will help you grasp scripting theory faster (idk am just making words up but you get the point)

It like studying beforehand for a math test. Once you know how everything works then it’ll be ez ez ez ez ez ez ez ez ez ez ez ez ez ez ez ez ez ez ez ez ez ez ez ez ez ez ez…

Anyways back on topic lol, we gotta clean this godforsaken script to make it work for our needs

Gonna put comments here so it easier to understand what I did

local function RIDE_THE_LIGHTNING () -- changed the function name to something better
		
		--[[ for the purposes of this tutorial we will be shooting a lightning bolt, forward, from the player's chest

		No worrys, you can probably use this as reference for your needs once you understand enough about rays]]
		
		-- also just assume you already have a variable assigned for the character called "Char"
		
		-- oh yeah yeah btw this script only counts one bounce, but you can probably modify it to loop in on itself and bounce around the place multiple times
		
		-- Setting our parameters
		local Juan = Char:WaitForChild("HumanoidRootPart")
		local LightningRange = 69 -- How far do you want the lightning to go?
		
		-- Setting our ray's origin story and direction of plot
		local rayOrigin = Juan.CFrame.p -- p is short for Position, pp is short for Pineapple Pie
		local rayDirection = Juan.CFrame.LookVector * LightningRange -- Because look vectors are unit vectors, we can multiply them by our LightningRange to get the position that is LightningRange far from our origin
		
		-- Setting our ray's params so that it ignores our body
		local raycastParams = RaycastParams.new()
		raycastParams.FilterDescendantsInstances = Char:GetChildren()
		raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
		local raycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)

		-- Getting our results and then doing math with them
		if raycastResult then
			local HitPos, HitNormal = raycastResult.Position, raycastResult.Normal -- Giving the information for the point of the wall that we hit to variables so they can be best buddies C:
			
			local RemainingRange = LightningRange - (HitPos - rayOrigin).Magnitude -- gets the remaining distance the lightning has to travel after hitting the wall 
			local reflectedNormal = (Juan.CFrame.LookVector - (2 * Juan.CFrame.LookVector:Dot(HitNormal) * HitNormal)) -- copy and pasted from "https://medium.com/@sleitnick/roblox-reflecting-rays-548ae88841d5"
			-- reflectedNormal basically gives a unit vector of the reflection. it kinda like .LookVector because it is in unit vector form and we can multiply it by our remaining lightning range to get our final point
			
			-- now because reflectedNormal is pretty much a .LookVector of the reflection, it should work... right????????? idk I've never done this before lol
			local FinalDestination = HitPos + reflectedNormal * RemainingRange -- yay! we got our 3rd and final point
		end
	end

ok this the entire yellow line part of the demonstration above done! now we gotta make the lightning itself, but how do we do that???

ok this might look a bit weird, but bear with me ok C:

furst, the yellow line is a segment of the other yellow line from the other image

so the plan is to divide it into smaller segments and get the positions of their endpoints
and then after we get the positions of the endpoints, we offset them to be away from the center line

but because this is 3d, we have to work with this monstrosity (no pun intended)
circle

ok this is kinda side view of what we are gonna do
the goal is to get the positions of the black boxes (I put 2 so it easier to visualize)

but we have to offset that from our secondary points, the ones we got from dividing the yellow segment into multiple parts

again, because this is fricking 3d QIOJWDIJOQWIOJDWQIOJDiIQ I hate 3d, we need to get the x and y offset relative to the center part to get the positions of our black boxes

using the power of MATH (WOW SO FUN AMIRIGHT XDFXDXDXDXD) all we need are 2 segments of a triangle to get the length of the 3rd one. So so so, pardon me, I’m descending into madness here, utilizing the holy grail of math, THE PYTHAGORIEOM THEORM we can derive the 3rd and final segment! so ez amiright??? chuckles, I’m in danger

so we already have one side, which is our desired range for how far we want our lighting bolt to be away from the center line. One down! ez

next, to get our 2nd side, we can randomize the x segment’s length which would also randomize our 3rd side while still keeping it on the edge of our little circle here.

and here’s the modified formula!

C^2 - A^2 = B^2

now we can just plug in our variables

local DesiredOffset = 4
local XOffset = math.random(DesiredOffset * -1, DesiredOffset)
local YOffset = math.sqrt(DesiredOffset^2 - XOffset^2)

almost done!
now we gotta get position for each vector? edge? idk bro, and plot it all into a table
it’s scripting commentary time!

local function RIDE_THE_LIGHTNING ()
		
		local Juan = Char:WaitForChild("HumanoidRootPart")
		local LightningRange = 69 -- How far do you want the lightning to go?
		
		local rayOrigin = Juan.CFrame.P -- P is short for Position, PP is short for Pineapple Pie
		local rayDirection = Juan.CFrame.LookVector * LightningRange -- Because look vectors are unit vectors, we can multiply them by our LightningRange to get the position that is LightningRange far from our origin
		
		local raycastParams = RaycastParams.new()
		raycastParams.FilterDescendantsInstances = Char:GetChildren()
		raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
		local raycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)

		if raycastResult then
			local HitPos, HitNormal = raycastResult.Position, raycastResult.Normal
			
			local RemainingRange = LightningRange - (HitPos - rayOrigin).Magnitude
			local reflectedNormal = (Juan.CFrame.LookVector - (2 * Juan.CFrame.LookVector:Dot(HitNormal) * HitNormal))
			
			local FinalDestination = HitPos + reflectedNormal * RemainingRange -- ez we got our 3rd and final point
			
			-- Where we left off! <<<<<<<<<<<< HERE :D
			
			local DesiredOffset = 4 -- How far away they are from the center
			local DistBetweenPoints = 4 -- How far away
			
			local Table = {} -- Creating the table of stuff

			Table[#Table + 1] =  Juan.CFrame.P -- Setting the starting point for the lightning bolt!
			-- #Table gives the number of items in the table, if we put a +1 after it, it will give us the table number for an item that isn't in the table yet, allowing us to add a new item at the end of the table! :DD

			local AmountOfVertices = math.floor((HitPos - Juan.CFrame.P).Magnitude/DistBetweenPoints) -- FINALLY I GOT THE WORD, ITS VERTICES!!! OMG OGMOGMGOMG
			-- The above basically divides the yellow segment from the previous pics into semi congruent segments
			for i = 1, AmountOfVertices do
				local XOffset = math.random(DesiredOffset * -1, DesiredOffset)
				local Up = math.random(0, 1) -- If it's 1, then the lightning bolt will go up. 0 makes it go down.

				if Up == 0 then
					Up = -1 -- Sets up to -1 if it was 0, because we can't do math.random(-1, 1) cuz it has 3 outcomes: -1, 0, and 1
				end

				local YOffset = math.sqrt(DesiredOffset^2 - XOffset^2) * Up
				local Reference = CFrame.lookAt(Juan.CFrame.P, Juan.CFrame.LookVector) -- Using this to get the .RightVector and .UpVector to use with the x and y offsets
				Table[#Table + 1] = Juan.CFrame.LookVector* DistBetweenPoints + Reference.RightVector * XOffset + Reference.UpVector * YOffset
			end
			
			Table[#Table + 1] = HitPos -- The position at which where the wall was hit
			
			local AmountOfVertices2 = math.floor((FinalDestination - HitPos).Magnitude/DistBetweenPoints) -- This is the length of the rest of the lightning after being reflected
			
			--just replace HitPos with Final Destination, Juan.CFrame.P with HitPos and Juan.CFrame.LookVector with reflectedNormal
			for i = 1, AmountOfVertices do
				local XOffset = math.random(DesiredOffset * -1, DesiredOffset)
				local Up = math.random(0, 1) -- If it's 1, then the lightning bolt will go up. 0 makes it go down.

				if Up == 0 then
					Up = -1 -- Sets up to -1 if it was 0, because we can't do math.random(-1, 1) cuz it has 3 outcomes: -1, 0, and 1
				end

				local YOffset = math.sqrt(DesiredOffset^2 - XOffset^2) * Up
				local Reference = CFrame.lookAt(HitPos, reflectedNormal) -- Using this to get the .RightVector and .UpVector to use with the x and y offsets
				Table[#Table + 1] = reflectedNormal* DistBetweenPoints + Reference.RightVector * XOffset + Reference.UpVector * YOffset
			end

			Table[#Table + 1] = FinalDestination -- The end!

			return(Table) -- Returning it as a table
			
		else -- In case the lightning isn't gonna bounce off of anything, pretty much the same thing as the above, without counting a reflected segment
			
			local DesiredOffset = 4
			local DistBetweenPoints = 4
			
			
			local Table = {}
			Table[#Table + 1] =  Juan.CFrame.P
			local AmountOfVertices = math.floor((Juan.CFrame.LookVector * LightningRange - Juan.CFrame.P).Magnitude/DistBetweenPoints) -- Just replecing HitPos with Juan.CFrame.LookVector * LightningRange
			for i = 1, AmountOfVertices do
				local XOffset = math.random(DesiredOffset * -1, DesiredOffset)
				local Up = math.random(0, 1) -- If it's 1, then the lightning bolt will go up. 0 makes it go down.

				if Up == 0 then
					Up = -1 -- Sets up to -1 if it was 0, because we can't do math.random(-1, 1) cuz it has 3 outcomes: -1, 0, and 1
				end

				local YOffset = math.sqrt(DesiredOffset^2 - XOffset^2) * Up
				local Reference = CFrame.lookAt(Juan.CFrame.P, Juan.CFrame.LookVector) -- Using this to get the .RightVector and .UpVector to use with the x and y offsets
				Table[#Table + 1] = Juan.CFrame.LookVector* DistBetweenPoints + Reference.RightVector * XOffset + Reference.UpVector * YOffset
			end

			Table[#Table + 1] = Juan.CFrame.LookVector * LightningRange

			return(Table) -- Returning it as a table
		end
	end
	local LightningPositions = RIDE_THE_LIGHTNING() -- AND THEN WALAH! THIS GIVES A TABLE OF POSITIONS WHERE YOU CAN TWEEN A PART WITH A TRAIL TO GO TO!

Ok that all! I know there are ways to make the lightning waay better and more accurate with just a few minor edits, but I’ll leave you to decide if you wanna do that. I made this to kinda show the process behind making a script work, not so much as a walkthrough.

Imagine if the script doesn’t work lol. (again, I didn’t actually test it so it might not xD)

Thank you for attending my UN press conference, it’s ya boii Physic owo nics
~uwu

9 Likes

I think this belongs in the #resources:community-tutorials section. Very, very interesting tutorial, though.

2 Likes

Very interesting. Good nevertheless - And yeah this maybe should belong in community tutorials instead of code review.

1 Like

To be fair, op did specify that the code wasnt tested yet.
Probably does belong in code review. Correct me if I am wrong.

I put in #resources:community-tutorials, I didn’t even know that was a category. Thanks! C:

1 Like

It could do without the excessive flavour text that makes this post slightly unreadable and annoying to go through - not to mention that it’s littered in the code too - but thanks for sharing. It could help for thinking about bouncing or reflecting objects in general.

yeah writing essays is not my specialty