How do I make an oxygen system for my game?

Hello, Dev’s!

I’m making a game that takes place on the moon, and I’m doing a good amount of progress on it.
recently, a friend of mine made me a GUI script, which is an oxygen bar GUI that drops whenever the player touches the “sand” terrain (the material that I use to build the terrain in my game), this was meant to be a way for the player to lose oxygen whenever it goes outside the moon surface, but I realize that it’s a bad idea since players can jump over the sand before they lose oxygen (aka cheat the system).

I thought of making the script function only when a player is touching a part called “NoOxygen” for example, but since I’m more of a builder than a scripter, I couldn’t find a way to make it work when touching a part, my scripting skills are limited and my friend can not help me since he’s busy making his game and etc.

image
image

local Player = game.Players.LocalPlayer

local BreathUI = script:WaitForChild("OxygenGui")
local Gui = nil

local Running = false

local Percent = 100
local InWater = false

local Func = {}
local GuiTab = {}

-- Functions --

Func.checkFor = function(parent, ...)
	for i, objName in pairs({...}) do
		if not parent:findFirstChild(objName) then
			return false
		end
	end
	return true
end

Func.Gather = function(Objects, recurse, tabby, Type)
	for i, Object in pairs(Objects:GetChildren()) do
		if Object:IsA(Type) then
			table.insert(tabby, Object)
		end
		if recurse then
			Func.Gather(Object, true, tabby, Type)
		end
	end
	return tabby
end

Func.CheckTouch = function(Pos, Enam)
	local cellLocation = Workspace.Terrain:WorldToCellPreferSolid(Pos)
	local cellMaterial = Workspace.Terrain:GetCell(cellLocation.X, cellLocation.Y, cellLocation.Z)
	if cellMaterial == Enam then
		return true
	end
	return false
end

Func.HandleGui = function(bool)
	if bool then
		local Gui = BreathUI:Clone()
		Gui.Parent = Player.PlayerGui
		
		table.insert(GuiTab, Gui)
		
		return Gui
	else
		for i, g in pairs(GuiTab) do
			g:Destroy()
		end
		GuiTab = {}
	end
end

Func.HandleUIAnimation = function(gui, up, bool)
	if not Running then
		local x, y, z, t
		
		if up then
			x, y, z, t = Percent, 100, 1, 0.001 -- How many seconds between each percentage of air is gained.
		else
			x, y, z = Percent, 0, -1, 0.1 -- How many seconds between each percentage of air is lost.
		end
		
		local c = coroutine.create(function()
			Running = true	
		
			for i = x, y, z do
				
				if bool ~= InWater then
					break
				end
				
				wait(t)
				local num = i * 0.01
				gui.OxFrame.OxBar.Size = UDim2.new(num, 0, 1, 0)
				gui.OxFrame.OxLabel.Text = "Oxygen "..i.."% "
				Percent = i
			end
			
			Running = false
		end)
		
		coroutine.resume(c)
	end
end

Func.HandleHealth = coroutine.wrap(function()
	while true do
		if InWater and Percent <= 0 then
			wait(0.5) --How many seconds between each time health is taken away for no oxygen.
			Player.Character.Humanoid:TakeDamage(5) -- How much health is taken away for no oxygen.
		else
			wait()
		end
	end
end)

-- Run --

repeat wait() until Func.checkFor(Player.Character, "LeftFoot", "LeftHand", "LeftLowerArm", "Head", "LeftLowerLeg", "LeftUpperArm", "Humanoid", "LeftUpperLeg", "LowerTorso", "RightFoot", "RightHand", "RightLowerArm", "RightLowerLeg", "RightUpperArm", "RightUpperLeg", "UpperTorso")

Gui = Func.HandleGui(true)

Func.HandleHealth()

