SCP Blood - Decal based realistic yet comical blood

Hey everyone, I recently made a blood script for my game and I would like to share it with y’all thinking someone might find a use for it sometime, its not similar to many other games I’ve seen on roblox, but it feels similar to SCP Containment breach hence the name.

Also yes it works on NPCs

And it works pretty well with motion

Game featured in the videos: Site-17 - Roblox

It goes in starter character scripts or in any character that you want to BLEED
You can also add or remove blood textures and replace them with your own
And it also has no sounds so you can implement that if you want and (hopefully) share it with us

(Also sorry for the bad code since I never intended to open it to the public)

4 Likes

Rewrote the script because why not
If you wanna use it, put it in ServerScriptService.

local TweenService = game:GetService("TweenService")
local Players = game:GetService("Players")

local BloodLocation = workspace.GFX
local BloodSize = {3, 4.5}
local BloodTextures = {
	"rbxassetid://119810117710356",
	"rbxassetid://115394637191321",
	"rbxassetid://72028379167893",
	"rbxassetid://122801970062010",
	"rbxassetid://75231418139407"
}

local function spawner(func)
	local wrap = coroutine.wrap(func)
	wrap()
end

local function GetNormal(Part, Position)
	local Shape = nil
	
	if Part:IsA("Part") then 
		Shape = Part.Shape.Value
	elseif Part:IsA("WedgePart") then 
		Shape = 3
	elseif Part:IsA("CornerWedgePart") then 
		Shape = 4
	else 
		Shape = 5
	end
	
	if Shape == 0 then
		return (Position - Part.Position).Unit, "curve", Position
	elseif Shape == 1 or Shape == 3 then
		local r = Part.CFrame:pointToObjectSpace(Position) / Part.Size
		local Rotation = Part.CFrame - Part.Position
		
		if r.x > 0.4999 then 
			return Rotation * Vector3.new(1, 0, 0), "right", Position
		elseif r.x < -0.4999 then 
			return Rotation * Vector3.new(-1, 0, 0), "left", Position
		elseif r.y > 0.4999 then 
			return Rotation * Vector3.new(0, 1, 0), "top", Position
		elseif r.y < -0.4999 then
			return Rotation * Vector3.new(0, -1, 0), "bottom", Position
		elseif r.z > 0.4999 then 
			return Rotation * Vector3.new(0, 0, 1), "back", Position
		elseif r.z < -0.4999 then 
			return Rotation * Vector3.new(0, 0, -1), "front", Position
		end
		
		return Rotation * Vector3.new(0, Part.Size.Z, -Part.Size.Y).Unit, "ramp", Position
	elseif Shape == 2 then
		return (Position - Part.Position).Unit, "curve", Position
	elseif Shape == 4 then
		local r = Part.CFrame:pointToObjectSpace(Position) / Part.Size
		local Rotation = Part.CFrame - Part.Position
		
		if r.x > 0.4999 then 
			return Rotation * Vector3.new(1, 0, 0), "right", Position
		elseif r.y < -0.4999 then 
			return Rotation * Vector3.new(0, -1, 0), "bottom", Position
		elseif r.z < -0.4999 then 
			return Rotation * Vector3.new(0, 0, -1), "front", Position
		elseif r.Unit:Dot(Vector3.new(1, 0, 1).Unit) > 0 then 
			return Rotation * Vector3.new(0, Part.Size.Z, Part.Size.Y).Unit, "lslope", Position
		end
		
		return Rotation * Vector3.new(-Part.Size.Y, Part.Size.X,0).Unit, "rslope", Position
	else
		return Vector3.new(0, 1, 0), "unknown", Position
	end
end

