Help with raycasting to charachterHead

Issue with raycasting on meshes

I’m trying to make a gun system for my upcoming game, only i’m stuck with this small issue accuring when firing at meshes or in this case the head of the character. I tried searching up solutions but couldn’t find one.

https://gyazo.com/576ce3f65f99f25d0fc344ff875b1f87

As you can see the gun detectes intersections outside of the mesh you can see which is expected because it’s in reality a normal part.

But it’s not ideal. I want the bulletholes on the mesh not on the part.

Btw I rather use surfacegui to show bulletholes for ClipsDescendants.

Serverscript inside gun model:

print("hi")
local worldPositionToGui = require(game:GetService("ServerScriptService").WorldPositionToGuiPosition) 
local bulletHole = require(game:GetService("ServerScriptService").BulletHole)

local Debris = game:GetService("Debris")
local firerate = .1

local fireable = true
script.Parent.shot.OnServerEvent:Connect(function(player, character, mp2, ur2)
	if fireable == true then
		fireable = false
		local MaxDistance = 50000 --Maximun distance the ray can go
		script.Parent.Handle.pow:Play()
		script.Parent.Handle.Flare.ParticleEmitter:Emit(100)
		local raycastResult = bulletHole.raycastGun(player, character, script.Parent, mp2, ur2, MaxDistance)
		if raycastResult then
			if raycastResult.Instance.Parent:FindFirstChild("Humanoid") then
				raycastResult.Instance.Parent:FindFirstChild("Humanoid").Health -= math.random(10,15)
				bulletHole.setHole(raycastResult, true)
			else
				bulletHole.setHole(raycastResult)
			end
			bulletHole.addBeam(raycastResult, script.Parent.Handle)
		else			
			bulletHole.addBeam()
		end
		
		task.wait(firerate)
		fireable = true
	end
end)

The modulescript with the name “BulletHole”

local bulletHole = {}

local Debris = game:GetService("Debris")



function bulletHole.raycastGun(player, character, gun, mp2, ur2, MaxDistance)
	local dist = (character:FindFirstChild(gun.Name).Handle.Flare.WorldPosition - mp2)
	local raycastparams = RaycastParams.new()
	local direction = (mp2 - ur2.Origin).Unit * MaxDistance
	raycastparams.FilterDescendantsInstances = gun.Parent:GetDescendants()
	raycastparams.FilterType = Enum.RaycastFilterType.Exclude
	local raycastResult = game:GetService("Workspace"):Raycast(ur2.Origin, direction, raycastparams)
	return raycastResult
end



function bulletHole.checkGuiOnSurface(raycastResult, Surface)
	local partInstances = raycastResult.Instance:GetDescendants()
	for	i, v in pairs(partInstances) do
		if v.Name == "SurfaceGui" and v.Face == Surface then
			return v
		end
	end
end

function bulletHole.checkFrameOnSurface(raycastResult, Surface)
	local partInstances = raycastResult.Instance:GetDescendants()
	for	i, v in pairs(partInstances) do
		if v.Name == "Frame" and v.Parent.Face == Surface then
			return v
		end
	end
end


function bulletHole.addBeam(raycastResult, Handle, duration)
	local beam = Handle.Beam
	local part = Instance.new("Part")
	if not duration then
		duration = .1
	end
	part.Parent = workspace
	part.Size = Vector3.new(1,1,1)
	part.Position = raycastResult.Position
	part.Anchored = true
	part.Transparency = 1
	part.CanCollide = false
	part.CanQuery = false
	part.CanTouch = false
	Debris:AddItem(part, 2+duration) --to avoid the beam lasting longer than the added part.
	local att = Instance.new("Attachment")
	att.Parent = part
	beam.Attachment1 = att 
	task.wait(duration)
	beam.Attachment1 = nil
end