while wait() do
	if Func.CheckTouch(Player.Character.RightFoot.Position + Vector3.new(0, 2.5, 0), Enum.CellMaterial.Sand) then	
		InWater = true
		Func.HandleUIAnimation(Gui, false, true)
	else
		InWater = false
		if Percent < 100 then
			Func.HandleUIAnimation(Gui, true, false)
		end
	end
end

I hope to see your solution, Thank You :+1:

1 Like

You should look into this module, and the rest should be easy.

1 Like

Make a huge invisible part that is Sand, that will act like a zone of no oxygen

1 Like

You could use Humanoid.FloorMaterial in order to detect what material a player is standing on.

For example:

if Character.Humanoid.FloorMaterial == Enum.Material.Sand then

end
2 Likes

That wouldn’t fix his problem, since his script already functions like that

1 Like

The script only functions when the player touches the terrain, and of course, there isn’t a “CanCollide” or a transparency option for terrain, at least not yet.

1 Like

Here is an open-sourced YouTube video’s Model. This might help you out getting started.

1 Like

In that case, he can use Raycasting to detect the material that is below the player.

Ray1 = Ray.new(character.HumanoidRootPart.Position, Vector3.new(0,-5,0))

Part = workspace:FindPartOnRay(Ray1)

if Part ~= nil then
    if Part.Material == Enum.Material.Sand then
  
    end
end
2 Likes

i’ve watched the video, and it only seems like the same script as mine, except the player only loses oxygen when touching terrain water

2 Likes

This might help tho, im not sure, but i’ll try it

1 Like

I’ve inserted the script, and it doesnt seem to work, there is an error in the outpot:
image
here’s your script:
image

1 Like

You did not change “Character” to the actually reference of the character. Make sure that Character actually equals the player character on line 125. For instance, game.Players.LocalPlayer.Character.

1 Like

from my understanding, I’ve added, Player.Character because of the reference as you said.

image
image

No error messages, but there is no print in the output, hopefully, I’m not doing something wrong.

1 Like

Where are you implementing this code?

local Player = game.Players.LocalPlayer

local BreathUI = script:WaitForChild("OxygenGui")
local Gui = nil

local Running = false

local Percent = 100
local InWater = false

local Func = {}
local GuiTab = {}

-- Functions --

Func.checkFor = function(parent, ...)
	for i, objName in pairs({...}) do
		if not parent:findFirstChild(objName) then
			return false
		end
	end
	return true
end

Func.Gather = function(Objects, recurse, tabby, Type)
	for i, Object in pairs(Objects:GetChildren()) do
		if Object:IsA(Type) then
			table.insert(tabby, Object)
		end
		if recurse then
			Func.Gather(Object, true, tabby, Type)
		end
	end
	return tabby
end

Func.CheckTouch = function(Pos, Enam)
	local cellLocation = Workspace.Terrain:WorldToCellPreferSolid(Pos)
	local cellMaterial = Workspace.Terrain:GetCell(cellLocation.X, cellLocation.Y, cellLocation.Z)
	if cellMaterial == Enam then
		return true
	end
	return false
end

Func.HandleGui = function(bool)
	if bool then
		local Gui = BreathUI:Clone()
		Gui.Parent = Player.PlayerGui
		
		table.insert(GuiTab, Gui)
		
		return Gui
	else
		for i, g in pairs(GuiTab) do
			g:Destroy()
		end
		GuiTab = {}
	end
end

Func.HandleUIAnimation = function(gui, up, bool)
	if not Running then
		local x, y, z, t
		
		if up then
			x, y, z, t = Percent, 100, 1, 0.001 -- How many seconds between each percentage of air is gained.
		else
			x, y, z = Percent, 0, -1, 0.1 -- How many seconds between each percentage of air is lost.
		end
		
		local c = coroutine.create(function()
			Running = true	
		
			for i = x, y, z do
				
				if bool ~= InWater then
					break
				end
				
				wait(t)
				local num = i * 0.01
				gui.OxFrame.OxBar.Size = UDim2.new(num, 0, 1, 0)
				gui.OxFrame.OxLabel.Text = "Oxygen "..i.."% "
				Percent = i
			end
			
			Running = false
		end)
		
		coroutine.resume(c)
	end
