How would I script an oxygen bar by reading terrain voxels?

Oh neat, check my free model. It’s pretty old and I’m uncertain of it’s functionality but I do know it was in a state of working a few years ago at minimum.

You can grab whatever assets I have with the model, but here is some source code I just now pulled from it :slight_smile: This should operate an Oxygen Bar when your head (or however accurate you can check w/ voxels) exits Terrain Water.

local SurfaceGui = script.Parent
local OA = SurfaceGui.Frame.OxygenBar
local Plr = game.Players.LocalPlayer

repeat wait() until Plr.Character

local Hum = Plr.Character:WaitForChild("Humanoid")
local cOxygen, MaxOxygen= 20, 20 -- 1 = 1 second of air
local Swim = false
local UIS = game:GetService("UserInputService")
local T = game.Workspace:WaitForChild("Terrain")
local Ended = true

function UnderWaterCheck(Head)
	if Head then
		local InWater = true
		local Offset = Head.Size / 2
		local P1, P2 = (Head.Position - Vector3.new(1, 0, 0)) -  Offset,  (Head.Position + Vector3.new(1, 0, 0)) + Offset
		local R3 = Region3.new(P1, P2):ExpandToGrid(4)
		
		local Tbl = T:ReadVoxels(R3, 4)
		
		for _, Material in pairs(Tbl[2][1]) do
			if Material == Enum.Material.Air then
				InWater = false
			end
		end
		
		return InWater
	end
end

Hum.Swimming:connect(function()
	if not Swim then
		SurfaceGui.Frame.Visible = true
		Swim = true
	end
	
	UIS.InputBegan:connect(function(Pressed)
		if Swim and Pressed.KeyCode == Enum.KeyCode.Space then
			Ended = false

			UIS.InputEnded:connect(function(Released)
				if Swim and Released.KeyCode == Enum.KeyCode.Space then
					Ended = true
				end
			end)
			
			repeat wait()
				if not UnderWaterCheck(Hum.Parent.Head) and Swim then
					Swim = false 
					SurfaceGui.Frame.Visible = false
					cOxygen = MaxOxygen
					OA:TweenSize(UDim2.new(cOxygen/MaxOxygen,0, 1,0), "Out", "Linear", .3, true)
					Ended = true
				end
			until Hum.Health == 0 or Ended or not Swim
		end
	end)
	
	Hum.Running:connect(function()
		if Swim then
			SurfaceGui.Frame.Visible = false
			Swim = false
			cOxygen = MaxOxygen
		end
	end)
end)

while wait(1) do
	OA:TweenSize(UDim2.new(cOxygen/MaxOxygen,0, 1,0), "Out", "Linear", 1, true)
	
	if Swim then
		if cOxygen - 1 > 0 then
			cOxygen = cOxygen - 1
		else
			Hum.Health = 0 --Kill the player, no oxygen left
		end
	else
		if cOxygen ~= MaxOxygen and Hum.Health > 0 then
			cOxygen = MaxOxygen
		end
	end
end

Put the other script in ServerScriptService, then put the following script as the child of a textlabel.

Here is a picture of the explorer:
Screen Shot 2021-03-11 at 8.12.22 PM
The "Bar textlabel’ should be green and the “Background textlabel” should be grey. The “OxygenText Textlabel” should have a background transparency of 1.

LOCAL SCRIPT (This must be a direct child of the “Background” textlabel.)

local player = game.Players.LocalPlayer

local background = script.Parent
local Bar = background:WaitForChild("Bar")
local OxygenText = background:WaitForChild("OxygenText")

OxygenText.Size = UDim2.new(1,0,1,0)

while wait(0.3) do
      Bar:TweenSize(UDim2.new(player.OxygenValue.Value/1000,0,1,0))
      OxygenText.Text = player.OxygenValue.Value.."/1000"
end

I did it and it’s not working I’ll show you the explorer and the scripts:
Screen Shot 2021-03-11 at 8.45.58 PM

This doesn’t work for me because even when your head comes out of the water you still lose oxygen

You’ll likely have to jump out higher then, Voxels don’t offer a high resolution for checks like you want. However I do believe this is the method for “an oxygen bar by reading terrain voxels”,

1 Like

