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 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:
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
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”,
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.
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
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
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)