So I wanted to show you how you could make your own vehicle damage system when hit at a certain speed. It’s quite a long code but great results. Let’s get started!
So first things first, you want to insert a Server Script, place it in a model, and set the RunContext to Client.
Now we would want to create a folder named “Explosions” and place it inside the workspace to store our explosions.
If you have a model already, create a folder inside the model and name it “Parts” and store any parts you want to be able to break.
Ok, now we want to set the network ownership of our model to a player you want for smooth physics.
Create an ObjectValue called “OwnerValue” and place it inside the model.
Insert a Server Script inside the model and paste this code in:
local model = script.Parent
local Parts = model:WaitForChild("Parts")
local VehicleSeat = Parts:WaitForChild("VehicleSeat")
local OwnerValue = model:WaitForChild("OwnerValue")
local oldestPlayer = nil
local function SetNetworkOwner(owner)
OwnerValue.Value = owner
for i,v in ipairs(model:GetDescendants()) do
if v:IsA("BasePart") then
local result = v:CanSetNetworkOwnership() -- checks if it can be set
if result then
v:SetNetworkOwner(owner)
end
end
end
end
local function CheckOwnership()
if VehicleSeat.Occupant then
local player = game.Players:GetPlayerFromCharacter(VehicleSeat.Occupant.Parent)
if player then
SetNetworkOwner(player)
end
else
if not OwnerValue.Value and oldestPlayer then -- if there is no owner of the vehicle, set the ownership to the oldest player
SetNetworkOwner(oldestPlayer)
end
end
end
VehicleSeat:GetPropertyChangedSignal("Occupant"):Connect(function() -- if the occupant has changed
CheckOwnership()
end)
CheckOwnership()
--------------------------------------------------------------------------------------------------
game.Players.PlayerAdded:Once(function(player)
oldestPlayer = player -- the player that joined the game first will be the oldest player in the server
CheckOwnership()
end)
Now we will create the crash system.
Create a Server Script inside the model and name it “CrashHandler”.
Set the RunContext to Client.
Now we will start coding our crash system!
Here are some variables you will need:
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Remotes = ReplicatedStorage:WaitForChild("Remotes") -- create a folder in RS and name it "Remotes"
local model = script.Parent -- our model
local Parts = model:WaitForChild("Parts") -- our Parts folder
local blastPower = 50 -- the power to blast the part away after break
local crashSpeed = 10 -- minimum speed to break
local crashSpeedY = 10 -- minimum speed from falling to break
local explodedParts = {} -- store parts that have already been crashed
local previousPartVelocities = {} -- store the part's previous velocities
local velocityConnections = {} -- store our velocity loops in this table
local velocityCoroutineConnections = {} -- store our velocity coroutines in this table
local Explosions = workspace:WaitForChild("Explosions")
Now we will need some functions to handle this:
local function IsAPlayer(hit)
local character = hit:FindFirstAncestorWhichIsA("Model") -- checks if "hit" is inside a model
if character then
local player = game.Players:GetPlayerFromCharacter(character) -- if it really is a player
if player then
return true -- its a player
end
end
end
local function CheckIfPartIsConnected(part)
local getConnectedParts = part:GetConnectedParts(true) -- get every part that is connected to that part
local connectedParts = #getConnectedParts -- total parts connected to that part
if connectedParts > 0 then -- if there is more than 0 parts connected to that part
return true -- it is connected
end
end
local function CanBreak(part, hit, previousVelocity)
if previousVelocity then
local speed = math.abs((previousVelocity - hit.AssemblyLinearVelocity).Magnitude) -- speed of the traveling part
local yVelocity = math.abs(previousVelocity.Y - hit.AssemblyLinearVelocity.Y) -- speed of the part that is falling
local isExploded = table.find(explodedParts, hit) -- checks if "hit" is inside the table
if not isExploded then -- if it hasen't crashed
if hit.Position.Y < part.Position.Y then -- if the part is higher than the "hit" part
if yVelocity >= crashSpeedY then
return true -- can be broken
end
else
if speed >= crashSpeed then -- if it is at the minimum speed
return true -- can be broken
end
end
end
end
end
More functions…
local function BreakPart(part)
part:BreakJoints() -- breaks every joint inside the parts like welds
if previousPartVelocities[part] then -- if it exists
previousPartVelocities[part] = nil -- make it not exist
end
if velocityConnections[part] then
velocityConnections[part]:Disconnect() -- disconnect the loop
velocityConnections[part] = nil
end
if velocityCoroutineConnections[part] then
coroutine.close(velocityCoroutineConnections[part]) -- stop the coroutine
velocityCoroutineConnections[part] = nil
end
end
local function Explode(part)
BreakPart(part)
local isConnected = CheckIfPartIsConnected(part) -- if the part is connected to anything
local isExploded = table.find(explodedParts, part) -- if its has crashed
if isConnected and not isExploded then -- if its connected and hasn't crashed
table.insert(explodedParts, part) -- insert inside the table to exclude it
local explosion = Instance.new("Explosion")
explosion.BlastPressure = 0
explosion.BlastRadius = 3 -- adjust this radius to your likings
explosion.DestroyJointRadiusPercent = 0
explosion.Position = part.Position
explosion.Visible = false
explosion.Parent = Explosions
Remotes.ExplodePart:FireServer(part, explosion.Position, blastPower)
explosion.Hit:Connect(function(hit)
local isConnected = CheckIfPartIsConnected(hit)
local isExploded = table.find(explodedParts, hit)
if isConnected and not isExploded then
table.insert(explodedParts, hit)
BreakPart(hit) -- break the part before breaking it in the server for smoothness
Remotes.ExplodePart:FireServer(hit, explosion.Position, blastPower) -- sends a remote to the server to break the part
end
end)
end
end
local function Crash(part, hit, previousVelocity)
local canBreak = CanBreak(part, hit, previousVelocity) -- checks if part can be broken
if canBreak then -- if so..
Explode(part)
end
end
local function CheckHit(part)
previousPartVelocities[part] = part.AssemblyLinearVelocity -- set the previous velocity of the part
part.Touched:Connect(function(hit)
local previousVelocity = previousPartVelocities[part] -- get the part previous velocity
local isAPlayer = IsAPlayer(hit) -- if the part has hit a player
if not isAPlayer then -- exclude the player
Crash(part, hit, previousVelocity)
end
end)
velocityCoroutineConnections[part] = coroutine.wrap(function()
velocityConnections[part] = RunService.Heartbeat:Connect(function()
if previousPartVelocities[part] then
previousPartVelocities[part] = part.AssemblyLinearVelocity -- will always update the previous velocity because without it, if a part touches the ground, this would be the difference: (updated velocity: 80, non-updated velocity: 40) if you understand.
end
end)
end)()
end
And now our final bits of code:
model.DescendantAdded:Connect(function(child) -- we do this because sometimes, parts load after the server has loaded
if child:IsA("BasePart") and child:IsDescendantOf(Parts) then
CheckHit(child)
end
end)
for i,v in ipairs(Parts:GetDescendants()) do -- just in case
if v:IsA("BasePart") and v:IsDescendantOf(Parts) then
CheckHit(v)
end
end
We are done now! This would be the full code:
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Remotes = ReplicatedStorage:WaitForChild("Remotes")
local model = script.Parent
local Parts = model:WaitForChild("Parts")
local blastPower = 50
local crashSpeed = 10
local crashSpeedY = 10
local explodedParts = {}
local previousPartVelocities = {}
local velocityConnections = {}
local velocityCoroutineConnections = {}
local Explosions = workspace:WaitForChild("Explosions")
local function IsAPlayer(hit)
local character = hit:FindFirstAncestorWhichIsA("Model")
if character then
local player = game.Players:GetPlayerFromCharacter(character)
if player then
return true
end
end
end
local function CheckIfPartIsConnected(part)
local getConnectedParts = part:GetConnectedParts(true)
local connectedParts = #getConnectedParts
if connectedParts > 0 then
return true
end
end
local function CanBreak(part, hit, previousVelocity)
if previousVelocity then
local speed = math.abs((previousVelocity - hit.AssemblyLinearVelocity).Magnitude)
local yVelocity = math.abs(previousVelocity.Y - hit.AssemblyLinearVelocity.Y)
local isExploded = table.find(explodedParts, hit)
if not isExploded then
if hit.Position.Y < part.Position.Y then
if yVelocity >= crashSpeedY then
return true
end
else
if speed >= crashSpeed then
return true
end
end
end
end
end
local function DestroyPart(part)
part:BreakJoints()
if previousPartVelocities[part] then
previousPartVelocities[part] = nil
end
if velocityConnections[part] then
velocityConnections[part]:Disconnect()
velocityConnections[part] = nil
end
if velocityCoroutineConnections[part] then
coroutine.close(velocityCoroutineConnections[part])
velocityCoroutineConnections[part] = nil
end
end
local function Explode(part)
DestroyPart(part)
local isConnected = CheckIfPartIsConnected(part)
local isExploded = table.find(explodedParts, part)
if isConnected and not isExploded then
table.insert(explodedParts, part)
local explosion = Instance.new("Explosion")
explosion.BlastPressure = 0
explosion.BlastRadius = 3
explosion.DestroyJointRadiusPercent = 0
explosion.Position = part.Position
explosion.Visible = false
explosion.Parent = Explosions
Remotes.ExplodePart:FireServer(part, explosion.Position, blastPower)
explosion.Hit:Connect(function(hit)
local isConnected = CheckIfPartIsConnected(hit)
local isExploded = table.find(explodedParts, hit)
if isConnected and not isExploded then
table.insert(explodedParts, hit)
DestroyPart(hit)
Remotes.ExplodePart:FireServer(hit, explosion.Position, blastPower)
end
end)
end
end
local function Crash(part, hit, previousVelocity)
local canBreak = CanBreak(part, hit, previousVelocity)
if canBreak then
Explode(part)
end
end
local function CheckHit(part)
previousPartVelocities[part] = part.AssemblyLinearVelocity
part.Touched:Connect(function(hit)
local previousVelocity = previousPartVelocities[part]
local isAPlayer = IsAPlayer(hit)
if not isAPlayer then
Crash(part, hit, previousVelocity)
end
end)
velocityCoroutineConnections[part] = coroutine.wrap(function()
velocityConnections[part] = RunService.Heartbeat:Connect(function()
if previousPartVelocities[part] then
previousPartVelocities[part] = part.AssemblyLinearVelocity
end
end)
end)()
end
model.DescendantAdded:Connect(function(child)
if child:IsA("BasePart") and child:IsDescendantOf(Parts) then
CheckHit(child)
end
end)
for i,v in ipairs(Parts:GetDescendants()) do
if v:IsA("BasePart") and v:IsDescendantOf(Parts) then
CheckHit(v)
end
end
This crash system works very good so enjoy!
Here are some results: