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

I need to make an oxygen bar in my game that decreases when you go in water and increases when you come out of water. The only thing is that I want to do this by reading terrain voxels since you can gain oxygen just by getting your head out of the water which i would need to do for my game. (a beach game) The only problem is that i don’t know how to make and script this. Does anyone know how to do this?

2 Likes

Hm, maybe use raycasting? I think that’s your best option.

touching terrain parts check??

This worked perfectly for me! The “oxygen” value will increase when they are above water and decrease when they are below water.

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)

In the following video, look at the output on the left. You can see the oxygen increase when I come out of the water and decrease when I become submerged:
https://gyazo.com/a526b1333c207e12194a9d07149e5779

Would I use this with like a GUI or does it work without one

Yes, you can change the size of a gui or something.

Ok so if I use it with a GUI then where would I put this script

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.