-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLightningBolt.lua
More file actions
218 lines (175 loc) · 9.81 KB
/
LightningBolt.lua
File metadata and controls
218 lines (175 loc) · 9.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
--Procedural Lightning Module. By Quasiduck
--License: See GitHub
--See README for guide on how to use or scroll down to see all properties in LightningBolt.new
--All properties update in real-time except PartCount which requires a new LightningBolt to change
--i.e. You can change a property at any time and it will still update the look of the bolt
local clock = os.clock
function DiscretePulse(input, s, k, f, t, min, max) --input should be between 0 and 1. See https://www.desmos.com/calculator/hg5h4fpfim for demonstration.
return math.clamp( (k)/(2*f) - math.abs( (input - t*s + 0.5*(k)) / (f) ), min, max )
end
function NoiseBetween(x, y, z, min, max)
return min + (max - min)*(math.noise(x, y, z) + 0.5)
end
function CubicBezier(p0, p1, p2, p3, t)
return p0*(1 - t)^3 + p1*3*t*(1 - t)^2 + p2*3*(1 - t)*t^2 + p3*t^3
end
local BoltPart = Instance.new("Part")
BoltPart.TopSurface, BoltPart.BottomSurface = 0, 0
BoltPart.Anchored, BoltPart.CanCollide = true, false
BoltPart.Shape = "Cylinder"
BoltPart.Name = "BoltPart"
BoltPart.Material = Enum.Material.Neon
BoltPart.Color = Color3.new(1, 1, 1)
BoltPart.Transparency = 1
local PARTS_IN_CACHE = 2000 --Recommend setting higher if you intend to use LightningSparks
local PartCache = require(script.Parent.PartCache:WaitForChild("PartCache"))
local LightningCache = PartCache.new(BoltPart, PARTS_IN_CACHE)
LightningCache:SetCacheParent(workspace.CurrentCamera)
local rng = Random.new()
local xInverse = CFrame.lookAt(Vector3.new(), Vector3.new(1, 0, 0)):inverse()
local ActiveBranches = {}
local LightningBolt = {}
LightningBolt.__index = LightningBolt
--Small tip: You don't need to use actual Roblox Attachments below. You can also create "fake" ones as follows:
--[[
local A1, A2 = {}, {}
A1.WorldPosition, A1.WorldAxis = chosenPos1, chosenAxis1
A2.WorldPosition, A2.WorldAxis = chosenPos2, chosenAxis2
local NewBolt = LightningBolt.new(A1, A2, 40)
--]]
function LightningBolt.new(Attachment0, Attachment1, PartCount)
local self = setmetatable({}, LightningBolt)
--Main (default) Properties--
--Bolt Appearance Properties--
self.Enabled = true --Hides bolt without destroying any parts when false
self.Attachment0, self.Attachment1 = Attachment0, Attachment1 --Bolt originates from Attachment0 and ends at Attachment1
self.CurveSize0, self.CurveSize1 = 0, 0 --Works similarly to beams. See https://dk135eecbplh9.cloudfront.net/assets/blt160ad3fdeadd4ff2/BeamCurve1.png
self.MinRadius, self.MaxRadius = 0, 2.4 --Governs the amplitude of fluctuations throughout the bolt
self.Frequency = 1 --Governs the frequency of fluctuations throughout the bolt. Lower this to remove jittery-looking lightning
self.AnimationSpeed = 7 --Governs how fast the bolt oscillates (i.e. how fast the fluctuating wave travels along bolt)
self.Thickness = 1 --The thickness of the bolt
self.MinThicknessMultiplier, self.MaxThicknessMultiplier = 0.2, 1 --Multiplies Thickness value by a fluctuating random value between MinThicknessMultiplier and MaxThicknessMultiplier along the Bolt
--Bolt Kinetic Properties--
--Allows for fading in (or out) of the bolt with time. Can also create a "projectile" bolt
--Recommend setting AnimationSpeed to 0 if used as projectile (for better aesthetics)
--Works by passing a "wave" function which travels from left to right where the wave height represents opacity (opacity being 1 - Transparency)
--See https://www.desmos.com/calculator/hg5h4fpfim to help customise the shape of the wave with the below properties
self.MinTransparency, self.MaxTransparency = 0, 1 --See https://www.desmos.com/calculator/hg5h4fpfim
self.PulseSpeed = 2 --Bolt arrives at Attachment1 1/PulseSpeed seconds later. See https://www.desmos.com/calculator/hg5h4fpfim
self.PulseLength = 1000000 --See https://www.desmos.com/calculator/hg5h4fpfim
self.FadeLength = 0.2 --See https://www.desmos.com/calculator/hg5h4fpfim
self.ContractFrom = 0.5 --Parts shorten or grow once their Transparency exceeds this value. Set to a value above 1 to turn effect off. See https://imgur.com/OChA441
--Bolt Color Properties--
self.Color = Color3.new(1, 1, 1) --Can be a Color3 or ColorSequence
self.ColorOffsetSpeed = 3 --Sets speed at which ColorSequence travels along Bolt
--
self.Parts = {} --The BoltParts which make up the Bolt
local a0, a1 = Attachment0, Attachment1
local parent = workspace.CurrentCamera
local p0, p1, p2, p3 = a0.WorldPosition, a0.WorldPosition + a0.WorldAxis*self.CurveSize0, a1.WorldPosition - a1.WorldAxis*self.CurveSize1, a1.WorldPosition
local PrevPoint, bezier0 = p0, p0
local MainBranchN = PartCount or 30
for i = 1, MainBranchN do
local t1 = i/MainBranchN
local bezier1 = CubicBezier(p0, p1, p2, p3, t1)
local NextPoint = i ~= MainBranchN and (CFrame.lookAt(bezier0, bezier1)).Position or bezier1
local BPart = LightningCache:GetPart()
BPart.Size = Vector3.new((NextPoint - PrevPoint).Magnitude, 0, 0)
BPart.CFrame = CFrame.lookAt(0.5*(PrevPoint + NextPoint), NextPoint)*xInverse
BPart.Locked, BPart.CastShadow = true, false
self.Parts[i] = BPart
PrevPoint, bezier0 = NextPoint, bezier1
end
self.PartsHidden = false
self.DisabledTransparency = 1
self.StartT = clock()
self.RanNum = math.random()*100
self.RefIndex = #ActiveBranches + 1
ActiveBranches[self.RefIndex] = self
return self
end
function LightningBolt:Destroy()
ActiveBranches[self.RefIndex] = nil
for i = 1, #self.Parts do
LightningCache:ReturnPart(self.Parts[i])
--if i%100 == 0 then wait() end
end
self = nil
end
local offsetAngle = math.cos(math.rad(90))
game:GetService("RunService").Heartbeat:Connect(function ()
for _, ThisBranch in pairs(ActiveBranches) do
if ThisBranch.Enabled == true then
ThisBranch.PartsHidden = false
local MinOpa, MaxOpa = 1 - ThisBranch.MaxTransparency, 1 - ThisBranch.MinTransparency
local MinRadius, MaxRadius = ThisBranch.MinRadius, ThisBranch.MaxRadius
local thickness = ThisBranch.Thickness
local Parts = ThisBranch.Parts
local PartsN = #Parts
local RanNum = ThisBranch.RanNum
local StartT = ThisBranch.StartT
local spd = ThisBranch.AnimationSpeed
local freq = ThisBranch.Frequency
local MinThick, MaxThick = ThisBranch.MinThicknessMultiplier, ThisBranch.MaxThicknessMultiplier
local a0, a1, CurveSize0, CurveSize1 = ThisBranch.Attachment0, ThisBranch.Attachment1, ThisBranch.CurveSize0, ThisBranch.CurveSize1
local p0, p1, p2, p3 = a0.WorldPosition, a0.WorldPosition + a0.WorldAxis*CurveSize0, a1.WorldPosition - a1.WorldAxis*CurveSize1, a1.WorldPosition
local timePassed = clock() - StartT
local PulseLength, PulseSpeed, FadeLength = ThisBranch.PulseLength, ThisBranch.PulseSpeed, ThisBranch.FadeLength
local Color = ThisBranch.Color
local ColorOffsetSpeed = ThisBranch.ColorOffsetSpeed
local contractf = 1 - ThisBranch.ContractFrom
local PrevPoint, bezier0 = p0, p0
if timePassed < (PulseLength + 1) / PulseSpeed then
for i = 1, PartsN do
--local spd = NoiseBetween(i/PartsN, 1.5, 0.1*i/PartsN, -MinAnimationSpeed, MaxAnimationSpeed) --Can enable to have an alternative animation which doesn't shift the noisy lightning "Texture" along the bolt
local BPart = Parts[i]
local t1 = i/PartsN
local Opacity = DiscretePulse(t1, PulseSpeed, PulseLength, FadeLength, timePassed, MinOpa, MaxOpa)
local bezier1 = CubicBezier(p0, p1, p2, p3, t1)
local time = -timePassed --minus to ensure bolt waves travel from a0 to a1
local input, input2 = (spd*time) + freq*10*t1 - 0.2 + RanNum*4, 5*((spd*0.01*time) / 10 + freq*t1) + RanNum*4
local noise0 = NoiseBetween(5*input, 1.5, 5*0.2*input2, 0, 0.1*2*math.pi) + NoiseBetween(0.5*input, 1.5, 0.5*0.2*input2, 0, 0.9*2*math.pi)
local noise1 = NoiseBetween(3.4, input2, input, MinRadius, MaxRadius)*math.exp(-5000*(t1 - 0.5)^10)
local thicknessNoise = NoiseBetween(2.3, input2, input, MinThick, MaxThick)
local NextPoint = i ~= PartsN and (CFrame.new(bezier0, bezier1)*CFrame.Angles(0, 0, noise0)*CFrame.Angles(math.acos(math.clamp(NoiseBetween(input2, input, 2.7, offsetAngle, 1), -1, 1)), 0, 0)*CFrame.new(0, 0, -noise1)).Position or bezier1
if Opacity > contractf then
BPart.Size = Vector3.new((NextPoint - PrevPoint).Magnitude, thickness*thicknessNoise*Opacity, thickness*thicknessNoise*Opacity)
BPart.CFrame = CFrame.lookAt(0.5*(PrevPoint + NextPoint), NextPoint)*xInverse
BPart.Transparency = 1 - Opacity
elseif Opacity > contractf - 1/(PartsN*FadeLength) then
local interp = (1 - (Opacity - (contractf - 1/(PartsN*FadeLength)))*PartsN*FadeLength)*(t1 < timePassed*PulseSpeed - 0.5*PulseLength and 1 or -1)
BPart.Size = Vector3.new((1 - math.abs(interp))*(NextPoint - PrevPoint).Magnitude, thickness*thicknessNoise*Opacity, thickness*thicknessNoise*Opacity)
BPart.CFrame = CFrame.lookAt(PrevPoint + (NextPoint - PrevPoint)*(math.max(0, interp) + 0.5*(1 - math.abs(interp))), NextPoint)*xInverse
BPart.Transparency = 1 - Opacity
else
BPart.Transparency = 1
end
if typeof(Color) == "Color3" then
BPart.Color = Color
else --ColorSequence
t1 = (RanNum + t1 - timePassed*ColorOffsetSpeed)%1
local keypoints = Color.Keypoints
for i = 1, #keypoints - 1 do --convert colorsequence onto lightning
if keypoints[i].Time < t1 and t1 < keypoints[i+1].Time then
BPart.Color = keypoints[i].Value:lerp(keypoints[i+1].Value, (t1 - keypoints[i].Time)/(keypoints[i+1].Time - keypoints[i].Time))
break
end
end
end
PrevPoint, bezier0 = NextPoint, bezier1
end
else
ThisBranch:Destroy()
end
else --Enabled = false
if ThisBranch.PartsHidden == false then
ThisBranch.PartsHidden = true
local datr = ThisBranch.DisabledTransparency
for i = 1, #ThisBranch.Parts do
ThisBranch.Parts[i].Transparency = datr
end
end
end
end
end)
return LightningBolt