Hello, i want to optimise this explosion script and correct some logic issues. I want the explosion script to be polished and have the capabillity to perform tons of explosions in quick sucsession without exploding my computer
The explosion script works fine but it has alot more room for improvement such as
-performance
-readablilty
-cleaned up logic
-reliability
And as a bonus you can use this script for free!!
The explosion script was made to replace the already existing roblox engine explosion written in (C). The purpose of the script is to give developers way more controll over an explosion which has many good use cases (fighter games, game details, realism, action, just pure fun)
What i am also aiming for is to add explosion FX that is contructed with SpecialMesh and Normal Parts that runs on one client or multiple. Smoke effects would interact with the enviroment using raycasting and Tweening. Some smoke effects could also shape into a mushroom cloud.
--!nocheck
-- ExplosionModule.lua
local ExplosionModule = {}
--// Services
local RunService = game:GetService("RunService")
local Debris = game:GetService("Debris")
local ContentProvider = game:GetService("ContentProvider")
local TweenService = game:GetService("TweenService")
local Lighting = game:GetService("Lighting")
--// Cosmetic folders
local CosmeticFolder = workspace:FindFirstChild("CosmeticWorkspaceFolder") or Instance.new("Folder")
CosmeticFolder.Name = "CosmeticWorkspaceFolder"
CosmeticFolder.Parent = workspace
local ExplosionFXFolder = CosmeticFolder:FindFirstChild("ExplosionFX") or Instance.new("Folder")
ExplosionFXFolder.Name = "ExplosionFX"
ExplosionFXFolder.Parent = CosmeticFolder
--// Shared FX attachment
local FXAttachment = Instance.new("Attachment")
FXAttachment.Name = "XplosionFXAttachment"
FXAttachment.Parent = workspace.Terrain
--// Fast locals
local clamp, max, min, random = math.clamp, math.max, math.min, math.random
local GetPartBoundsInRadius = workspace.GetPartBoundsInRadius
local clone, t_type = table.clone, type
local Stepped = RunService.Stepped
--// Default config
function ExplosionModule.GetDefaultExplosionConfigs()
return {
explosionSound = {
{ id = "3802270141", volume = 1.5, pitch = 1 },
},
explosionRadius = 75,
explosionMaxDamage = 75,
shockwaveSpeed = 210,--96.04,
explosionVelocity = 38,
explosionDestroyVelocity = 30,
explosionCanDestroy = false,
explosionMomentumThreshold = 0.2,
ignoreParts = {},
DEBUG = false,
}
end
--// Merge configs
local function mergeConfigs(defaults, overrides)
if not overrides then return clone(defaults) end
local out = clone(defaults)
for k, v in overrides do
local dv = out[k]
out[k] = (t_type(v) == "table" and t_type(dv) == "table") and mergeConfigs(dv, v) or v
end
return out
end
--// Sound cache
local SoundCache = {}
local function createSound(data)
local s = Instance.new("Sound")
s.SoundId = "rbxassetid://" .. data.id
s.PlaybackSpeed = data.pitch or 1
s.Volume = data.volume or 0.5
s.RollOffMode = Enum.RollOffMode.Inverse
s.Name = tostring(data.id)
return s
end
local function preloadExplosionSounds(cfg)
local list = cfg.explosionSound
if not list or #list == 0 then return end
local preload = table.create(#list)
for _, s in list do
local id = tostring(s.id)
if not SoundCache[id] then
local tmpl = createSound(s)
SoundCache[id] = tmpl
preload[#preload + 1] = tmpl
end
end
if #preload > 0 then ContentProvider:PreloadAsync(preload) end
end
local function pickCachedSound(list)
if not list or #list == 0 then return nil end
local s = list[random(1, #list)]
return SoundCache[tostring(s.id)] or createSound(s)
end
local function playSoundAtPos(template, pos)
local sound = template:Clone()
sound.PlaybackSpeed += random(-200, 200) * 0.001
sound.PlayOnRemove = true
sound.Parent = FXAttachment
local function play()
FXAttachment.WorldPosition = pos
sound:Destroy()
end
if sound.IsLoaded then play() else sound.Loaded:Once(play) end
end
--// FX cache
local FXCache = {}
local function setupPart(p)
p.Anchored, p.CanCollide, p.CanQuery, p.CanTouch, p.Locked, p.CastShadow =
true, false, false, false, true, false
end
if not FXCache.DebugSphere then
local sphere = Instance.new("Part")
sphere.Shape, sphere.Material, sphere.Color = Enum.PartType.Ball, Enum.Material.Neon, Color3.new(1, 0.2, 0.1)
sphere.Transparency, sphere.Size, sphere.Name = 0.7, Vector3.one, "DebugShockwaveFX"
local Mesh=Instance.new("SpecialMesh")
Mesh.MeshType,Mesh.Scale=Enum.MeshType.Sphere,Vector3.one
Mesh.Parent,Mesh.Name=sphere,"SpecialMesh"
setupPart(sphere)
FXCache.DebugSphere = sphere
end
if not FXCache.DebugHit then
local p = Instance.new("ParticleEmitter")
p.Speed, p.Lifetime, p.Enabled, p.LightEmission, p.LightInfluence, p.Brightness =
NumberRange.new(0), NumberRange.new(1), false, 1, 0, 1
p.Parent = FXAttachment
FXCache.DebugHit = p
end
local function createDebugSphere(pos, radius, speed)
local sphere = FXCache.DebugSphere:Clone()
sphere.Parent, sphere.Position = ExplosionFXFolder, pos
local total = radius / speed
TweenService:Create(sphere.SpecialMesh, TweenInfo.new(total, Enum.EasingStyle.Linear),
{ Scale = Vector3.one * radius * 2 }):Play()
TweenService:Create(sphere,TweenInfo.new(total, Enum.EasingStyle.Cubic,Enum.EasingDirection.In),{Transparency = 1}):Play()
Debris:AddItem(sphere, total)
end
--// Humanoid cache
local humanoidCache = setmetatable({}, { __mode = "k" })
local function getHumanoid(model)
local cached = humanoidCache[model]
if cached ~= nil then
if cached == false then return nil end
if cached:IsA("Humanoid") and cached.Parent == model then return cached end
end
local found = model:FindFirstChildOfClass("Humanoid")
humanoidCache[model] = found or false
return found
end
local function applyImpulseOnce(part:BasePart, center, dist, radius, blastPressure, threshold, maxVel, offset, destroyVel)
local root = part.AssemblyRootPart or part
if root.Anchored==true and part.Anchored==false then
root=part
end
local falloff = 1 - (dist / radius)
falloff = falloff * falloff
local impulse = blastPressure * falloff / (min(max(root.AssemblyMass, 0.01), part.Mass))
if impulse < threshold then return threshold end
local newVel = root.AssemblyLinearVelocity + offset * (impulse / dist)
local maxSpeed = max(maxVel * 4, destroyVel)
local speed = newVel.Magnitude
if speed > maxSpeed then
newVel *= maxSpeed / speed
speed = maxSpeed
end
root.AssemblyLinearVelocity = newVel
return speed
end
--// Damage handler
local DamagedTag = Instance.new("Folder")
DamagedTag.Name = "XplosionDamagedTag"
local function tryDamage(part, dist, radius, dmg, canDestroy)
local model = part.Parent
if model:FindFirstChild("XplosionDamagedTag") then return end
local ratio = 1 - (dist / radius)
ratio = ratio * ratio
if ratio>.1 then
local hum = getHumanoid(model)
if hum then
hum:TakeDamage(dmg * ratio)
local tag = DamagedTag:Clone()
tag.Parent = model
Debris:AddItem(tag, 0.175)
elseif canDestroy and part:IsA("BasePart") then
part:BreakJoints()
end
end
end
local function applyEffectsOnHit(part, center, dist, profile, offset)
local v = applyImpulseOnce(part, center, dist, profile.R, profile.BlastPressure,
profile.Threshold, profile.ExplosionVel, offset, profile.ExplosionDestroyVelocity)
if v >= profile.ExplosionDestroyVelocity then
tryDamage(part, dist, profile.R, profile.MaxDmg, profile.CanDestroy)
end
end
--// Shockwave runner
local XplodeParams = OverlapParams.new()
XplodeParams.FilterType=Enum.RaycastFilterType.Exclude
local function makeShockwaveRunner(pos, profile, Params)
local elapsed, R, V, DEBUG = 0, profile.R, profile.V, profile.DEBUG
XplodeParams.FilterDescendantsInstances = Params.FilterDescendantsInstances
local function DoDebug() end
if DEBUG then
createDebugSphere(pos, R, V)
function DoDebug(hit)
FXAttachment.CFrame = hit.CFrame
FXCache.DebugHit:Emit(1)
end
end
while true do
local _, dt = Stepped:Wait()
elapsed += dt
local currR = V * elapsed
if currR >= R then currR = R end
for _, part: BasePart in GetPartBoundsInRadius(workspace, pos, currR, XplodeParams) do
if not part.Anchored then
local offset = part.Position - pos
applyEffectsOnHit(part, pos, offset.Magnitude, profile, offset)
if DEBUG then DoDebug(part) end
XplodeParams:AddToFilter(part)
end
end
if currR >= R then break end
end
end
--// Public API
local Params = OverlapParams.new()
Params.FilterType = Enum.RaycastFilterType.Exclude
function ExplosionModule.CreateExplosion(config, position)
local merged = mergeConfigs(ExplosionModule.GetDefaultExplosionConfigs(), config)
local BlastPressure = merged.blastPressure or (merged.explosionMaxDamage * 9000)
local Threshold = merged.explosionMomentumThreshold or
clamp((BlastPressure / (merged.explosionRadius * 9000)), 0.05, 1.5)
local profile = {
R = merged.explosionRadius,
V = merged.shockwaveSpeed,
MaxDmg = merged.explosionMaxDamage,
BlastPressure = BlastPressure,
CanDestroy = merged.explosionCanDestroy,
DEBUG = merged.DEBUG,
Threshold = Threshold,
ExplosionDestroyVelocity = merged.explosionDestroyVelocity,
ExplosionVel = merged.explosionVelocity,
}
Params.FilterDescendantsInstances = merged.ignoreParts
preloadExplosionSounds(merged)
local sound = pickCachedSound(merged.explosionSound)
if sound then playSoundAtPos(sound, position) end
makeShockwaveRunner(position, profile, Params)
end
return ExplosionModule
- Caution -
Please do not ask people to write entire scripts or design entire systems for you.