How to detect when player camera is underwater

For the final time I’d urge anyone including you to please please read my post!
I don’t know how many times I’ve said this but I don’t want to use readvoxels. It’s inaccurate!

1 Like

this is the copied code. Here it is:

local Work = game.Workspace
local Camera = Work.CurrentCamera
local Terrain = Work.Terrain

function CFrameToVector3(cframe)
	return Vector3.new(cframe.X,cframe.Y,cframe.Z)
end

while wait() do
	local REGION = Terrain:ReadVoxels(Region3.new(CFrameToVector3(Camera.CFrame)-Vector3.new(.0001,.0001,.0001),CFrameToVector3(Camera.CFrame)):ExpandToGrid(4),4)
	for Number, Information in pairs(REGION[1][1]) do
		for i,v in pairs(Work:GetDescendants()) do
			if Information == Enum.Material.Water and v:IsA("EqualizerSoundEffect") and v.Name == "Muffle" then
				v.Enabled = true
			elseif v:IsA("EqualizerSoundEffect") and v.Name == "Muffle" then
				v.Enabled = false
			end
		end
	end
end

Put this script inside StarterPlayerScript.
For sound, you can put it anywhere inside workspace but you need to add a EqualizerSoundEffect and name it muffle.

EqualizerSoundEffect Set up:
High Gain:-50
Low Gain:0
Mid Gain:-50
Enabled = false

From youtube.

1 Like
while true do
	local position = workspace.CurrentCamera.CFrame.Position
	
	local minimum = Vector3.new(position.X + 0.01, position.Y + 0.01, position.Z + 0.01)
	local maximum = Vector3.new(position.X - 0.01, position.Y - 0.01, position.Z - 0.01)
	local region = Region3.new(maximum, minimum)
	region = region:ExpandToGrid(4)
	
	if region then
		local material = workspace.Terrain:ReadVoxels(region, 4)
		if material[1][1][1] == Enum.Material.Water then
			print("in water")
		else
			print("not in water")
		end
	end
	wait(0.1)
end

this is the method I use, and it works perfectly fine, and in my case, it does account for waves, it prints in water when I’m in the waves, and prints not in water when I’m not in the water

edit: if you’re using more “agressive” waves, then this might not work, I’m using this for rather “calm” waves, I’ve set the waterwavesize to 0.35

1 Like

Exactly, thats what i’m trying to say,

You can also change the size of the region of the camera to be smaller to get more accurate reading off the the camera part or even resizing the camera part itself to being smaller so it more accurately responds to it.

The current size of the camera is roughly 2 by 4 in scale, if you scale that down by like i don’t know 2x or 3x it could get a more accurate reading of the part touching the terrain part.

1 Like

Waterwavesize is .35

1 Like

I have been looking for a solution to something like this for a while now, and it really is more complicated than it should be. I would suggest looking more into raycasting if the other solutions aren’t working for you.

On the other hand, Roblox honestly needs to redesign terrain water completely to make wave formations more realistic and include some sort of function that automatically detects when your camera clips underwater so something like this wouldn’t be so complicated to make accurately. I made something similar to this as a feature request in case you’re interested.

2 Likes

I completely agree. I’d also go on to say that water should rise a bit on coastal areas based on a value a person can set
image

2 Likes

I only briefly read the post, and none of the replies so I’m sorry if someone already said this.

Couldn’t you accomplish this with raycasting? Something like this:

local module = {}

local rayDistance = 5 -- you can change this if you want
local cam = game.Workspace.CurrentCamera
local userInput = game:GetService("UserInputService")
local player = game:GetService("Players").LocalPlayer

function module:CastWithWhitelist(whiteList)
	local mousePos = userInput:GetMouseLocation()
	local viewportMouseRay = cam:ViewportPointToRay(mousePos.X, mousePos.Y)
	local shortRayForWater =  Ray.new(viewportMouseRay.Origin, viewportMouseRay.Direction * rayDistance)
	return workspace:FindPartOnRayWithWhitelist(shortRayForWater, whiteList, false, false) --Make sure this last one is false
end

return module

I’ve never tried to detect water before, but it is a parameter for raycasting. This will cast a ray from the camera and if it hits water, should return it. I think you also need to pass this module {workspace.Terrain}.

1 Like

