There’s no simple or built-in way.
You’ll have to write control logic that makes a VectorForce or LinearVelocity do the right thing instead. The problem with writing your own physics logic is that AFAIK there’s no way to update it as fast as or in lock-step with the built-in physics engine, so there’s always going to be a bit of jank. It’s definitely possible to get some nice systems though, especially for something like this where you don’t need it to be perfect from frame to frame.
It would really be more natural to use a VectorForce or LineForce, since we’re working with the buoyant force, but a LinearVelocity is fine too. The LineVelocity should just always be higher than the part’s actual velocity, otherwise the buoyant force will work to slow the part down, instead of speeding it up. That way we can just set the MaxForce to be the actual force we want.
You’ll want to move the body upwards when it’s submerged, and stop once it reaches the surface. IRL this happens because the buoyant force is proportional to the submerged volume, which is 0 when out of the water and equals the volume of the object when it’s fully submerged, and some fraction of that when it’s partly submerged.
For starters let’s just pretend like it’s only ever fully submerged or not at all submerged, and use a LinearVelocity to just apply a force when a body is submerged.
local TagS = game:GetService("CollectionService")
local RunS = game:GetService("RunService")
local WATER_DENSITY = 3
function submergedBuoyantForce(part: BasePart): number
local displacedVolume = part.Size.X * part.Size.Y * part.Size.Z
local partDensity = (part.CustomPhysicalProperties or PhysicalProperties.new(part.Material)).Density
local gravity = -game.Workspace.Gravity
return gravity * displacedVolume * (partDensity - WATER_DENSITY)
end
function setupBuoyantPart(part: BasePart)
local buoyancyAtt = Instance.new("Attachment")
buoyancyAtt.Parent = part
local lineVel = Instance.new("LinearVelocity")
lineVel.Parent = part
lineVel.RelativeTo = Enum.ActuatorRelativeTo.World
lineVel.VelocityConstraintMode = Enum.VelocityConstraintMode.Line
lineVel.LineDirection = Vector3.yAxis
lineVel.Attachment0 = buoyancyAtt
RunS.Heartbeat:Connect(function()
local y = part.Position.Y
if y < 0 then
lineVel.LineVelocity = 1000
lineVel.MaxForce = submergedBuoyantForce(part)
print(lineVel.MaxForce)
else
lineVel.LineVelocity = 0
lineVel.MaxForce = 0
end
end)
end
for _, tagged in ipairs(TagS:GetTagged("BuoyantPart")) do
setupBuoyantPart(tagged)
end
TagS:GetInstanceAddedSignal("BuoyantPart"):Connect(setupBuoyantPart)

It works, but has some issues. The bodies are oscillating violently instead of coming to rest at some partly- submerged depth. This probably has to do with the “all-or-nothing” submerged volume approach, but no matter what there also needs to be some dampening that causes the bodies to lose kinetic energy as they move through the viscous water, i.e. drag.
Drag is a force that opposes movement, and is proportional to the body’s coefficient of drag * it’s cross-sectional area in the direction of movement, which is complicated to determine because it depends on the body’s rotation and shape and stuff. Let’s just say it’s proportional to the volume, that way at least bigger parts have a bigger C_d. Of course drag also depends on the fluid the body is moving in, so it’s going to be much bigger in the water than in air. Let’s just say it’s 0 in air to simplify things:
local TagS = game:GetService("CollectionService")
local RunS = game:GetService("RunService")
local WATER_DENSITY = 1.5
local WATER_DRAG_DENSITY = 1 --Could be same as WATER_DENSITY, but it's nice to tweak buoyancy and drag separately
function submergedBuoyantForce(part: BasePart): number
local displacedVolume = part.Size.X * part.Size.Y * part.Size.Z
local partDensity = (part.CustomPhysicalProperties or PhysicalProperties.new(part.Material)).Density
local gravity = -game.Workspace.Gravity
return gravity * displacedVolume * (partDensity - WATER_DENSITY)
end
function submergedDragForce(part: BasePart): Vector3
local velocity = part.AssemblyLinearVelocity
local dragCoefficientTimesReferenceArea = part.Size.X * part.Size.Y * part.Size.Z
return -velocity * dragCoefficientTimesReferenceArea * WATER_DRAG_DENSITY
end
function setupBuoyantPart(part: BasePart)
local buoyancyAtt = Instance.new("Attachment")
buoyancyAtt.Parent = part
local buoyancyVel = Instance.new("LinearVelocity")
buoyancyVel.Parent = part
buoyancyVel.RelativeTo = Enum.ActuatorRelativeTo.World
buoyancyVel.VelocityConstraintMode = Enum.VelocityConstraintMode.Line
buoyancyVel.LineDirection = Vector3.yAxis
buoyancyVel.Attachment0 = buoyancyAtt
local dragVectorForce = Instance.new("VectorForce")
dragVectorForce.Parent = part
dragVectorForce.RelativeTo = Enum.ActuatorRelativeTo.World
dragVectorForce.Attachment0 = buoyancyAtt
RunS.Heartbeat:Connect(function()
local y = part.Position.Y
if y < 0 then
buoyancyVel.LineVelocity = 1000
buoyancyVel.MaxForce = submergedBuoyantForce(part)
dragVectorForce.Force = submergedDragForce(part)
else
buoyancyVel.LineVelocity = 0
buoyancyVel.MaxForce = 0
dragVectorForce.Force = Vector3.zero
end
end)
end
for _, tagged in ipairs(TagS:GetTagged("BuoyantPart")) do
setupBuoyantPart(tagged)
end
TagS:GetInstanceAddedSignal("BuoyantPart"):Connect(setupBuoyantPart)

