Over the past week, I have been trying to optimise a script for a game I am working on. The game is about getting to the other side of a busy highway by dodging vehicles. This script is responsible for generating the vehicles, animating the vehicles, and then disposing of the vehicles. However, the script isn’t great on performance, and it is imperative that it improves. This is a rough idea of how the script works. Note that the script is client sided:
- Loops through each vehicle spawn and then creates a loop for each.
- Continuously spawns a random vehicle after a random amount of time for each spawn. The vehicle is either retrieved from a recycled vehicle or cloned from storage.
- Vehicle is added to a dictionary.
- Function connected to a heartbeat event loops through the dictionary and moves each vehicle by the desired amount by setting the
CFrame
of thePrimaryPart
(the other parts of the vehicles are unanchored and welded to thePrimaryPart
). - When a vehicle reaches the end of the road it is put into a folder in
ReplicatedStorage
to be recycled.
Here’s the script:
local TS = game:GetService("TweenService")
local MS = game:GetService("MarketplaceService")
local Info = TweenInfo.new(25,Enum.EasingStyle.Linear,Enum.EasingDirection.Out,0,false,0)
local Easy = workspace.CarSpawns.Easy
local Cars = game.ReplicatedStorage.Vehicles.Normal:GetChildren()
local Trucks = game.ReplicatedStorage.Vehicles.Trucks:GetChildren()
local PoliceCars = {}
local Wheels = {}
local FastSpawn = true
local EasySpawnTime1 = 1000
local EasySpawnTime2 = 3000
local HardSpawnTime1 = 750
local HardSpawnTime2 = 1600
local EasySpeed1 = 120
local EasySpeed2 = 160
local HardSpeed1 = 200
local HardSpeed2 = 280
local EasyTruckSpawn = 3
local HardTruckSpawn = 8
local ChangeLaneTime = 5
local CanSwitch = true
local Tweens = {}
local ChangingLanes = 0
local MovingCars = {}
local Plr = game.Players.LocalPlayer
local Range = 800
local CarChances = {}
local PassCarChances = {}
local NormalColours = {
Color3.fromRGB(255,255,255),
Color3.fromRGB(21, 46, 92),
Color3.fromRGB(0,0,0),
Color3.fromRGB(186, 186, 186),
Color3.fromRGB(117, 26, 18),
Color3.fromRGB(229, 202, 122),
Color3.fromRGB(80, 80, 80),
Color3.fromRGB(22, 72, 33)
}
local SpecialColours = {
["Bugatti Chiron '20"] = {
{
Secondary = Color3.fromRGB(49, 70, 192),
Primary = Color3.fromRGB(23, 23, 23)
},
},
["Cadillac V16 '30"] = {
{
Primary = Color3.fromRGB(0, 0, 0),
Secondary = Color3.fromRGB(255, 255, 255)
},
},
["Bugatti Veyron '06"] = {
{
Secondary = Color3.fromRGB(168, 0, 0),
Primary = Color3.fromRGB(23, 23, 23)
},
},
["Lamborghini Countach '90"] = {
{
Primary = Color3.fromRGB(70, 9, 107),
},
},
["Volvo FH16 '90"] = {
{
Primary = Color3.fromRGB(190, 190, 190),
Secondary = Color3.fromRGB(233, 233, 233),
},
{
Primary = Color3.fromRGB(190, 190, 190),
Secondary = Color3.fromRGB(101, 101, 101),
},
{
Primary = Color3.fromRGB(190, 190, 190),
Secondary = Color3.fromRGB(21, 46, 92),
},
{
Primary = Color3.fromRGB(190, 190, 190),
Secondary = Color3.fromRGB(33, 33, 33),
},
},
["Dodge Charger '06"] = {
{
Primary = Color3.fromRGB(21, 46, 92),
},
{
Primary = Color3.fromRGB(0,0,0),
},
{
Primary = Color3.fromRGB(186, 186, 186),
},
{
Primary = Color3.fromRGB(117, 26, 18),
},
{
Primary = Color3.fromRGB(229, 202, 122),
},
{
Primary = Color3.fromRGB(80, 80, 80),
},
{
Primary = Color3.fromRGB(22, 72, 33),
}
},
["Limousine"] = {}
}
local function Heartbeat(DT)
for i, v in pairs(MovingCars) do
if not i.Parent then
MovingCars[i] = nil
continue
end
local Negative = 1
if MovingCars[i]["Lane"].Parent.Name == "Easy" then
Negative = -1
if MovingCars[i]["Value"] >= i:GetAttribute("Z") then
MovingCars[i] = nil
i.Parent = game.ReplicatedStorage.UsedVehicles
continue
end
else
if MovingCars[i]["Value"] <= i:GetAttribute("Z") then
MovingCars[i] = nil
i.Parent = game.ReplicatedStorage.UsedVehicles
continue
end
end
MovingCars[i]["Value"] -= (MovingCars[i]["Lane"]:GetAttribute("Speed")*DT) * Negative
if Plr.Character and Plr.Character.PrimaryPart then
local Magnitude = (Vector3.new(MovingCars[i]["X"],MovingCars[i]["Y"],MovingCars[i]["Value"])-Plr.Character.PrimaryPart.Position).Magnitude
if Magnitude <= Range then
i.Parent = workspace.Vehicles
i.PrimaryPart.CFrame = CFrame.new(MovingCars[i]["PrimaryX"],MovingCars[i]["PrimaryY"],MovingCars[i]["Value"]) * CFrame.Angles(MovingCars[i]["XAngle"],MovingCars[i]["YAngle"],MovingCars[i]["XAngle"])
else
i.Parent = game.ReplicatedStorage.VehicleHolder
end
end
end
end
local function GetColours(Car)
if SpecialColours[Car.Name] then
if not SpecialColours[Car.Name][1] then
return
end
local Primary = SpecialColours[Car.Name][math.random(1,#SpecialColours[Car.Name])]["Primary"]
local Secondary = SpecialColours[Car.Name][math.random(1,#SpecialColours[Car.Name])]["Secondary"]
for i, v in pairs(Car:GetDescendants()) do
if v.Name == "Hull" or v.Name == "Primary" then
v.Color = Primary
end
if v.Name == "Secondary" then
v.Color = Secondary
end
end
else
local Colour = NormalColours[math.random(1,#NormalColours)]
for i, v in pairs(Car:GetDescendants()) do
if v.Name == "Hull" or v.Name == "Secondary" then
v.Color = Colour
end
end
end
end
local function SirenTransparency(Car,Transparency)
if not Car.Body:FindFirstChild("SirenBlue") then return end
Car.Body.SirenBlue.Transparency = Transparency
Car.Body.SirenRed.Transparency = Transparency
Car.Body.SirenHolder.Transparency = Transparency
Car.Body.SirenMiddle.Transparency = Transparency
end
local function New(Type,Name,Value,Parent)
local New = Instance.new(Type)
New.Name = Name
New.Value = Value
New.Parent = Parent
return New
end
local function GetCar(CarName,Fol)
local Recycled = false
local Car
if game.ReplicatedStorage.UsedVehicles:FindFirstChild(CarName) then
Recycled = true
Car = game.ReplicatedStorage.UsedVehicles[CarName]
else
Car = Fol[CarName]:Clone()
end
return Car,Recycled
end
local function SpawnCar(CarSpawn,Speed,AdjacentLanes)
local PoliceSpawn = 50
if CarSpawn.Parent.Name == "Hard" then
PoliceSpawn = 40
end
local Police = math.random(1,PoliceSpawn)
local TruckSpawn
if CarSpawn.Parent.Name == "Easy" then
TruckSpawn = EasyTruckSpawn
else
TruckSpawn = HardTruckSpawn
end
local Type = math.random(1,TruckSpawn)
local Truck
if CarSpawn.Parent.Name == "Easy" then
if Type == 1 then
Truck = true
end
elseif Type == 1 or Type == 2 or Type == 3 then
Truck = true
end
local Car
local Chances = CarChances
if MS:UserOwnsGamePassAsync(game.Players.LocalPlayer.UserId,22882286) or game.Players.LocalPlayer:WaitForChild("Data"):WaitForChild("PassesTrying"):FindFirstChild("Lucky Cars") then
Chances = PassCarChances
end
local Recycled = false
if Police == 1 then
local CarName = PoliceCars[math.random(1,#PoliceCars)].Name
Car,Recycled = GetCar(CarName,game.ReplicatedStorage.Vehicles.Normal)
SirenTransparency(Car,0)
for i, v in pairs(Car.Body:GetChildren()) do
if v.Name == "Hull" and not v:GetAttribute("Police") then
v.Color = Color3.fromRGB(0,0,0)
end
end
elseif Truck then
local CarName = Trucks[math.random(1,#Trucks)].Name
Car,Recycled = GetCar(CarName,game.ReplicatedStorage.Vehicles.Trucks)
else
local CarName = Chances[math.random(1,#Chances)].Name
Car,Recycled = GetCar(CarName,game.ReplicatedStorage.Vehicles.Normal)
SirenTransparency(Car,1)
end
if Police ~= 1 then
Car:SetAttribute("Police",false)
GetColours(Car)
end
if Truck then
Type = "Truck"
else
Type = "Car"
end
local Val
if Recycled then
Val = Car.Lane
Val.Value = CarSpawn
Car.AdjacentLanes:Destroy()
else
local Cframe,Size = Car:GetBoundingBox()
local Cframe2 = Cframe
local Size2 = Size + Vector3.new(0,10,0)
local Cframe3 = Cframe
local Size3 = Size
if Type ~= 1 then
Cframe *= CFrame.new(0,-Size.Y/2,7)
Cframe2 *= CFrame.new(0,Size.Y+5,0)
end
Size = Vector3.new(CarSpawn.Size.X,Size.Y,Size.Z-7)
local Hitbox = Instance.new("Part")
Hitbox.CanCollide = false
Hitbox.Size = Size
Hitbox.Transparency = 1
Hitbox.Name = "HitBox"
Hitbox.CFrame = Cframe
local Weld = Instance.new("WeldConstraint")
Weld.Part0 = Hitbox
Weld.Part1 = Car.PrimaryPart
Weld.Parent = Car.PrimaryPart
Hitbox.Parent = Car.Body
if Type ~= "Truck" or Police == 1 then
local Jumped = Instance.new("Part")
Jumped.CanCollide = false
Jumped.Size = Size2
Jumped.Transparency = 1
Jumped.Name = "Jumped"
Jumped.CFrame = Cframe2
local Weld = Instance.new("WeldConstraint")
Weld.Part0 = Jumped
Weld.Part1 = Car.PrimaryPart
Weld.Parent = Car.PrimaryPart
Jumped.Parent = Car.Body
end
Val = New("ObjectValue","Lane",CarSpawn,Car)
end
AdjacentLanes:Clone().Parent = Car
Car.Lanes.Name = "AdjacentLanes"
for i, v in pairs(Car.Wheels:GetChildren()) do
local Clone
if Recycled then
Clone = v.Water
else
Clone = game.ReplicatedStorage.Particles.Water:Clone()
Clone.Parent = v
end
if game.ReplicatedStorage.Values.Weather.Value == "Flood" then
Clone.Enabled = true
else
Clone.Enabled = false
end
end
local Y
if CarSpawn.Parent.Name == "Easy" then
Y = -180
else
Car:SetAttribute("Hard",true)
Y = 0
end
Car:SetPrimaryPartCFrame(CFrame.new(CarSpawn.Position.X,Car.PrimaryPart.Position.Y,CarSpawn.Position.Z)*CFrame.Angles(0,math.rad(Y),0))
local Z
if CarSpawn.Parent.Name == "Easy" then
Z = 4245.46
else
Z = -2986.449
end
local Distance = (CarSpawn.Position-Vector3.new(CarSpawn.Position.X,CarSpawn.Position.Y,Z)).Magnitude
local Time = Distance/Speed
for i, v in pairs(Car.Body:GetChildren()) do
if v:IsA("BasePart") then
if not Recycled then
if v.Material == Enum.Material.Metal then
v.Material = Enum.Material.SmoothPlastic
v.Reflectance = .05
end
if v == Car.PrimaryPart then
v.Anchored = true
else
v.Anchored = false
end
v.CanCollide = false
end
if v.Name == "Lights" and (game.Lighting.ClockTime > 17.5 or game.Lighting.ClockTime < 6.3 or game.ReplicatedStorage.Values.Lights.Value) then
v.Material = Enum.Material.Neon
if v:FindFirstChild("Light") then
v.Light.Enabled = true
end
end
end
end
if game.Lighting.ClockTime > 17.5 or game.Lighting.ClockTime < 6.3 or game.ReplicatedStorage.Values.Lights.Value then
if Car:GetAttribute("Police") then
local New = Instance.new("ObjectValue")
New.Value = Car
New.Parent = game.ReplicatedStorage.Sirens
game:GetService("Debris"):AddItem(New,Time)
Car.Body.SirenHolder.Siren:Play()
end
end
if not Recycled then
local Clone = game.ReplicatedStorage.Sounds[Type]:Clone()
Clone.PlaybackSpeed = math.random((Clone.PlaybackSpeed - .2)*1000,(Clone.PlaybackSpeed + .2)*1000)/1000
Clone:Play()
Clone.Parent = Car.Body.HitBox
local Clone = game.ReplicatedStorage.Sounds.Water:Clone()
Clone.PlaybackSpeed = math.random((Clone.PlaybackSpeed - .355)*1000,(Clone.PlaybackSpeed + .35)*1000)/1000
Clone.Parent = Car.Body.HitBox
end
if game.ReplicatedStorage.Values.Weather.Value == "Flood" then
Car.Body.HitBox.Water:Play()
end
MovingCars[Car] = {
Value = CarSpawn.Position.Z,
Lane = CarSpawn,
X = Car.Body.HitBox.Position.X,
Y = Car.Body.HitBox.Position.Y,
Z = Car.Body.HitBox.Position.Z,
PrimaryX = Car.PrimaryPart.Position.X,
PrimaryY = Car.PrimaryPart.Position.Y,
PrimaryZ = Car.PrimaryPart.Position.Z,
XAngle = math.rad(Car.PrimaryPart.Orientation.X),
YAngle = math.rad(Car.PrimaryPart.Orientation.Y),
ZAngle = math.rad(Car.PrimaryPart.Orientation.Z),
}
Car:SetAttribute("Z",Z)
Car.Parent = workspace.Vehicles
end
for i, v in pairs(game.ReplicatedStorage.Vehicles.Normal:GetChildren()) do
for x = 1,v:GetAttribute("Chance"),1 do
table.insert(CarChances,v)
end
end
for i, v in pairs(game.ReplicatedStorage.Vehicles.Normal:GetChildren()) do
local Num = v:GetAttribute("Chance")
if Num ~= 10000 then
Num *= 2
end
for x = 1,Num,1 do
table.insert(PassCarChances,v.Name)
end
end
for i, v in pairs(Cars) do
if v:GetAttribute("Police") then
table.insert(PoliceCars,v)
end
end
repeat wait(.1) until #Easy:GetChildren() == 5
local Tweens = {}
for i, v in pairs(workspace.CarSpawns:GetChildren()) do
for x, y in pairs(v:GetChildren()) do
local Z
if v.Name == "Easy" then
Z = -2986.449
else
Z = 4245.46
end
local Tween = TS:Create(y,Info,{Position = Vector3.new(y.Position.X,y.Position.Y,Z)})
Tween:Play()
table.insert(Tweens,Tween)
local Speed1
local Speed2
if v.Name == "Easy" then
Speed1 = EasySpeed1
Speed2 = EasySpeed2
else
Speed1 = HardSpeed1
Speed2 = HardSpeed2
end
local Speed = math.random(Speed1,Speed2)
y:SetAttribute("Speed",Speed)
spawn(function()
while true do
local SpawnTime1
local SpawnTime2
if v.Name == "Easy" then
SpawnTime1 = EasySpawnTime1
SpawnTime2 = EasySpawnTime2
else
SpawnTime1 = HardSpawnTime1
SpawnTime2 = HardSpawnTime2
end
if FastSpawn then
SpawnTime1 /= 2
SpawnTime2 /= 2
end
wait(math.random(SpawnTime1,SpawnTime2)/1000)
SpawnCar(y,y:GetAttribute("Speed"),y.Lanes)
end
end)
end
end
game:GetService("RunService").Heartbeat:Connect(Heartbeat)
wait(25)
for i, v in pairs(Tweens) do
v:Destroy()
v = nil
end
Tweens = nil
FastSpawn = false