Summary
Custom water in my game breaks specifically for PlayStation 4 players. I was able to reproduce the issue on PS4 when it happened to players, but I can’t remember if it also broke on other platforms. Just nobody on any other platform has reported the issue.
Other Info:
On 7/20/2024, a player sent me the following feedback:
The swimming mechanique is bugged. Whenever the character interacts with the tower body of water, it freezes at the border and cannot be moved at all.
On 6/12/2025, a brother of mine played my game, and he told me that the water wasn’t working.
In both cases, I pushed an update, and that fixed the issue. The thing is, nothing I’d change would have anything to do with the water code at all. I’d fix a small UI issue or add a sound effect, completely unrelated to water itself. I didn’t touch it, but now the water works perfectly fine.
Other notes:
- Nobody on any other platform has reported this issue.
- I use Collectionservice with Tags for the custom water.
- I have streamingEnabled on
- The Model’s streamingMode is Atomic
The Actual Issue?
I use ZonePlus version 3.2 to know if a player is in or out of the custom water. Specifically, I use the module on the client with playerEntered and playerExited events. The module has not been updated since September 7th, 2021, according to the changelog on their site. Half the issues made/filed on the GitHub repo are not solved/closed, and it doesn’t look too maintained, so idk if I should go through all this effort.
The actual issue, though, might not even be the resource/module. Roblox’s engine internally may be to blame, based on code that is in ZonePlus, and no code of my own.
No errors or warnings in the console. It just sometimes breaks for PS4 players. I used Ctrl + shift + F to search for words like “ps4”, “console”, “gamepad”, “PlayStation”, and nothing came up. To go through everything, that’s at least 2,000 lines of code with the internal dependencies and whatnot. That’s with the added, no way to accurately reproduce the issue.
Repro File?
I’ve tried to make a repro file, but the nature of the issue and the complexity of my project can make that difficult. I have no guarantee of the issue being reproducible.
I tried to confine a baseplate file to ZonePlus and all code that has to deal directly with the water. I closed the tab by accident, cause I had two tabs, and didn’t save the file, and there’s no auto save. I’m not remaking it as I spent hours on it and still couldn’t get all the code I needed to sift through anyway. Custom Animate script and other stuff connect to it.
I don’t feel comfortable giving my 4+ year project file to the public, but if ForeverHD or Roblox staff want to take a look, I can send it via private message or the private section of a bug support form.
Here is what I can show, though:
local CollectionService = game:GetService("CollectionService")
local MySoundService = game.ReplicatedStorage:WaitForChild("MySoundService")
local Toggle_PlayerInWaterTheyCanActivelySwimIn_RemoteEvent = game.Workspace.RemoteEventsFolder.Toggle_PlayerInWaterTheyCanActivelySwimInState
local Zone = require(game:GetService("ReplicatedStorage").Zone)
local splashSFX = MySoundService.SoundEffects:WaitForChild("SplashIntoWater_SFX")
local playerIsInWater = false
local is_DynamicWater_Enabled = true
local worldPlayerIsCurrentlyIn = nil
local objectsLoadedIn_Table = {}
local tagForThisScript = "WaterTag"
local localPlayer = game.Players.LocalPlayer
local function makeObjectModelWhatItIs(objectModel_v)
local zone1 = Zone.new(objectModel_v:WaitForChild("hitbox"))
--print("Zone made!")
zone1.playerEntered:Connect(function(player)
if playerIsInWater == false and player == game.Players.LocalPlayer then
--print("PLAYER ENTERED WATER!")
--print("player entered water")
playerIsInWater = true
splashSFX.TimePosition = 0.35
splashSFX:Play()
if objectModel_v.isThisShallowWater.Value == false then
--print("not in shallow water!")
--print("player NOT in shallow water and in that water")
local playerInWater_ToggleState = true
Toggle_PlayerInWaterTheyCanActivelySwimIn_RemoteEvent:FireServer(playerInWater_ToggleState)
end
end
end)
zone1.playerExited:Connect(function(player)
--print("PLAYER EXITED WATER!")
local playerInWater_ToggleState = false
Toggle_PlayerInWaterTheyCanActivelySwimIn_RemoteEvent:FireServer(playerInWater_ToggleState)
playerIsInWater = false
end)
local worldThisWaterIsIn = objectModel_v.WorldThisObjectIsIn_StringValue.Value
local moveWaterTween = game:GetService("TweenService"):Create(objectModel_v:WaitForChild("water_WithTopCaustics").Texture, TweenInfo.new(10,Enum.EasingStyle.Quad,Enum.EasingDirection.InOut,-1,true), {OffsetStudsU = objectModel_v:WaitForChild("water_WithTopCaustics").Texture.OffsetStudsU + 10})
local function toggleDynamicWater(toggleState)
if toggleState == true then
moveWaterTween:Play()
else
if toggleState == false then
moveWaterTween:Pause()
end
end
end
local PlayerEnteredAWorld_RemoteEvent = game.Workspace.RemoteEventsFolder.PlayerEnteredAWorld_RemoteEvent
PlayerEnteredAWorld_RemoteEvent.OnClientEvent:Connect(function(whatWorldDidThePlayerEnter)
worldPlayerIsCurrentlyIn = whatWorldDidThePlayerEnter
if whatWorldDidThePlayerEnter == worldThisWaterIsIn and is_DynamicWater_Enabled == true then
toggleDynamicWater(true)
else
toggleDynamicWater(false)
end
end)
local ToggleDynamicWater_RemoteEvent = game.Workspace.RemoteEventsFolder.OptionsMenuToggles.Toggle_DynamicWater
ToggleDynamicWater_RemoteEvent.OnClientEvent:Connect(function(toggleState)
is_DynamicWater_Enabled = toggleState
toggleDynamicWater(toggleState)
end)
end
-- to get things already loaded in:
for _, taggedObject in pairs(CollectionService:GetTagged(tagForThisScript)) do
if table.find(objectsLoadedIn_Table, taggedObject) == nil then
table.insert(objectsLoadedIn_Table, taggedObject)
makeObjectModelWhatItIs(taggedObject)
end
end
-- to get future parts:
CollectionService:GetInstanceAddedSignal(tagForThisScript):Connect(function(newpart: Part)
--print("Check GetInstanceAddedSignal!")
--print(newpart:GetFullName())
if table.find(objectsLoadedIn_Table, newpart) == nil then
table.insert(objectsLoadedIn_Table, newpart)
makeObjectModelWhatItIs(newpart)
end
end)
-- to remove parts as they are unloaded:
CollectionService:GetInstanceRemovedSignal(tagForThisScript):Connect(function(oldpart: Part)
--print("Check GetInstanceRemovedSignal!")
local index = table.find(objectsLoadedIn_Table, oldpart)
if index then
table.remove(objectsLoadedIn_Table, index)
end
end)
Conclusion & Why I post this
I want to make sure this is a Roblox Engine issue with PS4, and not one with ZonePlus, before I make a bug report about it. If anyone can help, I’d really appreciate it, and if y’all need/want more info, lmk.