end

Func.HandleHealth = coroutine.wrap(function()
	while true do
		if InWater and Percent <= 0 then
			wait(0.5) --How many seconds between each time health is taken away for no oxygen.
			Player.Character.Humanoid:TakeDamage(5) -- How much health is taken away for no oxygen.
		else
			wait()
		end
	end
end)

-- Run --

repeat wait() until Func.checkFor(Player.Character, "LeftFoot", "LeftHand", "LeftLowerArm", "Head", "LeftLowerLeg", "LeftUpperArm", "Humanoid", "LeftUpperLeg", "LowerTorso", "RightFoot", "RightHand", "RightLowerArm", "RightLowerLeg", "RightUpperArm", "RightUpperLeg", "UpperTorso")

Gui = Func.HandleGui(true)

Func.HandleHealth()

while wait() do
	if Func.CheckTouch(Player.Character.RightFoot.Position + Vector3.new(0, 2.5, 0), Enum.CellMaterial.Sand) then	
		InWater = true
		Func.HandleUIAnimation(Gui, false, true)
	else
		InWater = false
		if Percent < 100 then
			Func.HandleUIAnimation(Gui, true, false)
		end
	end
	
local Ray1 = Ray.new(Player.Character.HumanoidRootPart. Position, Vector3.new(0,-5,0))

local Part = workspace:FindPartOnRay(Ray1)
	
if Part ~= nil then
	    if Part.Material == Enum.Material.Sand then
		print("THERE IS A FOUNCTION!")
  
    end
end
end
1 Like

Try this:

local Ray1 = Ray.new(Player.Character.HumanoidRootPart. Position, Vector3.new(0,-100,0))

local Part, Position, Normal, Material = workspace:FindPartOnRay(Ray1)
	
if Material == Enum.Material.Sand then
	print("THERE IS A FOUNCTION!")
end
2 Likes

image
the message has been printed, but it only works with sand terrain, do you have any idea how to make the message print with a normal sand Part/Brick?

Of course! I thought you wanted it to be specifically for sand. Just remove the if statement.

local Ray1 = Ray.new(Player.Character.HumanoidRootPart. Position, Vector3.new(0,-100,0))

local Part, Position, Normal, Material = workspace:FindPartOnRay(Ray1)
if Material ~= Enum.Material.Water and Material ~= Enum.Material.Air then
print("THERE IS A FOUNCTION!")
end
1 Like

I was messing around with a drowning/oxygen system
Drowning.rbxl (24.4 KB)

ServerScriptService-Script

game.Players.PlayerAdded:Connect(function(player)
	local Stats = Instance.new("Folder", player)
	Stats.Name = "Stats"
	local Oxygen = Instance.new("NumberValue", Stats)
	Oxygen.Name = "Oxygen"
	Oxygen.Value = 100
	local Underwater = Instance.new("BoolValue", Oxygen)
	Underwater.Name = "Underwater"
	Underwater.Changed:Connect(function()
		if Underwater.Value == true then
			repeat
				wait(0.05)
				if Oxygen.Value > 0 then
					Oxygen.Value -= 0.25
				elseif Oxygen.Value < 0 then
					Oxygen.Value = 0
				elseif Oxygen.Value == 0 then
					local Character = Underwater.Parent.Parent.Parent.Character or Underwater.Parent.Parent.Parent.CharacterAdded:Wait()
					if Character ~= nil then
						if Character.Humanoid.Health > 0 then
							Character:WaitForChild("Humanoid"):TakeDamage(0.5)
							local Sound = Instance.new("Sound", Character.Head)
							Sound.SoundId = "rbxassetid://1129547534"
							Sound.Volume = 0.25
							Sound:Play()
							game:GetService("Debris"):AddItem(Sound, Sound.TimeLength)
						end
					end
				end
			until
				Underwater.Value == false
		else
			repeat
				wait(0.05)
				if Oxygen.Value < 100 then
					Oxygen.Value += 0.15
				end
				if Oxygen.Value > 100 then
					Oxygen.Value = 100
				end
			until
				Underwater.Value == true
		end
	end)
	player.CharacterAdded:Connect(function(Character)
		player.Stats.Oxygen.Value = 100
	end)
end)