Lot’s better, the bodies are now bobbing up and down a bit more like we’d expect, although it’s still not great. As soon as it’s center goes above the waterline, the buoyant force becomes 0 and it starts “falling” rapidly, making the bobbing a bit too violent still. This time I really think it’s because of the wrong submerged volume calculation, so let’s fix that:
local TagS = game:GetService("CollectionService")
local RunS = game:GetService("RunService")
local WATER_DENSITY = 2.25
local WATER_DRAG_DENSITY = 2 --Could be same as WATER_DENSITY, but it's nice to tweak buoyancy and drag separately
function submergedVolume(part: BasePart): number
local totalVolume = part.Size.X * part.Size.Y * part.Size.Z
local sizeY = part.Size.Y
local waterY = 0
local yMax = part.Position.Y + sizeY / 2
local yMin = part.Position.Y - sizeY / 2
local submergedProportion = math.clamp((waterY - yMin)/(yMax - yMin), 0, 1)
return totalVolume * submergedProportion
end
function submergedBuoyantForce(part: BasePart): number
local displacedVolume = submergedVolume(part)
local partDensity = (part.CustomPhysicalProperties or PhysicalProperties.new(part.Material)).Density
local gravity = -game.Workspace.Gravity
return gravity * displacedVolume * (partDensity - WATER_DENSITY)
end
function submergedDragForce(part: BasePart): Vector3
local velocity = part.AssemblyLinearVelocity
local dragCoefficientTimesReferenceArea = submergedVolume(part)
return -velocity * dragCoefficientTimesReferenceArea * WATER_DRAG_DENSITY
end
function setupBuoyantPart(part: BasePart)
local buoyancyAtt = Instance.new("Attachment")
buoyancyAtt.Parent = part
local buoyancyVel = Instance.new("LinearVelocity")
buoyancyVel.Parent = part
buoyancyVel.RelativeTo = Enum.ActuatorRelativeTo.World
buoyancyVel.VelocityConstraintMode = Enum.VelocityConstraintMode.Line
buoyancyVel.LineDirection = Vector3.yAxis
buoyancyVel.Attachment0 = buoyancyAtt
local dragVectorForce = Instance.new("VectorForce")
dragVectorForce.Parent = part
dragVectorForce.RelativeTo = Enum.ActuatorRelativeTo.World
dragVectorForce.Attachment0 = buoyancyAtt
RunS.Heartbeat:Connect(function()
buoyancyVel.MaxForce = submergedBuoyantForce(part)
dragVectorForce.Force = submergedDragForce(part)
local y = part.Position.Y
if y < 0 then
buoyancyVel.LineVelocity = 1000
else
buoyancyVel.LineVelocity = 0
end
end)
end
for _, tagged in ipairs(TagS:GetTagged("BuoyantPart")) do
setupBuoyantPart(tagged)
end
TagS:GetInstanceAddedSignal("BuoyantPart"):Connect(setupBuoyantPart)
Here I’m just modeling the body as like an upright cylinder or other prismatic shape, to simplify volume calculations.

I’d say that’s a pretty good result. It can be tweaked a bit more, both with the density constants but also by setting the density of the bodies themselves. Something with greater density than the water will sink faster than just normal gravity, same density and it’s like if it was in air, a bit less density and it’ll float.
It’s got some limitations, like completely ignoring the rotation of the bodies. So something like a raft made from a flat part won’t keep itself upright. You can kind of get it to work with more smaller parts, but it’s still not great IMO. If you just need it to make characters float, you should be good to go.
