ZonePlus Issue? - Custom Water Does Not Work on Ps4

@ForeverHD


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.

@Ro_JJam - Could you provide any Ps4, or PlayStation, specific information that might help?

Maybe how assets load or work internally are different based on upload/pushing date of an experience/game or asset files are updated or something outside of that?

I feels like this issue is just magically there or not, but something might help. :pray:

Hello, I have made a new Zone module which only works for detecting when players enter/exit a BasePart. It should work on both client and server side:

ZoneModule - Creator Store

If you decide to use it and the problem doesn’t go away, it is likely an issue with the Touched event

Example usage:

local ZoneModule = require(game:GetService("ReplicatedStorage"):WaitForChild("ZoneModule"))

local Zone = ZoneModule.new( BasePart )

Zone.Entered = function(plr)
	
	print(plr.Name .. " entered")	
end

Zone.Exited = function(plr)

	print(plr.Name .. " exited")
end

I have no idea why it would only be messing up for PS4 users…

1 Like

The reason I used ZonePlus in the first place is that Roblox’s built-in TouchEnded event is unreliable and unintuitive.

Here are some forum threads showing this is an issue:

In short, it just doesn’t work, and thus, respectfully, your module wouldn’t solve the issue.

Throughout the threads, we were advised to use Region3-related methods like FindPartsInRegion3 , which are “deprecated, and should not be used in new work.” It was overall superseded by this update: Introducing OverlapParams - New Spatial Query API. ZonePlus 3.2 uses that API.


I assume that somewhere in that use of the API, either in ZonePlus or Roblox’s internal code, breaks after a certain amount of time (from last update or how something loading-wise works?), and only on PS4.

From here, I guess I’ll look into understanding the API and ZonePlus as a whole, make a module myself, and hope it doesn’t break. The issue is, though, I’ll likely never know if it’s 100% fixed, no matter what I do, as it takes months for it to break again. :person_shrugging:

I just updated the zone module to use a Heartbeat connection and also GetPartsInPart and OverlapParams (instead of Touched). It’s only around 100 lines long, and functions the same as I told you previously

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.