local function EmitBlood(HumanoidRootpart, Part)
	local RandomBloodSize = math.random(BloodSize[1], BloodSize[2])
	local BloodPart = Instance.new("Part")
	BloodPart.Name = "BloodPart"
	BloodPart.CanCollide = false
	BloodPart.CanQuery = false
	BloodPart.CanTouch = false
	BloodPart.Anchored = true
	BloodPart.Material = Enum.Material.Glass
	BloodPart.Size = Vector3.new(RandomBloodSize, 0, RandomBloodSize)
	BloodPart.CastShadow = false
	BloodPart.Transparency = 1
	BloodPart.Parent = workspace.GFX
	
	local BloodDecal = Instance.new("Decal")
	BloodDecal.Texture = BloodTextures[math.random(1, #BloodTextures)]
	BloodDecal.Face = Enum.NormalId.Top
	BloodDecal.Parent = BloodPart
	
	local Normal = GetNormal(Part, HumanoidRootpart.Position)
	
	local Parameters = RaycastParams.new()
	Parameters.FilterDescendantsInstances = {HumanoidRootpart.Parent, BloodLocation}
	local Origin = HumanoidRootpart.Position
	local Direction = -Normal * 20
	local RaycastResult = workspace:Raycast(Origin, Direction, Parameters)
	
	if RaycastResult then
		if RaycastResult.Instance then
			local Change = false
			
			if Normal == Vector3.new(0, 1, 0) then 
				Normal = Vector3.new(0.01, 0.9, 0) 
				Change = true 
			end
			
			local Right = -Vector3.yAxis:Cross(Normal)
			
			if not Change then
				BloodPart.CFrame = CFrame.fromMatrix(RaycastResult.Position, Right, Normal)
			else
				BloodPart.CFrame = CFrame.fromMatrix(RaycastResult.Position, Right, Normal)
				BloodPart.Orientation = Vector3.new(0, 90, 0)
			end
		end
	end
	
	spawner(function()
		task.wait(45)
		
		local BloodSize = TweenService:Create(BloodDecal, TweenInfo.new(5), {Transparency = 1}, true)
		local BloodTransparency = TweenService:Create(BloodPart, TweenInfo.new(5), {Size = Vector3.zero}, true)
		BloodSize:Play()
		BloodTransparency:Play()
		
		task.wait(5)
		
		BloodPart:Destroy()
	end)
end

Players.PlayerAdded:Connect(function(Player)
	Player.CharacterAdded:Connect(function(Character)
		local Humanoid = Character:FindFirstChildOfClass("Humanoid")
		local HumanoidRootPart = Character:FindFirstChild("HumanoidRootPart")
		
		if Humanoid and HumanoidRootPart then
			local Bleeding = false
			local LastHealth = Humanoid.Health
			
			task.wait() --// added a wait because my character wasn't spawning properly. you can probably remove this
			
			for i, child in Character:GetChildren() do
				if child:IsA("BasePart") then
					child.Touched:Connect(function(TouchedPart)
						if Bleeding or Humanoid.Health <= 0 then
							if not TouchedPart:FindFirstAncestor(Character.Name) and TouchedPart.Parent.Name ~= "Blood" and TouchedPart.Anchored then
								EmitBlood(HumanoidRootPart, TouchedPart)
							end
						end
					end)
				end
			end
			
			Humanoid.HealthChanged:Connect(function(NewHealth)
				LastHealth = NewHealth
				if NewHealth > LastHealth then return end
				
				local Parameters = RaycastParams.new()
				Parameters.FilterDescendantsInstances = {Character, BloodLocation}
				local Origin = HumanoidRootPart.Position
				local Direction = Vector3.new(0, -20, 0) * 20
				local RaycastResult = workspace:Raycast(Origin, Direction, Parameters)
				
				if RaycastResult then
					if RaycastResult.Instance then
						spawner(function()
							for i = 1, 3 do
								EmitBlood(HumanoidRootPart, RaycastResult.Instance)
								EmitBlood(HumanoidRootPart, RaycastResult.Instance)
								task.wait(1 / 3)
							end
						end)
					end
				end
				
				spawner(function()
					Bleeding = true
					task.wait(1)
					Bleeding = false
				end)
			end)
		end
	end)
end)

Works so well with ragdolls. I love it.


1 Like