I’ve been recently developing a war game, and during bug testing with some friends I realized that my bullet tracers need some heavy optimization.
The bullets are projectiles with some effects like a billboard GUI (for visibility at long range, only on rounds that have bright tracers), and beams for a more traditional tracer effect. They are also simulated only on each client. A player fires a gun (bullet is created for that player), a signal is sent to the server and then sent back to all clients notifying them that a gun has been fired (bullet is created for all other players).
It seems that there’s a noticeable drop in framerate only when the bullets are being created, not while they are in flight. The framerate climbs back up to a steady 60 fps whenever the firing has stopped, even if all fired bullets are still traveling in the air and being simulated. This leads me to believe that it is something with the section of code that instances the bullet.
This is the function responsible for creating the projectile:
function m:createProjectile(Vehicle, VehicleSettings, WeaponData, TracerShot, Muzzle)
-- Vehicle is the vehicle that this weapon is on
-- Vehicle settings is a module
-- WeaponData is the settings of the weapon on the vehicle
-- TracerShot is a boolean that determines whether the projectile has a billboard tracer
--- Muzzle is an attachment that the projectile originates from
local camMagnification = VehicleFrameworkClient.defaultFOV/game.Workspace.CurrentCamera.FieldOfView
-- cam magnification is used for setting the size of the tracer billboard
-- cam magnification can change because a player can zoom in and out when aiming their gun
local ProjVel = Muzzle.Parent.CFrame:ToWorldSpace(Muzzle.CFrame).LookVector * WeaponData.projectileSpeed + Vehicle.Base.AssemblyLinearVelocity
local Projectile = RepStore.iTech.Assets.VehicleWeapons.Projectile:Clone()
-- the projectile part is stored in Replicated Storage and copied
Projectile.Parent = game.Workspace.Ignore
Projectile.Name = "Projectile"
Projectile.Size = WeaponData.projectileSize
Projectile.Color = WeaponData.projectileColor
Projectile.Shape = Enum.PartType.Cylinder
Projectile.Material = Enum.Material.Neon
Projectile.Transparency = WeaponData.projectileTransparency
Projectile.CanCollide = false
Projectile.Anchored = false
Projectile.CFrame = Muzzle.Parent.CFrame:ToWorldSpace(Muzzle.CFrame)
Projectile.AssemblyLinearVelocity = ProjVel
local billBoard = Projectile.BillboardGui -- the billboard tracer effect
billBoard.Parent = Projectile
billBoard.Adornee = Projectile
billBoard.Size = UDim2.new(0,WeaponData.projectileBillboardSize*camMagnification, 0, WeaponData.projectileBillboardSize*camMagnification)
billBoard.LightInfluence = 0
billBoard.ImageLabel.ImageColor3 = WeaponData.projectileColor
billBoard.ImageLabel.ImageTransparency = WeaponData.projectileTransparency
if not TracerShot then
billBoard.Enabled = false
end
local trailParent = Instance.new("Part")
-- I create a second part for holding the beam so it isn't instantly removed when the projectile is removed
trailParent.Name = "ProjectileTrail"
trailParent.Parent = game.Workspace.Ignore
trailParent.Anchored = false
trailParent.CanCollide = false
trailParent.Massless = true
trailParent.Transparency = 1
Projectile.Impact.Parent = trailParent
local trailPointer = Instance.new("ObjectValue")
-- an object value that points to the trail part so it can be referenced
trailPointer.Parent = Projectile
trailPointer.Value = trailParent
trailPointer.Name = "ProjectileTrail"
local trailWeld = Instance.new("Weld")
-- welds trail to part to projectile
trailWeld.Parent = trailParent
trailWeld.Part0 = Projectile
trailWeld.Part1 = trailParent
local trailAT0 = Instance.new("Attachment")
local trailAT1 = Instance.new("Attachment")
trailAT0.Parent = trailParent
trailAT1.Parent = trailParent
trailAT0.Position = Vector3.new(0,Projectile.Size.Y*2, Projectile.Size.Z/2)
trailAT1.Position = Vector3.new(0,-Projectile.Size.Y*2, Projectile.Size.Z/2)
local trail = Vehicle[WeaponData.TurretFolder].BulletTrail:Clone()
-- the specific beam object is stored in a folder that holds some of the weapon's data
trail.Parent = trailParent
trail.Attachment0 = trailAT0
trail.Attachment1 = trailAT1
if WeaponData.projectileTrail == false then -- projectile should not use trail
trail.Enabled = false
Projectile.Material = Enum.Material.SmoothPlastic
end
local At = Instance.new("Attachment")
At.Parent = Projectile
local antiGrav = Instance.new("VectorForce")
-- a force to counteract gravity so custom bullet drop can be used
antiGrav.Parent = Projectile
antiGrav.ApplyAtCenterOfMass = true
antiGrav.Attachment0 = At
antiGrav.RelativeTo = Enum.ActuatorRelativeTo.World
antiGrav.Force = Vector3.new(0,Projectile.Mass*game.Workspace.Gravity - Projectile.Mass*WeaponData.projectileBulletDrop,0)
game:GetService("Debris"):AddItem(Projectile, WeaponData.projectileLifetime)
game:GetService("Debris"):AddItem(trailParent, WeaponData.projectileLifetime)
local renderStepped
renderStepped = game:GetService("RunService").RenderStepped:Connect(function(deltaTime)
camMagnification = VehicleFrameworkClient.defaultFOV/game.Workspace.CurrentCamera.FieldOfView
if Projectile.Parent == nil then
renderStepped:Disconnect()
return
end
billBoard.Size = UDim2.new(0,WeaponData.projectileBillboardSize*camMagnification, 0, WeaponData.projectileBillboardSize*camMagnification)
end)
return Projectile, ProjVel
end
If you guys see anything detrimental or redundant in the code, some feedback would be greatly appreciated. Also, any general advice on creating optimized bullet projectiles and tracers would be great. Thanks!