Help with bullet impact effects

Hi. Recently I’ve been working on my own custom gun kit utilising the FastCast module. I’ve got bullet stuff done, and hit detection, but now I want to have effects for when a part is hit by a bullet.

I’ve tried this function, (this is all on a server script)

function ImpactEffects(Type, Tool, ImpactedPiece, ImpactedParent)
	if Type == "Hum" then
		for _, fx in pairs(Tool:WaitForChild("Blood"):GetChildren()) do
			if fx:IsA("ParticleEmitter") then
				 local newfx = fx:Clone()
				newfx.Parent = ImpactedPiece
				newfx:Emit(newfx.EmitCount.Value)
				game:GetService("Debris"):AddItem(newfx, newfx.Lifetime.Max)
			end
		end
	elseif Type == "Material" then
		if ImpactedPiece.Material == Enum.Material.Plastic then
			for _, fx in pairs(Tool:WaitForChild("Plastic"):GetChildren()) do
				if fx:IsA("ParticleEmitter") then
					local newfx = fx:Clone()
					newfx.Parent = ImpactedPiece
					newfx:Emit(newfx.EmitCount.Value)
					game:GetService("Debris"):AddItem(newfx, newfx.Lifetime.Max)
					--game:GetService("Debris"):AddItem(a0, newfx.Lifetime.Max)
				elseif fx:IsA("Folder") and fx.Name == "MaterialSounds" then
					for _, fx2 in pairs(fx:GetChildren()) do
						if fx2:IsA("Sound") then
							local newfx2 = fx2:Clone()
							newfx2.Parent = ImpactedPiece
							newfx2.PlayOnRemove = true
							newfx2:Destroy()
						end
					end
				end
			end
		end
	end
end

However, this clones the particle emitters WAY too many times. I only want it to clone a particle emitter once, and I think it’s because of the for loop I’ve added but I’m not sure how to fix this.

I’d also like to know how to only emit the effects at the exact position the bullet hits. I tried attachments, however this almost fried my computer. Is there a more optimised way I could do this?

If you need to see more of the script please tell me!

Thanks.

1 Like

FastCast is wonderful good choice.
To answer your last question first while I read the rest of your code. Attachments is what I any many others use for particle emissions.

I use this on the CLIENT generally speaking you don’t want to run things like particles on the server if you can avoid it as its less stress on the server so you off put that to the client’s computer, send an event to all clients with positions of where these particles are needed then have them render it.

I at the moment don’t see a purpose in the for loop for getting your children when it’s likely you already know where the directory of the particle emitters are. Again I recommend offsetting all of this code to the client and store the impact effects in replicated storage to be cloned from there when the gun is fired.

function MakeParticleFX(position, normal,part)
	local attachment = Instance.new("Attachment")
	attachment.CFrame = CFrame.new(position, position + normal)
	attachment.Parent = workspace.Terrain

	local particle = ImpactParticle:Clone()
	if part.Parent:FindFirstChild("Humanoid") then
		particle.Color = ColorSequence.new{
			ColorSequenceKeypoint.new(0, Color3.fromRGB(207, 0, 3)),
			ColorSequenceKeypoint.new(1, Color3.fromRGB(207, 0, 3))
		}
	else
		particle.Color = ColorSequence.new{
			ColorSequenceKeypoint.new(0, part.Color),
			ColorSequenceKeypoint.new(1, part.Color)
		}
	end
	particle.Parent = attachment
	Debris:AddItem(attachment, particle.Lifetime.Max) -- Automatically delete the particle effect after its maximum lifetime.

	-- A potentially better option in favor of this would be to use the Emit method (Particle:Emit(numParticles)) though I prefer this since it adds some natural spacing between the particles.
	particle.Enabled = true
	wait(0.05)
	particle.Enabled = false
end
3 Likes

The code you posted clones too many particle emitters because that’s what it’s been told to do.
It clones and emits every single ParticleEmitter in the list of effects and plays every single sound.

Instead of doing that, choose one of them randomly and use only that.

(since I had already typed up this function: )

