How would I make a wall climbing system where players can swap sides

  1. What do you want to achieve? Keep it simple and clear!

I’m trying to learn ways/methods to how I can make a player swap surfaces of a part in my wall climbing system.

  1. What is the issue?

I’m having issues thinking of ways I can make my wall climbing system swap sides.

Whole Client
local CollectionService = game:GetService("CollectionService")
local PlayerService = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local Debris = game:GetService("Debris")

local Player = PlayerService.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local HumanoidRootPart = Character.PrimaryPart
local Humanoid = Character:WaitForChild("Humanoid")

local wallClimb = false

local waitGyro = nil
local waitPos = nil

local connection1
local connection2

local cd = 0.25

local function Choice(...)
	local args = {...}
	Humanoid.PlatformStand = args[4]
	Humanoid.AutoRotate = args[1]
	Humanoid.WalkSpeed = args[2]
	Humanoid.JumpPower = args[3]
end

local function Raycast(...)
	local args = {...}
	local params = RaycastParams.new()
	params.FilterType = Enum.RaycastFilterType.Exclude
	params.FilterDescendantsInstances = {Character}
	
	local origin = args[2]
	local direction = args[3]

	local result = workspace:Raycast(origin,direction,params)
	
	if args[1] == "Attach" then
		if result and result.Instance == args[4] then
			return CFrame.lookAt(result.Position,result.Position + -result.Normal)
		end
	elseif args[1] == "Swap" then
		if result == nil then
			return "Returned"
		end
	end
end

local function bodyGyro()
	local gyro = Instance.new("BodyGyro")
	gyro.MaxTorque = Vector3.new(1e6,1e6,1e6)
	gyro.Parent = HumanoidRootPart
	return gyro
end

local function bodyPosition()
	local pos = Instance.new("BodyPosition")
	pos.MaxForce = Vector3.new(1e6,1e6,1e6)
	pos.Parent = HumanoidRootPart
	return pos
end

local function Move()
	local keyDownTable = {
		UserInputService:IsKeyDown(Enum.KeyCode.W),
		UserInputService:IsKeyDown(Enum.KeyCode.A),
		UserInputService:IsKeyDown(Enum.KeyCode.S),
		UserInputService:IsKeyDown(Enum.KeyCode.D)
	}
	
	if keyDownTable[1] then
		waitPos.Position = waitPos.Position + Vector3.new(0,0.25,0)
	end
	if keyDownTable[2] then
		waitPos.Position = waitPos.Position + Vector3.new(0.25,0,0)
	end
	if keyDownTable[3] then
		waitPos.Position = waitPos.Position + Vector3.new(0,-0.25,0)
	end
	if keyDownTable[4] then
		waitPos.Position = waitPos.Position + Vector3.new(-0.25,0,0)
	end
end

local function Clean()
	if wallClimb == true then
		coroutine.wrap(function()
			Debris:AddItem(waitPos,cd)
			Debris:AddItem(waitGyro,cd)
			task.wait(cd)
			waitPos = nil
			waitGyro = nil

			connection1:Disconnect()
			connection2:Disconnect()

			Choice(true,16,50,false)
			
			wallClimb = false
		end)()
	end
end

local function ifNil()
	local nilRaycast = Raycast("Swap",HumanoidRootPart.Position,HumanoidRootPart.CFrame.LookVector * 4,nil)
	if nilRaycast == "Returned" then
		Clean()
	end
end

for _,Wall in ipairs(CollectionService:GetTagged("Wall")) do
	Wall.Touched:Connect(function()
		if not wallClimb then
			
			local attachResult = Raycast("Attach",HumanoidRootPart.Position,HumanoidRootPart.CFrame.LookVector * 4, Wall)
			
			if attachResult ~= nil then
				wallClimb = true
				Choice(false,0,0,true)
				local gyro = bodyGyro()
				local pos = bodyPosition()

				waitGyro = gyro
				waitPos = pos

				HumanoidRootPart.CFrame = attachResult
				waitGyro.CFrame = HumanoidRootPart.CFrame
				waitPos.Position = HumanoidRootPart.Position

				connection1 = RunService.Heartbeat:Connect(Move)
				connection2 = RunService.Heartbeat:Connect(ifNil)
			end
		end
	end)
end
  1. What solutions have you tried so far?

I’ve thought of rotating 90 degrees using my gyro based on which key I was holding but I remember the last time I attempted to make a wall climbing system that only ruined my code.

Test: Home - Roblox

There are bugs but I am looking for ideas on how to swap sides.

Thanks, have a nice day!

I would shoot 2 rays from each hand at an angle

And check if the normal from either right or left ray is different from the normal you’re currently on, and switch to that side

1 Like

k I’ll try that Thirt cars

1 Like

The hand solution isn’t working for me, I probably got my math wrong since I don’t know how to really rotate directions.

Client script
local CollectionService = game:GetService("CollectionService")
local PlayerService = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local Debris = game:GetService("Debris")

local Player = PlayerService.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local HumanoidRootPart = Character.PrimaryPart
local Humanoid = Character:WaitForChild("Humanoid")

local wallClimb = false

local waitGyro = nil
local waitPos = nil

local UsingNormal = nil

local connection1
local connection2
local connection3

local cd = 0.25

