Which part are you most struggling with so I can help you understand?
The main part is all the functions, they confuse me a bit.
Here’s a commented version for you which should help:
-- helpers
local function create(obj) -- this is a function that just instances an object, and returns a function which allows you to change the object's properties by a table
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' --> Set up a public bindable event so we can tell the other scripts when we crash
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)
-- this function calculates the mass of a singular part given its volume and the density as defined by the PhysicalProperties class
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)
-- this function recursively calculates a model's mass by finding all the parts and calling the 'getMass' function
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)
-- this function takes the normal vector e.g. Vec3(1, 0, 0) and determines the surface of the object from the normal vector
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
-- Let's find the mass of the part we touched
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 and pass back the current info
this:crash(hit, collision, normal, surface, {
relativeMass = mass - this.mass,
impactVelocity = this.currentVelocity.magnitude
})
end
end
end
end
-- private
function this._construct(obj)
-- This is the constructor e.g. every time someone calls car.new() we'll make a new car class and set it up for use
if typeof(obj) ~= 'Instance' then
return warn 'Unable to instantiate, provide an instance'
end
-- construct our basic variables and our hit box
-- we're creating a hitbox so we can ensure we can detect collisions from all axes without having to set up a touch event for each part of the car
-- we've also ensured that we're checking to see if it's a model or not, to determine how to create the collision box
-- similarly, we've made sure to instantiate a weld if the car is unanchored so we don't have to recursively set the CFrame
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;
}
-- this is called a ternary operator, but i've sunk the return value
-- basically we've done:
--[[
if not this.bounds.Anchored then
-->> CREATE WELD
else
-- do nothing
end
]]
_ = 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 --> this.car is always the primarypart of the car, or it's the part that's acting as a car
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 (because the weld we tried to create won't work 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 is just the equation v = d/t where v = velocity, d = distance, and t = time
this.lastPosition = pos
end)
this.connections['collision'] = this.bounds.Touched:connect(carCollision) --> Set up touch event with the bounding box
return this
end
-- public
function this:getMass() -- function for other scripts to get our mass
return this.mass
end
function this:getCar() -- function for other scripts to get the car's primary part
return this.car
end
function this:setMassThreshold(val) -- function for other scripts to set the mass threshold
this.massThreshold = val
return this -- this is called 'chaining' we're passing back the current instance of our class so they can do further changes
end
function this:setVelocityThreshold(val) -- function for other scripts to set the velocity threshold
this.veloThreshold = val
return this -- this is called 'chaining' we're passing back the current instance of our class so they can do further changes
end
function this:crash(hit, position, normal, surface, impact) -- this is our crash event which fires the .crashed event we created earlier
-- 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
-- Fire this event to our .crashed event, so any listeners will be told of our crash
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
The other script commented:
-- local
local function create(obj) -- same as in the other script
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) -- require the module with our fancy car class
-- fires when the car crashes as defined by our MassThreshold and VelocityThreshold properties as I mentioned in the other script
car.crashed:connect(function (hit, position, normal, surface, impact)
-- debug to show the hit information
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)
-- this is an example of the 'chaining' i mentioned above
car:setMassThreshold(10):setVelocityThreshold(30):getCar().BodyVelocity.Velocity = Vector3.new(0, 0, -50)
-- this wasn't necessary though, you could have also done this:
car:setMassThreshold(10)
car:setVelocityThreshold(30)
local car_primary_part = car:getCar()
car_primary_part.BodyVelocity.Velocity = Vector3.new(0, 0, -50)
Check out the placefile.rbxl file I sent above to see the architecture of the set up e.g. a model with a primarypart, and inside the primarypart is a BodyVelo + BodyPos. You don’t need to use those BodyMovers though, your car can be anchored and you can do whatever you like with it.
Similarly, as mentioned in other posts, you don’t need to use this car module - it’s just because I got bored and wanted to try it and thought it may be helpful. You’re more than welcome to rip out parts from the crashing parts and use it as you wish
Hey there,
Are you able to explain how to implement your plugin into A-Chassis system?
As well, doing the repair tool.
I have a issue with this script when I add BodyVelocity to the car it messes the cars up by making it not able to drive. Any tips?