-- Assumes that parent has at least one child
local function ChooseRandomChild(parent)
	local children = parent:GetChildren()
	return children[math.random(#children)]
end

Unfortunately, you will have to ensure that the folder containing blood particles, plastic particles, plastic sounds etc. only contain what they are supposed to contain, unless you filter the list of children to only contain particles or sounds.
In essence, use that for loop to add all ParticleEffects to a list, then pick a random one from that list.

You seem to be making a gun kit for others and not just your game, so you can’t really prevent users from shooting themselves in the foot by putting junk in these folders, so that effort might be fruitful. Oh well.

Note that:

  1. You’re not disposing of the Attachments, so eventually there will be a billion of them.
  2. The original poster’s code has no mention of a normal direction, so it’s likely that he does not know how to get it.
  3. Lower graphics effects will throttle the amount of particles emitted per second, whereas :Emit() will emit exactly as many as you specify.
1 Like

Hi. Thank you all for your replies.

What I’ve done is edited Douglas’s code to be like this:

local function MakeParticleFX(Folder, part, hitB)
	local attachment = Instance.new("Attachment")
	attachment.WorldPosition = hitB.Position
	attachment.Parent = workspace.Terrain
	
	local particle = ChooseRandomChild(Folder):Clone()
	particle.Parent = attachment
	particle:Emit(particle:WaitForChild("EmitCount").Value)
	game:GetService("Debris"):AddItem(attachment, particle.Lifetime.Max) -- Automatically delete the particle effect after its maximum lifetime.
end

The code works fine for me, thankfully. It’s also linked up to a Client event.

game.ReplicatedStorage.GunsRoot.Events.particl.OnClientEvent:Connect(function(Part, hitB, Folder, realTool)
	print("got event")
	if realTool ~= nil then
		local folderH = realTool:WaitForChild("BodyAttach"):FindFirstChild("HitEffects")
		if folderH then
			if folderH:FindFirstChild(Folder) then
				local FReal = folderH:WaitForChild(Folder)
				MakeParticleFX(FReal, Part, hitB)
			end
		end
	end
end)

server script:

-- Function for bullet hit effects
function ImpactEffects(Player, Folder, ImpactedPiece, ImpactedParent, realTool)
	local mdl = ImpactedPiece:FindFirstAncestorWhichIsA("Model")
	if not mdl or (mdl and not mdl:FindFirstChildOfClass("Humanoid")) then
		if ImpactedPiece.Material == Enum.Material.Plastic then
			particleEvent:FireClient(Player, ImpactedPiece, ImpactedParent, "Plastic", realTool)
		end
	end
end

Now, what’s wrong is that after the first shot, it keeps doubling up on the particle events. I’ve got a print command which tells me if I get the client event;

game.ReplicatedStorage.GunsRoot.Events.particl.OnClientEvent:Connect(function(Part, hitB, Folder, realTool)
	print("got event")
	if realTool ~= nil then
		local folderH = realTool:WaitForChild("BodyAttach"):FindFirstChild("HitEffects")
		if folderH then
			if folderH:FindFirstChild(Folder) then
				local FReal = folderH:WaitForChild(Folder)
				MakeParticleFX(FReal, Part, hitB)
			end
		end
	end
end)

I think, I’m not sure how, but the event is getting fired multiple times. Here is all my code for bullet handling, by the way.

function Fired(player, mousePos, origin, velocity, tool, bulletType, Dmg, HeadshotDmg, Module, FX)
	local REQUIREDMODULE = require(Module)
	if Dmg ~= REQUIREDMODULE.BaseDamage then
		SecurityCheck(REQUIREDMODULE, Module)
	end
	CastParams.FilterDescendantsInstances = {player.Character, projrootWorkspace}
	CastBehaviour.CosmeticBulletTemplate = projroot[bulletType]
	CastBehaviour.MaxDistance = REQUIREDMODULE.Range
	CastBehaviour.HighFidelityBehavior = FCModule.HighFidelityBehavior.Default
	
	local function Fire(direc)
		local dirCF = CFrame.new(Vector3.new(), direc)
		local spreadDir = CFrame.fromOrientation(0, 0, math.random(0, math.pi * 2))
		local spreadAng = CFrame.fromOrientation(math.rad(math.random(REQUIREDMODULE.SpreadMinimum, REQUIREDMODULE.SpreadMaximum)), 0, 0)
		local dirFinal = (dirCF * spreadDir * spreadAng).LookVector
		Caster:Fire(origin, dirFinal, velocity, CastBehaviour)
	end
	
	local POSNew = RayCastRays(mousePos, Module)
	local dir = (POSNew - origin).Unit
	for i = 1, REQUIREDMODULE.BulletsPerShot do
		Fire(dir)
	end
	
	local function Impact(cast, result, velocity, bullet)
		local HIT = result.Instance
		local char = HIT:FindFirstAncestorWhichIsA("Model")
		if char then
			local targetHum = char:FindFirstChildOfClass("Humanoid")
			if targetHum and (targetHum.Health > 0) then
				local charPlayer = game.Players:GetPlayerFromCharacter(char)
				if charPlayer then
					if not char:FindFirstChild("creator") then
						local crtag = Instance.new("ObjectValue")
						crtag.Parent = char
						crtag.Name = "creator"
						crtag.Value = player
						game:GetService("Debris"):AddItem(crtag, 2)
					end
				end
				if DMGModule.CanDamage(char, player) then
--[[					if REQUIREDMODULE.DamageDropoffEnabled then
						local DropoffRange = 
					end]]
					if DMGModule.HeadshotDamage(char, HIT) then
						targetHum:TakeDamage(HeadshotDmg)
					else
						targetHum:TakeDamage(Dmg)
					end
				end
			elseif not targetHum then
				
			end
		else
			
		end
		ImpactEffects(player, FX, HIT, bullet, tool)
	end
	Caster.RayHit:Connect(Impact)
end

function lengthChanged(cast, lastPos, dir, length, v, bullet)
	if bullet then
		CastBehaviour.CosmeticlugerBullet = bullet
		local bulletLength = bullet.Size.Z/2
		local offSet = CFrame.new(0, 0, -(length - bulletLength))
		bullet.CFrame = CFrame.lookAt(lastPos, (lastPos+dir)):ToWorldSpace(offSet)
		game:GetService("Debris"):AddItem(bullet, 1.3)
	end
end