Are you sure if this can detect and take into account waves using educated guesses with equations as opposed to just brainless guessing and voxel reading? I’m 60% sure raycasts aren’t accurate with terrain unless it’s raycasted with a mouse

1 Like

I’m not 100% sure, no. It’s just my best guess. It’s a pretty quick test though, call this module passing it {workspace.Terrain} and use the returned value to test it.

[EDIT] You’ll probably want to change the distance so it isn’t capturing water when your camera is 5 studs above it.

1 Like

It seems the module has an error?
image
Distance is not defined
image

1 Like

Oh whoops! I was using some of my code as an example, so I forgot to delete that out. I’ll edit the code for you, but just replace distance with rayDistance

1 Like

I’m curious if this solved your problem or not, did it work?

The easy test is to spawn your character underwater and call this module passing it the terrain like this:

wait(5)--Lazy/quick way to wait for the player to load
local foundWater = yourRaycastingModule:CastWithWhitelist({workspace.Terrain})
if foundWater then
	print("Camera is underwater")
else
	print("Camera is not underwater")
end

If it works, then you’ll have to figure out a few more things before it will work the way you want it to. First, you’ll need to filter it even more so that it only detects water and not other terrain. Second, you’ll need to figure out when and where to call this module. Since it has to do with the camera, you’ll want to call it every frame. I suggest using a region3(or maybe hook it if the character state is swimming) as an event signal to start checking it every frame so that you aren’t needlessly checking for water in areas where there is no water. Lastly, you should change rayDistance to something less (I have absolutely no idea how small a ray can be, but I would experiment with things like 0.5, 0.1, 1 etc…). It should definitely work with a value of 1 since that’s the beginning length of the ray’s direction.

1 Like


image

It seems to be giving me another error.

Also, how is it that one filters out terrain? Is it as simple as checking the material?

1 Like

I’m not exactly sure what is causing that error. Maybe you’ll have to wait for the character to load instead of the wait(5).

As for your question, it should be as simple as doing this:

local object, position, normal, material = workspace:CastWithWhitelist({workspace.Terrain})
if object and object.Name == "Terrain" and material  == Enum.Material.Water then
	print("Camera is underwater")
end
1 Like

Made this script for a game back in 2017

game:GetService("RunService").RenderStepped:Connect(function(step)
	local pos = workspace.Terrain:WorldToCell(workspace.CurrentCamera.CoordinateFrame.p)
	local isInWater = workspace.Terrain:GetWaterCell(pos.X, (pos.Y), pos.z)
	
	if isInWater == true then
		-- if the camera is underwater, do something
	else
		-- if the camera is not underwater, do something else
	end
end)

This doesn’t account for waves, but using the formula above, could possibly work

2 Likes

Hey I’ve been reading this thread since it started but I couldn’t reply as I wasn’t a member yet :grimacing:
Not to be cold or anything but I’m pretty sure this is impossible due to a lack of functions for Roblox water.
Also like I have literally never seen this done before. In the game you showed roblox waterpark I went around testing the system but it seemed to just all be triggers located in the water. As there are some pools of water that don’t work unless I put my camera further in and then stopped working once my camera went further into the water.
Personally, if I was you I’d make my own custom water or something.
Obviously I’m not a professional for all I know maybe there is a way to detect if something is colliding with water but I’m pretty sure it’s just not possible to do it accurately.

3 Likes
while wait() do
	local pos = cam.CFrame.Position

	local min = Vector3.new(pos.X + offset,pos.Y + offset, pos.Z + offset)
	local max = Vector3.new(pos.X - offset,pos.Y - offset, pos.Z - offset)
	
	local region = Region3.new(max, min)
	region = region:ExpandToGrid(4)

	if region then
		local material = game.Workspace.Terrain:ReadVoxels(region, 4)
		if material[1][1][1] == Enum.Material.Water then
end
end

this is a great idea, if u can detect water using overlap params then that would be sufficient, so the part size would be 0.1, 0.1, 0.1 or just a small circle using overlap params but if overlap cant detect water which i think is true then gg, maybe read voxels but idk if that method is delayed cuz of how mid the terrain optimizations are

You bumped it. :sad:
Also, I’m not an advanced scripter, so, I dont know what to say.
But thanks for giving the help!
Dev was here in 2024

i know im not the owner of this