I am trying to create a way to apply blood decals to a player if they hit the ground too hard, but only on specific sides of their limbs based on where they hit. I’ve done a similar thing before but with raycasted guns, so i tried to do a similar thing where i would create a raycast in the air to get the face that hit but it didn’t work. Here is the code i am using to apply the decals
function GetNormalFromFace(part, normalId)
return part.CFrame:VectorToWorldSpace(Vector3.FromNormalId(normalId))
end
function NormalToFace(normalVector, part)
local TOLERANCE_VALUE = 1 - 0.001
local allFaceNormalIds = {
Enum.NormalId.Front,
Enum.NormalId.Back,
Enum.NormalId.Bottom,
Enum.NormalId.Top,
Enum.NormalId.Left,
Enum.NormalId.Right
}
for _, normalId in pairs( allFaceNormalIds ) do
-- If the two vectors are almost parallel,
if GetNormalFromFace(part, normalId):Dot(normalVector) > TOLERANCE_VALUE then
return normalId -- We found it!
end
end
return nil -- None found within tolerance.
end
function gore.LimbBloodSplatter(player, bodypart, normal)
local decalclone = script.BloodDecals["Decal"..math.random(1, 6)]:Clone()
decalclone.Parent = bodypart
decalclone.Transparency = 1
local face = NormalToFace(normal, bodypart)
decalclone.Face = face
tweenservice:Create(decalclone, TweenInfo.new(0.2, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut), {Transparency = 0}):Play()
end
If this is only for blocks why not just take the maximum component of the normal vector? For example (0.3, -0.4, 0.3) is closest to being the normal of the bottom surface, -Y, since the Y axis has the highest magnitude and is negative.
You’re looping through a list of all possible normals on a block, checking the dot product and accepting that as the normal if its “close enough”, basically snapping it to the exact normal. Instead you could do this
function SnapNormal(norm)
if math.abs(norm.X) > math.max(math.abs(norm.Y), math.abs(norm.Z) then
return Vector3.xAxis * math.sign(norm.X)
elseif math.abs(norm.Y) > math.abs(norm.Z) then
return Vector3.yAxis * math.sign(norm.Y)
else
return Vector3.zAxis * math.sign(norm.Z)
end
end
This does not tell me anything as you’ve only posted part of the error (No traceback, the blue part), and have not included the exact code that was run to produce the error. Please attach the entire code and error output so I can determine the line numbers.
for _, bodypart in pairs(character:GetChildren()) do
if bodypart:IsA("BasePart") and not bodypart:FindFirstAncestorOfClass("Accessory") then
bodypart.Touched:Connect(function(touchpart)
if bodypart.Velocity.Magnitude > 20 and touchpart:FindFirstAncestorOfClass("Model") ~= character then
local vector = (bodypart.Position + Vector3.new(0, 1, 0) - bodypart.Position)
local raycast = workspace:Raycast(bodypart.Position + Vector3.new(0, 1, 0), vector * 2, RaycastParams.new())
if raycast then
goremodule.LimbBloodSplatter(player, bodypart, vector)
print("mm")
end
end
end)
end
end
is setting face to nil, which is not allowed because it needs to be a Face enum. Your NormalToFace function only works if the normal you give it is already very close to being an exact face normal. You can use the SnapNormal function I showed you to make sure that normal is always an exact face normal, such as (1, 0, 0)
Here is a test script that shows you what is happening:
function SnapNormal(norm)
if math.abs(norm.X) > math.max(math.abs(norm.Y), math.abs(norm.Z)) then
return Vector3.xAxis * math.sign(norm.X)
elseif math.abs(norm.Y) > math.abs(norm.Z) then
return Vector3.yAxis * math.sign(norm.Y)
else
return Vector3.zAxis * math.sign(norm.Z)
end
end
function VecPretty(vec)
return string.format("(%+8.3f,%+8.3f,%+8.3f)", vec.X, vec.Y, vec.Z)
end
--Test vector
for i = 1, 10 do
local tv = Vector3.new(math.random() * 2.0 - 1.0, math.random() * 2.0 - 1.0, math.random() * 2.0 - 1.0).Unit
local snapped = SnapNormal(tv)
print(VecPretty(tv) .." -snap-> ".. VecPretty(snapped))
end