for i,v in pairs(game.Workspace:GetChildren()) do
	if v.Name == "Water" then
		v.Touched:Connect(function(t)
			local TPlayer = game.Players:GetPlayerFromCharacter(t.Parent)
			if TPlayer ~= nil and t.Name == "HumanoidRootPart" then
				local PlayerCharacter = TPlayer.Character or TPlayer.CharacterAdded:Wait()
				if PlayerCharacter ~= nil then
					local Sound = Instance.new("Sound", PlayerCharacter.Head)
					Sound.SoundId = "rbxassetid://3750961471"
					Sound.Volume = 0.5
					Sound:Play()
					game:GetService("Debris"):AddItem(Sound, Sound.TimeLength)
				end
			end
		end)
	end
end

while true do
	wait()
	for q, player in pairs(game.Players:GetPlayers()) do
		local PlayerCharacter = player.Character or player.CharacterAdded:Wait()
		if PlayerCharacter ~= nil then
			local PlayerHead = PlayerCharacter:WaitForChild("Head")
			if PlayerHead ~= nil then
				local Water = {}
				for i,v in pairs(game.Workspace:GetChildren()) do
					if v.Name == "Water" then
						table.insert(Water, v)
					end
				end
				local Underwater = false
				for i,v in pairs(Water) do
					local Region = Region3.new(v.Position - v.Size/2, v.Position + v.Size/2)
					local Parts = workspace:FindPartsInRegion3(Region)
					for x,c in pairs(Parts) do
						if c == PlayerHead then
							Underwater = true
						end
					end
				end
				if Underwater then
					player:WaitForChild("Stats"):WaitForChild("Oxygen"):WaitForChild("Underwater").Value = true
				else
					player:WaitForChild("Stats"):WaitForChild("Oxygen"):WaitForChild("Underwater").Value = false
				end
			end
		end
	end
end

Gui-Localscript

for i,v in pairs(workspace.Camera:GetChildren()) do
	if v:IsA("Sound") then
		v:Stop()
		v:Destroy()
	end
end
local Oxygen = game.Players.LocalPlayer:WaitForChild("Stats"):WaitForChild("Oxygen")
local UnderwaterBool = Oxygen:WaitForChild("Underwater")
local LastUnderwaterSound = nil
Oxygen.Changed:Connect(function()
	script.Parent.Frame.TextLabel.Text = "Oxygen: "..math.floor(Oxygen.Value).."%"
	script.Parent.Frame.Oxygen.Size = UDim2.fromScale(1 * Oxygen.Value/100,1)
	if UnderwaterBool.Value == true then
		if LastUnderwaterSound == nil then
			local Sound = Instance.new("Sound")
			Sound.SoundId = "rbxassetid://2539296793"
			Sound.Volume = 0.5
			Sound.Looped = true
			Sound.Parent = workspace.Camera
			LastUnderwaterSound = Sound
			Sound:Play()
		else
			LastUnderwaterSound.Volume = 0.5
		end
	else
		if LastUnderwaterSound ~= nil then
			LastUnderwaterSound.Volume = 0
		end
	end
end)
2 Likes

it seems to be working, but the message keeps printing non-stop before even touching the part, and I’m guessing that’s because of the “air” material, in any case, I think I can fix it by changing the material and etc, thx for your time, I think I can handle it from here. :+1:

1 Like