Modular Swimming System

Hey, this is my first release and one of many to come!

This swimming system allows your player to swim through any part of your choosing, it allows for your own animations for idling and also while actually swimming!

Group 1 (2)

Group 1 (1)


Group 1

  • Swimming
  • Detects when you are at the surface of a part of any size
  • Detects when you are beyond the X and Z size of a part
  • When you get to surface level it launches you so it easier to get out of the water
  • Idling and Moving
  • Rotation relative to camera (you can look in any direction)
  • Flexibiity to fire a custom function when you start swimming

The module!

76 Likes

4 Likes

The model is currently offsale, fix pls!

edit: it’s fixed!

3 Likes

Will this work if the part is constantly being resized?

1 Like

Sorry all, thisi s my first release so I forgot to uncopylock it!

@MP412RexEnjoyer Yes this works regardless of the parts size!

1 Like

Why is the 2nd argument a string to refer to the water part? Shouldn’t it be the part itself?
image

I think an interface like this would look good compaired to using 6 parmeters:

local Swimzone = require(game.ReplicatedStorage.Swimzone)

local Zone = Swimzone.new()

-- Interface: Get, Set Methods
Zone:SetMaxSpeed(7)
Zone:SetPart(workspace.Water)

-- Another Interface: Indexing
Zone.MaxSpeed = 7
Zone.Part = workspace.Water

-- Another Interface: Get, Set Methods Chainable
local Zone = Swimzone.new():SetMaxSpeed(7):SetPart(workspace.Water)
print(Zone) --> Swimzone (workspace.Water)
1 Like

eh, I mean people just have to put 6 args its pretty simple in my opinion, but thanks for the suggestion though

also the second argument is what it idenfies as water, which in my case the part was called “Water”

Putting a lot of arguments in a row like that can be hard to read for some people. But then there’s TweenInfo.new that takes 6 arguments… so I don’t know. But I’m still confused about argument 2. Why is argument 2 a string and not a part?

Well because it is the part name that the ray identifies as the water part, its not the actual water part

Nice swimming system! Looks great.

1 Like

Can’t you just put workspace.Water? Or {workspace.Water1, workspace.Water2}? Or even better, using CollectionService and passing something like "Tag:Water" or just "Water"? Is the string just detecting each part by name?

The string just tells the ray what is and isnt water pretty much, its really rather simple

Is the string just detecting each part by name?

1 Like

image

Does this work with rotatated parts? What does the second parameter mean (“Water”)? Can’t I just do a table of parts?

1 Like

I wish Roblox were to add a Water Block so the community didn’t need to write it’s own script honestly.

7 Likes

Yes it works with rotated parts, the second parameter is what the raycast will detect, so anything named “Water” will only work

edit: Sorry no it doesnt work with rotated parts, the top surface must always be upright

I suppose it’s adding the “Water” parts into a stored table, for connections. Why couldn’t we send that table manually?

Un no not at all, its literally for the raycast so it knows which part is water and which isnt

you can take a look at the code, its rather simple really

Took a look at the code and I modified it to work as many people want it.

Code
local player = game.Players.LocalPlayer
local character = player.Character
local root = character.HumanoidRootPart
local humanoid = character.Humanoid
local camera = workspace.CurrentCamera

local debris = game:GetService("Debris")

local params = RaycastParams.new()
params.FilterDescendantsInstances = {character}
params.FilterType = Enum.RaycastFilterType.Blacklist


local swimming = {}
swimming.__index = swimming

function swimming.new(MAX_SPEED, WaterParts, IdleAnimation, ActionAnimation, YLaunch)

	local boolValue = Instance.new("BoolValue")
	boolValue.Name = "Swimming"
	boolValue.Value = false
	boolValue.Parent = character

	local self = {}

	self.MAX_SPEED = MAX_SPEED
	self.WaterParts = WaterParts
	self.BodyOfWater = nil
	self.YLaunch = YLaunch or 50
	self.Idle = humanoid:LoadAnimation(IdleAnimation)
	self.Action = humanoid:LoadAnimation(ActionAnimation)

	game:GetService("RunService").Heartbeat:Connect(function(deltaTime)

		local result = workspace:Raycast(root.Position, root.CFrame.UpVector * -3, params)

		if result and table.find(self.WaterParts,result.Instance) and not boolValue.Value and result.Instance.CanCollide then

			local bp = Instance.new("BodyPosition")
			bp.MaxForce = Vector3.new(math.huge,math.huge,math.huge)
			bp.P = 1500
			bp.D = 300
			bp.Parent = root
			bp.Position = result.Position - Vector3.new(0,3,0)

			local bg = Instance.new("BodyGyro")
			bg.MaxTorque = Vector3.new(math.huge,math.huge,math.huge)
			bg.P = 1500
			bg.D = 300
			bg.Parent = root

			humanoid.PlatformStand = true
			self.BodyOfWater = result.Instance
			result.Instance.CanCollide = false
			boolValue.Value = true

		end

		if boolValue.Value then

			local bp : BodyPosition = root:FindFirstChild("BodyPosition")
			local bg : BodyGyro = root:FindFirstChild("BodyGyro")

			if humanoid.MoveDirection .Magnitude == 0 then
				if self.Action.IsPlaying then
					self.Action:Stop(.5)
				end
				if self.Idle.IsPlaying == false then
					self.Idle:Play(.5)
				end
				bg.CFrame = camera.CFrame
			else
				if self.Idle.IsPlaying then
					self.Idle:Stop(.5)
				end
				if self.Action.IsPlaying == false then
					self.Action:Play(.5)
				end
				bp.Position += camera.CFrame.LookVector * self.MAX_SPEED * deltaTime
				bg.CFrame = camera.CFrame * CFrame.Angles(-math.rad(90),0,0)
			end

			if bp.Position.Y >= self.BodyOfWater.Position.Y + self.BodyOfWater.Size.Y / 2 then
				swimming.Stop(self,true)
			elseif bp.Position.X >= self.BodyOfWater.Position.X + self.BodyOfWater.Size.X / 2 then
				swimming.Stop(self)
			elseif bp.Position.X <= self.BodyOfWater.Position.X + -self.BodyOfWater.Size.X / 2 then
				swimming.Stop(self)
			elseif bp.Position.Z >= self.BodyOfWater.Position.Z + self.BodyOfWater.Size.Z / 2 then
				swimming.Stop(self)
			elseif bp.Position.Z <= self.BodyOfWater.Position.Z + -self.BodyOfWater.Size.Z / 2 then
				swimming.Stop(self)
			end
		end

	end)

	return setmetatable(self, {})
end


function swimming.Stop(self,isUp)

	self.Action:Stop()
	self.Idle:Stop()
	character.Swimming.Value = false

	if root:FindFirstChild("BodyPosition") then
		root.BodyPosition:Destroy()
	end

	if root:FindFirstChild("BodyGyro") then
		root.BodyGyro:Destroy()
	end

	if isUp then
		local bv = Instance.new("BodyVelocity")
		bv.MaxForce = Vector3.new(math.huge,math.huge,math.huge)
		bv.P = 1500
		bv.Velocity = root.CFrame.UpVector * self.YLaunch
		bv.Parent = root
		debris:AddItem(bv,.1)
	end

	humanoid:ChangeState(Enum.HumanoidStateType.GettingUp)

	task.spawn(function()
		task.wait(.5)
		self.BodyOfWater.CanCollide = true
		self.BodyOfWater = nil
	end)

end

return

Didn’t test if it works since I don’t have all the time and parameters to set up, but let me know if it doesn’t work.