function bulletHole.setHole(raycastResult, bulletholeBlood)
	local SCALE = 50
	local worldPositionToGui = require(game:GetService("ServerScriptService").WorldPositionToGuiPosition)
	local worldPosition = worldPositionToGui:WorldPositionToGuiPosition(raycastResult.Instance, raycastResult.Position)
	local Surface = worldPosition[1] --the face you hit
	local surfaceGui = bulletHole.checkGuiOnSurface(raycastResult, Surface)
	local gunshotsIdBasedOnMaterials = require(script.Parent.BulletHoleSettings).gunshotsIdBasedOnMaterials
	--If there is no surfacegui on that face, one will be added on that face.
	if not surfaceGui then 
		surfaceGui = Instance.new("SurfaceGui")
		surfaceGui.CanvasSize = Vector2.new( SCALE*worldPosition[2], SCALE*worldPosition[3] )
		surfaceGui.Face = Surface
		surfaceGui.Parent = raycastResult.Instance
		surfaceGui.ClipsDescendants = true
		surfaceGui.LightInfluence = 1
	end
	
	local bulletImage = Instance.new("ImageLabel")
	local frame = bulletHole.checkFrameOnSurface(raycastResult, Surface)
	--If there is no frame on that face, one will be added on that face.
	if not frame then
		local frame = Instance.new("Frame")
		frame.Parent = surfaceGui
		frame.ClipsDescendants = true
		frame.BackgroundTransparency = 1
		frame.Size = UDim2.new(1,0,1,0)
		bulletImage.Parent = frame
	end
	if frame then
		bulletImage.Parent = frame
	end
	
	bulletImage.Size = UDim2.new(0, 25, 0, 25) --standard size
	bulletImage.Position = UDim2.new(worldPosition[4], 0, worldPosition[5], 0)
	bulletImage.AnchorPoint = Vector2.new(0.5, 0.5)
	bulletImage.ImageTransparency = .3 --standard ImageTransparency
	bulletImage.BackgroundTransparency = 1
	Debris:AddItem(bulletImage, 60)
	math.randomseed(os.time())
	local texture
	if gunshotsIdBasedOnMaterials[raycastResult.Instance.Material] then
		texture = gunshotsIdBasedOnMaterials[raycastResult.Instance.Material][math.random(1, 3)]
	else
		texture = gunshotsIdBasedOnMaterials.other[math.random(1, 3)]
	end
	
	if gunshotsIdBasedOnMaterials[raycastResult.Instance.Material] then -- if texture then
		local size = gunshotsIdBasedOnMaterials[raycastResult.Instance.Material].size
		local ImageTransparency = gunshotsIdBasedOnMaterials[raycastResult.Instance.Material].ImageTransparency
		local BaseColor = gunshotsIdBasedOnMaterials[raycastResult.Instance.Material].BaseColor
		bulletImage.Image = texture
		bulletImage.Size = size --modified size
		bulletImage.ImageTransparency = ImageTransparency --modified ImageTransparency
		if BaseColor then
			bulletImage.ImageColor3 = BaseColor
		else
			bulletImage.ImageColor3 = raycastResult.Instance.Color
		end
		
			
	else --if no texture/material then use 'other' textures
		if bulletholeBlood then
			bulletImage.Image = "rbxassetid://16241247312" or ""
			bulletImage.ImageColor3 = Color3.fromRGB(255, 0, 0)
			print(bulletImage.ImageColor3)
		else
			math.randomseed(os.time())
			bulletImage.Image = gunshotsIdBasedOnMaterials.other[math.random(1,3)] or ""
			bulletImage.Size = gunshotsIdBasedOnMaterials.other.size --modified size
			bulletImage.ImageTransparency = gunshotsIdBasedOnMaterials.other.ImageTransparency 
			bulletImage.ImageColor3 = raycastResult.Instance.Color
		end
		
	end
	
	
end



return bulletHole

If you know any simple solution to this problem or know a link please post it here, thank you in advance.

1 Like

Well, you could replace the head with a union.

I would use MeshPart instead union. Speaking of using MeshPart, here’s asset id for head rbxasset://fonts/head.mesh

1 Like

If you really want to use SurfaceGuis then use them for simple shapes and decals for complex shapes.

I use surfacegui for ClipsDescendants so that’s not really a solution.

If anyone is wondering, I found a solution. Simply whenever the Raycastresult.Instance.Name == “Head” then spawn a decal instead of using ImageLabel.