Paint gun splatter is not working correctly

Hello everyone. I am trying to create a paint gun that shoots a projectile, and when the projectile hits something, it splats. The issue with my paint gun is that it is not correctly orientated and sometimes, the ball gets stuck inside something and does not go to the correct position.

Video of the paint gun

Expected behaviour

Code

local Paintballs = game.ReplicatedStorage.Paintballs
local char = script.Parent
local cam = workspace.CurrentCamera
local UIS = game:GetService("UserInputService")

local splashparams = RaycastParams.new()
splashparams.FilterType = Enum.RaycastFilterType.Whitelist
splashparams.FilterDescendantsInstances = {workspace.Map}
splashparams.IgnoreWater = true

local Splashes = {}

local function CreateSplash(painttype, pos, normal)
	local paint = Paintballs:FindFirstChild(painttype)
	if paint then
		local Splash = Instance.new("Part")

		--Splash.Shape = Enum.PartType.Cylinder
		Splash.Size = Vector3.new(5, 5, 0.5)
		--Splash.Orientation = Vector3.new(0,0,90)
		Splash.Position = pos
		Splash.Anchored = true
		Splash.CanCollide = false
		Splash.BrickColor = paint.BrickColor
		Splash.Name = "Splash"
		Splash.Parent = workspace.Paint
		
		if normal then
			Splash.CFrame = normal
		end

		table.insert(Splashes, Splash)

		local Emitter = paint.ParticleEmitter:Clone()
		Emitter.Enabled = false
		Emitter.Parent = Splash
		Emitter.EmissionDirection = "Right"
		Emitter:Emit(15)
	end
end

local function CreatePaint(painttype)
	local paint = Paintballs:FindFirstChild(painttype)
	if paint then
		local Clone = paint:Clone()
		Clone.Name = "Paintball"
		Clone.Position = cam["Paint Gun"].PaintSpawner.Position
		game:GetService("Debris"):AddItem(Clone, 30)
		local bodyvel = Instance.new("BodyVelocity")
		bodyvel.Velocity = cam.CFrame.LookVector * 100
		bodyvel.MaxForce = Vector3.new('inf', 'inf', 'inf')
		bodyvel.Parent = Clone
		game:GetService("Debris"):AddItem(bodyvel, 0.001)
		local connect
		connect = Clone.Touched:Connect(function(hit)
			if hit:FindFirstAncestor("Map") then
				connect:Disconnect()
				local params = RaycastParams.new()
				params.IgnoreWater = true
				params.FilterType = Enum.RaycastFilterType.Whitelist
				params.FilterDescendantsInstances = {hit}
				local ray = workspace:Raycast(Clone.Position, (hit.Position - Clone.Position).Unit * 500, params)
				local normal = nil
				if ray then
					print(ray.Normal, ray.Instance)
					normal = CFrame.lookAt(Clone.Position, Clone.Position + ray.Normal)
				end
				CreateSplash(painttype, Clone.Position, normal)
				Clone:Destroy()
			end
		end)
		Clone.Parent = workspace.Paint
	end
end

spawn(function()
	while wait() do
		if UIS:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) then
			CreatePaint("Blue")
			task.wait(0.05)
		elseif UIS:IsMouseButtonPressed(Enum.UserInputType.MouseButton2) then
			CreatePaint("Orange")
			task.wait(0.05)
		end
	end
end)

My best guess is that the projectile ends up stuck in a wall. Since rays can’t detect that it is inside the wall, it returns nothing.

Thanks for reading!

Try using Orange451’s WorldPositionToSurfaceGui, with a surfacegui

It works sometimes. Not most of the times, though.
The thing is, the paintballs are getting stuck into a wall. I raycast from the projectile to the wall, and if the projectile is inside of the wall, the raycast will not detect it.

I will reccomend you raycast like this blue ball, instead of red.


Raycasting is processed from previous ball position, before it touched wall. This may help you prevent balls glitching when inside wall.

Make sure created paint blocks ignore already created paint blocks.

1 Like

I tried doing it with a RunService, using Region3, but paintballs are now going through walls, and rays aren’t registering at all.


		local connect
		connect = game:GetService("RunService").RenderStepped:Connect(function()
			local TopLeftFrontOfPart = Clone.Position + Vector3.new(Clone.Size.X/2,Clone.Size.Y/2,Clone.Size.Z/2)
			local TopRightBackOfPart = Clone.Position + (Vector3.new(Clone.Size.X/2,Clone.Size.Y/2,Clone.Size.Z/2)*-1)
			local region = Region3.new(TopLeftFrontOfPart, TopRightBackOfPart)

			if #workspace:FindPartsInRegion3WithWhiteList(region, {workspace.Map}) > 0 then
				local part = workspace:FindPartsInRegion3WithWhiteList(region, {workspace.Map})[1]

				local params = RaycastParams.new()
				params.IgnoreWater = true
				params.FilterType = Enum.RaycastFilterType.Whitelist
				params.FilterDescendantsInstances = {part}
				local ray = workspace:Raycast(Clone.Position, (part.Position - Clone.Position).Unit * 500, params)
				if ray then
					print("yes")
					CreateSplash(painttype, Clone.Position, CFrame.lookAt(Clone.Position, Clone.Position + ray.Normal))
				end
				region = nil
				connect:Disconnect()
				Clone:Destroy()
			end
		end)

I would appreciate if someone could create a script that has good collision detection with rays.

Hello everyone. I have found a good solution to this problem.

Whenever the ball hits something with the .Touched event, we move it backwards a bit, so its not stuck into a wall.

Here’s the script for anyone:

local connect
connect = Clone.Touched:Connect(function(hit)
	if hit:FindFirstAncestor("Map") then
		Clone.CFrame = CFrame.lookAt(Clone.Position, hit.Position)
		Clone.CFrame = Clone.CFrame - (Clone.CFrame.LookVector * 4)
		local params = RaycastParams.new()
		params.FilterType = Enum.RaycastFilterType.Whitelist
		params.FilterDescendantsInstances = {hit}
		params.IgnoreWater = true
		local ray = workspace:Raycast(Clone.Position, (hit.Position - Clone.Position).Unit * 500, params)
		if ray then
			CreateSplash(painttype, ray.Position, CFrame.lookAt(ray.Position, ray.Position + ray.Normal))
		end
		connect:Disconnect()
		Clone:Destroy()
	end
end)

Thanks to everyone who replied!

But you need fix this too:
image