Bullet instancing optimization

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!

1 Like

Parent the projectile after setting all of these properties.

Same with the billboard.

And the tracer.

And the value.

And the weld.

Okay I’m sure you get the point here. Even Name should be assigned after parenting it.

1 Like

I’ve kept the parent value first for arbitrary consistency. Is parenting a part into the game world before settings its properties more performance heavy? Could you also explain why?

Well, benchmarks show that setting the instance’s parent before setting the properties will cause a significant performance decrease. The reason for this is after parenting it, the parent will listen for property changes and that will slow down the creation, therefore dropping FPS.

3 Likes

I see. I’m testing now with some changes.
Thanks for your super quick reply to my issue

1 Like

This simple change has had a drastic effect on performance. Thanks again :slight_smile:

1 Like