local function Choice(...)
	local args = {...}
	Humanoid.PlatformStand = args[4]
	Humanoid.AutoRotate = args[1]
	Humanoid.WalkSpeed = args[2]
	Humanoid.JumpPower = args[3]
end

local function Raycast(...)
	local args = {...}
	local params = RaycastParams.new()
	params.FilterType = Enum.RaycastFilterType.Exclude
	params.FilterDescendantsInstances = {Character}
	
	local origin = args[2]
	local direction = args[3]

	local result = workspace:Raycast(origin,direction,params)
	
	if args[1] == "Attach" then
		if result and result.Instance == args[4] then
			return {CFrame.lookAt(result.Position,result.Position + -result.Normal),result.Normal}
		end
	elseif args[1] == "Nil" then
		if result == nil then
			return "Returned"
		end
	elseif args[1] == "Right" then
		print(result)
		if result and result.Normal ~= UsingNormal then
			return {CFrame.lookAt(result.Position,result.Position + -result.Normal),"FoundRight", result.Normal}
		end
	elseif args[1] == "Left" then
		if result and result.Normal ~= UsingNormal then
			return {CFrame.lookAt(result.Position,result.Position + -result.Normal),"FoundLeft", result.Normal}
		end
	end
end

local function bodyGyro()
	local gyro = Instance.new("BodyGyro")
	gyro.MaxTorque = Vector3.new(1e6,1e6,1e6)
	gyro.Parent = HumanoidRootPart
	return gyro
end

local function bodyPosition()
	local pos = Instance.new("BodyPosition")
	pos.MaxForce = Vector3.new(1e6,1e6,1e6)
	pos.Parent = HumanoidRootPart
	return pos
end

local function Move()
	local keyDownTable = {
		UserInputService:IsKeyDown(Enum.KeyCode.W),
		UserInputService:IsKeyDown(Enum.KeyCode.A),
		UserInputService:IsKeyDown(Enum.KeyCode.S),
		UserInputService:IsKeyDown(Enum.KeyCode.D)
	}
	
	if keyDownTable[1] then
		waitPos.Position = waitPos.Position + Vector3.new(0,0.25,0)
	end
	if keyDownTable[2] then
		waitPos.Position = waitPos.Position + Vector3.new(0.25,0,0)
	end
	if keyDownTable[3] then
		waitPos.Position = waitPos.Position + Vector3.new(0,-0.25,0)
	end
	if keyDownTable[4] then
		waitPos.Position = waitPos.Position + Vector3.new(-0.25,0,0)
	end
end

local function Clean()
	if wallClimb == true then
		coroutine.wrap(function()
			if waitPos and waitGyro then
				Debris:AddItem(waitPos,cd)
				Debris:AddItem(waitGyro,cd)
			end
			task.wait(cd)
			waitPos = nil
			waitGyro = nil
			UsingNormal = nil

			connection1:Disconnect()
			connection2:Disconnect()

			Choice(true,16,50,false)
			
			wallClimb = false
		end)()
	end
end

local function Unenable()
	local nilRaycast = Raycast("Nil",HumanoidRootPart.Position,HumanoidRootPart.CFrame.LookVector * 4,nil)
	if nilRaycast == "Returned" then
		Clean()
	end
end

local function Swap()
	local rightDirection = HumanoidRootPart.CFrame.LookVector * 4 - Character:WaitForChild("Right Arm").Position
	local leftDirection = HumanoidRootPart.CFrame.LookVector * 4 - Character:WaitForChild("Left Arm").Position
	
	local cfRight = CFrame.Angles(0,math.rad(45),0)
	local cfLeft = CFrame.Angles(0,math.rad(-45),0)
	
	local rotatedRight = cfRight:VectorToWorldSpace(rightDirection)
	local rotatedLeft = cfLeft:VectorToWorldSpace(leftDirection)
	
	local right = Raycast("Right", HumanoidRootPart.Position,rotatedLeft,nil)
	local left = Raycast("Left", HumanoidRootPart.Position,rotatedRight,nil)
	if right[2] == "FoundRight" and right then
		HumanoidRootPart.CFrame = right[1]
		waitGyro.CFrame = HumanoidRootPart.CFrame
		UsingNormal = right[3]
	end
	if left[2] == "FoundLeft" and left then
		HumanoidRootPart.CFrame = right[2]
		waitGyro.CFrame = HumanoidRootPart.CFrame
		UsingNormal = left[3]
	end
end

for _,Wall in ipairs(CollectionService:GetTagged("Wall")) do
	Wall.Touched:Connect(function()
		if not wallClimb then
			
			local attachResult = Raycast("Attach",HumanoidRootPart.Position,HumanoidRootPart.CFrame.LookVector * 4, Wall)
			
			if attachResult ~= nil then
				UsingNormal = attachResult[2]
				wallClimb = true
				Choice(false,0,0,true)
				local gyro = bodyGyro()
				local pos = bodyPosition()

				waitGyro = gyro
				waitPos = pos

				HumanoidRootPart.CFrame = attachResult[1]
				waitGyro.CFrame = HumanoidRootPart.CFrame
				waitPos.Position = HumanoidRootPart.Position

				connection1 = RunService.Heartbeat:Connect(Move)
				connection2 = RunService.Heartbeat:Connect(Unenable)
				connection3 = RunService.Heartbeat:Connect(Swap)
			end
		end
	end)
end


Humanoid.Died:Connect(Clean)

I fixed some bugs and I updated the map.