Oh because I saw in a post someone was able to do what I wanted to do ill link the post I just dont know how to do it the way they did it
https://devforum.roblox.com/t/how-can-i-get-an-oxygen-bar/1083223
Theres also a video in that post showing it working

I’ve seen this question many times in DevForum. You tried to search for an answer?
The best way I found to do it, is by Reading Voxels on top of the Character’s Head.
You need to use
Terrain:WorldToCell , Region3 and Terrain:ReadVoxels
Once you read the Voxels material over the head of the character, if its water, drain oxygen, if its air increase oxygen.
Took me many tests to achieve it, I tried raycasting and many weird creative ways to do it… But I found Reading Voxels works perfect.

Instead of reading terrain voxels, I’d suggest reading the humanoids state, and detect if the player is swimming or not, that way you don’t have to deal with the inconsistency of terrain checks.

2 Likes

If I did that I don’t think it would work because if you’re above water wouldn’t you still be using the swimming animation

Sorry for not responding, i was away. I know this works because it worked for me. The script that I first gave you is supposed to be a server script in server script service. This is the script that should be the local script:

local player = game.Players.LocalPlayer

local background = script.Parent
local Bar = background:WaitForChild("Bar")
local OxygenText = background:WaitForChild("OxygenText")

OxygenText.Size = UDim2.new(1,0,1,0)

while wait(0.3) do
      Bar:TweenSize(UDim2.new(player.OxygenValue.Value/1000,0,1,0))
      OxygenText.Text = player.OxygenValue.Value.."/1000"
end

yea ik thats how I have it and it isnt working

Any errors in the output or anything?

Ill check the output I also designed the GUI a different way but the names are the same so I dont see how that would be an issue

1 Like

On my end I only tested the oxygen part, not the GUI code. I’ll test it and fix anything that is wrong then I’ll get back to you.


Here’s the output I don’t see anything regarding the oxygen script

It says there was an error on line 23 of a server script, that is also unrelated right?

I realized I had an error in the server script, can you recopy and paste that code and see if it works now?

This is the result that I got:
https://gyazo.com/9e29473c61b17c016e78e6d3b60dff5b

Maybe just replace the local script code with this?

local player = game.Players.LocalPlayer

local background = script.Parent
local Bar = background:WaitForChild("Bar")
local OxygenText = background:WaitForChild("OxygenText")

OxygenText.Size = UDim2.new(1,0,1,0)

local oxyValue = player:WaitForChild("OxygenValue")

while wait(0.3) do
	Bar:TweenSize(UDim2.new(player.OxygenValue.Value/1000,0,1,0), "Out", "Quad", 0.3)
	OxygenText.Text = "Oxygen "..player.OxygenValue.Value.."/1000"
end
1 Like


I get this error message for the server script

Try replacing the server script with this:

game.Players.PlayerAdded:Connect(function(player)
	local oxygen = Instance.new("IntValue", player)
    oxygen.Value = 1000
    oxygen.Name = "OxygenValue"
	local increaseRate = 3 --how fast it increases
	local decreseRate = 2 --how fast it decreases
    local maxOxygen = 1000 --change accordingly

	while wait() do
		local char = player.Character or player.CharacterAdded:Wait()
		local castRay = Ray.new(char.Head.Position, Vector3.new(0,-10,0))
		local whiteList = {char}
		local hit, hitposition = workspace:FindPartOnRayWithIgnoreList(castRay, whiteList)
		local underwater = false
		if not hit then
			local castRay2 = Ray.new(char.Head.Position + Vector3.new(0,1000,0), Vector3.new(0,-1000,0))
			local hit2, hit2position = workspace:FindPartOnRayWithIgnoreList(castRay2, whiteList)
			if hit2 then
				underwater = true
			end
		end
		if underwater == false then
			oxygen.Value = math.clamp(oxygen.Value + increaseRate, 0, maxOxygen)
		else
			oxygen.Value = math.clamp(oxygen.Value - decreseRate, 0, maxOxygen)
		end
		print(oxygen)
	end
end)
1 Like


this happens but the oxygen bar doesnt do anything
also the oxygen level doesnt go down either

If you change the print to,

print(oxygen.Value)

Does the value change?