Car Crash System

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)

13 Likes