Car crashes can be described by classical physics pretty well. In its simplest form, for the purpose of recreating this in a game, you should look at Newton’s laws of motion i.e. law of inertia (car stays in motion until it is acted upon by an external force) and the 1st law of thermodynamics i.e. that the total energy of a system is constant (kinetic energy of a car is transformed into malformation of the car when crashing).
Now we have two basic models to base your script on. Now we just need to check for collisions with objects that would enact a force on the car (e.g. check for collisions with anchored / large models or parts with a great enough mass to damage the car) and then based on the velocity at that impact, we can damage the car to some extent.
Edit:
I got bored and decided to play around with this. In no way is this a finished product, you’ll need to do far more than this, but it’s a start and should give you some idea on how to do this yourself:
Script:
-- local
local function create(obj)
obj = Instance.new(obj)
return function (props)
for prop, val in next, props do
obj[prop] = val
end
return obj
end
end
-- init
local car = require(script.car_class).new(script.Parent)
-- fires when the car crashes as defined by our MassThreshold and VelocityThreshold properties
car.crashed:connect(function (hit, position, normal, surface, impact)
-- debug
print(
('Our car instance collided with %s\'s %s surface (relative mass %d) at a velocity magnitude of %d studs/second'):format(hit.Name, surface, impact.relativeMass, impact.impactVelocity)
)
-- Instantiate our smoke / do any other animations
create "Smoke" {
Parent = car:getCar()
}
end)
car:setMassThreshold(10):setVelocityThreshold(30):getCar().BodyVelocity.Velocity = Vector3.new(0, 0, -50)
Car module class:
-- helpers
local function create(obj)
obj = Instance.new(obj)
return function (props)
for prop, val in next, props do
obj[prop] = val
end
return obj
end
end
-- init
local car = { }
function car.new(model)
local this = { }
this.car = nil
this.mass = nil
this.bounds = nil
this.currentVelocity = nil
this.lastPosition = nil
this.update = Instance.new 'BindableEvent'
this.crashed = this.update.Event
this.connections = { }
this.massThreshold = 0 --> Determines the minimum relative mass our object needs to be to damage our car
this.veloThreshold = 0 --> Determines the minimum velocity we need to be travelling to cause damage => You could alter this to be partly determined by the mass of the object instead
-- local
local function getMass(part)
local density = PhysicalProperties.new(part.Material).Density
local size = part.Size
local volume = size.x * size.y * size.x
return density * volume
end
local function recurseMass(obj)
local mass = 0
local recurse do
function recurse(item)
for i, v in next, item:GetChildren() do
if v:IsA 'BasePart' then
mass = mass + getMass(v)
end
recurse(v)
end
end
end
return mass
end
local function normalToSurface(cf, normal)
normal = cf:vectorToObjectSpace(normal)
local x, y, z = normal.x, normal.y, normal.z
if x ~= 0 and (y == 0 and z == 0) then
return x > 0 and 'Right' or 'Left'
elseif y ~= 0 and (x == 0 and z == 0) then
return y > 0 and 'Top' or 'Bottom'
elseif z ~= 0 and (x == 0 and y == 0) then
return z > 0 and 'Back' or 'Front'
end
return 'Back'
end
local function carCollision(hit)
if not this.currentVelocity then
return
end
if hit and hit.Parent then
local mass = getMass(hit)
-- Is it at least the same or more mass than ourselves?
if mass - this.mass > this.massThreshold then
-- Are we travelling faster than our threshold velocity for damage to occur?
if this.currentVelocity.magnitude > this.veloThreshold then
-- Find the position, normal and surface of the hit to pass back for some kind of impact on that part etc
local _, collision, normal = game.Workspace:FindPartOnRayWithWhitelist(
Ray.new(this.bounds.Position, (hit.Position - this.bounds.Position).unit * (hit.Position - this.bounds.Position).magnitude),
{hit}
)
local surface = normalToSurface(hit.CFrame, normal)
-- Perform our crash event
this:crash(hit, collision, normal, surface, {
relativeMass = mass - this.mass,
impactVelocity = this.currentVelocity.magnitude
})
end
end
end
end
-- private
function this._construct(obj)
if typeof(obj) ~= 'Instance' then
return warn 'Unable to instantiate, provide an instance'
end
-- construct our basic variables and our hit box
if obj:IsA 'Part' then
this.mass = getMass(obj)
this.bounds = obj
else
this.mass = recurseMass(obj)
local cf, size = obj:GetBoundingBox()
this.bounds = create 'Part' {
Name = 'BOUNDING_BOX';
CanCollide = false;
Anchored = obj.PrimaryPart.Anchored;
Size = size;
Transparency = 1;
Parent = obj;
}
_ = not this.bounds.Anchored and create 'Weld' {
Parent = this.bounds;
Part0 = obj.PrimaryPart;
Part1 = this.bounds;
C0 = obj.PrimaryPart.CFrame:toObjectSpace(cf);
}
end
this.car = obj:IsA "Model" and obj.PrimaryPart or obj
if not this.lastPosition then
this.lastPosition = this.bounds ~= obj and (function () local cf = obj:GetBoundingBox() return cf.p end)() or obj.CFrame.p
end
this.connections['joint'] = game:GetService('RunService').Heartbeat:connect(function (delta)
if not this.car or not this.car.Parent then
return this:destroy()
end
-- move our bounding box along with our car if it's anchored
local cf = this.bounds ~= obj and obj:GetBoundingBox() or obj.CFrame
if this.bounds ~= obj then
this.bounds.CFrame = cf
end
-- set up our velocity determinants
local pos = cf.p
this.currentVelocity = (pos - this.lastPosition) / delta
this.lastPosition = pos
end)
this.connections['collision'] = this.bounds.Touched:connect(carCollision)
return this
end
-- public
function this:getMass()
return this.mass
end
function this:getCar()
return this.car
end
function this:setMassThreshold(val)
this.massThreshold = val
return this
end
function this:setVelocityThreshold(val)
this.veloThreshold = val
return this
end
function this:crash(hit, position, normal, surface, impact)
-- Remove our listeners
for i, v in next, this.connections do
if v then
v:disconnect()
end
end
if this.bounds ~= this.car then
this.bounds:Destroy()
end
-- Remove our body objects if we're not anchored
if not this.car.Anchored then
for i, v in next, this.car:GetChildren() do
if v:IsA "BodyVelocity" or v:IsA "BodyPosition" or v:IsA "BodyGyro" then
v:Destroy()
end
end
end
this.update:Fire(hit, position, normal, surface, impact)
end
function this:destroy()
-- Remove all listeners and delete our car
for i, v in next, this.connections do
if v then
v:disconnect()
end
end
if this.car and this.car.Parent then
this.car:Destroy()
end
end
-- construct
return this._construct(model)
end
return car
Place file: car_collisions.rbxl (22.9 KB)