Dealing with Bullet Delays and accurate Visual Effects


So recently I have been attempting to make a Weapon System with Bullet Delays, and potentially Bullet Drop if I want to create a more realistic game, but a problem I have with it is that I want to have my visual effects relect where the rays are going in their direction, rather than a random spreading angle applied to them, which can lead to some weird results. I’m also having the issue of properly managing every ray, and how they will be calulated during the Bullet Delays.

The Module I created utilizes OOP (Object Oriented Programming) to structure its data, and fire its code.

Contents of Firing Event
self.FireEvent = Fire.OnServerEvent:Connect(function(Player, Position: Vector3)
	local Params =; -- Params
	Params.FilterDescendantsInstances = {Player.Character};
	Params.FilterType = Enum.RaycastFilterType.Exclude;

	local Root = Player.Character.PrimaryPart; -- Part to Raycast From
    -- This is done to avoid the ray going through objects in front of it

	local Distance = Player:DistanceFromCharacter(Position); -- Distance from point fired at
	local TimeDelay = Distance/self.ProjectileSpeed; -- Time it will take to get there
	if self.RequireAmmo then -- Checks if the Weapon uses Ammo or not
		self.Config:SetAttribute("Ammo",math.clamp(self.Config:GetAttribute("Ammo") - 1, 0, self.Config:GetAttribute("MaxAmmo")));
	utl.GlobalEffectEvent:FireAllClients(self.Tool, Position, self.ProjectileSpeed, self.ProjectileCount, self.ProjectileSpread, TimeDelay, self.Sounds)
    -- Plays Weapon Effects such as Bullet Trails, and Muzzle.
	print(`Ping...`); -- Start...
	local DelayedPositions = {}; -- Collects all Bullets being fired, and their
    -- respective directions if spread is accounted for
	for i = 1, self.ProjectileCount do -- iterates for every projectile
		local x = self.Random:NextNumber(-self.ProjectileSpread, self.ProjectileSpread)
		local y = self.Random:NextNumber(-self.ProjectileSpread, self.ProjectileSpread)
		local Direction =, Position) * CFrame.Angles(math.rad(x), math.rad(y), 0)
		DelayedPositions[i] = {Root.Position, Direction.LookVector * self.Range};

	task.delay(TimeDelay, function() -- Bullet Delay
		warn(`PONG! ({TimeDelay})`); -- End!
		for i = 1, #DelayedPositions do -- iterate through data
			local Items = DelayedPositions[i]; -- get for raycast
			local Result = workspace:Raycast(Items[1], Items[2], Params) -- raycast

			if Result then
				local Humanoid = self:VerifyTarget(Result.Instance) -- Verifies a target
				if Humanoid then
					local Damage = self.Damage.X * (self:IsHeadshot(Result.Instance) and self.Damage.Y or 1) -- Calulates the damage output
					self:DealDamage(Humanoid, Damage); -- Deal Damage;
Contents of Client Event
PlayEffects.OnClientEvent:Connect(function(Tool, Position, Speed, Count, Spread, Time, Sounds)
	local Handle = Tool.Handle;
	local Points = Handle.Points;
	Points.Pointer.WorldCFrame = CFrame.lookAt(Points.Pointer.WorldPosition, Position) -- Point towards location to launch effects
    -- this is so the effects will go towards
	Points.Pointer.BulletEffect.Speed =; -- Bullet Speed
	Points.Pointer.BulletEffect.Lifetime =;  -- Effect will die after it reaches its location
	Points.Pointer.BulletEffect.SpreadAngle =, Spread) -- Currently how the Spreading of Effects works
	Points.Pointer.BulletEffect:Emit(Count) -- Varies depending on Bullet Count
	Points.Muzzle.MuzzleEffect:Emit(10) -- Fires Muzzle Effect

    --/ This part isnt important to the topic, but you can view it
	local SoundId = Sounds[math.random(1, #Sounds)] -- Random Sound to pick
	local Sound ="Sound"); -- new Instance
	Sound.SoundId = `rbxassetid://{SoundId}`; -- apply soundId
	Sound.Parent = Tool.Handle; -- place in Handle
	Sound:Play() -- Play
	task.wait(1) -- wait a bit
	Debris:AddItem(Sound, Sound.TimeLength); -- kill

I’m looking for an efficient way to mange it, as I feel my current solution to create and execute these effects is inefficient and could use some work to do so.
I am also not looking for people who just paste code without explaining it, just a general explanation on what I should do and then an optional code example to explain further.

If possible, how would I go about doing this?