Making a "Wall Climbing" System

Blokav, your way of doing Wall Climbing is frankly unusable for a game where the map changes numerous times and unrelated to the Rogue Lineage Climbing System

Since Blokav’s System was unusable for my situation I followed Jimmy’s Steps as seen above to the best of my abilities this is the result:


I have a few bugs to fix after that I’ll be releasing the system to the public.

31 Likes

https://www.roblox.com/games/5889967357

I apologize for almost taking a week on this I kind of forgot about it please note that this isn’t a “complete” climbing system since it doesn’t let you climb from part to part, I hope the code is understandable enough though oh and have fun!

16 Likes

I don’t mind the flaws, but thanks for sharing the source to allow more understanding for everyone about wall climbing. I believe others will also improve from the roots of this system.

5 Likes

Thank you for the kind words.
My reasoning behind not releasing a “complete” climbing system is because I’m afraid that people will just get lazy with it and not change anything outside of that I don’t know if my code is ready yet for open sourcing I wanna see the criticism on that.

5 Likes

Nice script. Does it work for r6 to?

1 Like

How would I add animations to the script to.

1 Like

You gotta add it manually by using UserinputService or the context

1 Like

He said there were flaws and by no means perfect, If you read the opening replies you can see this sort of issue was addressed and a fix was given.

You can incorporate this to your own version…
If I were you I would just take implementation ideas instead of “changing” the code since I personally think it leads to a better code structure everytime.

1 Like

would you mind if you explain how this works? I don’t understand how most things work on this system.

1 Like

Well in honesty I don’t use this, I’ve done a different approach for my wall crawling where player wont be able to move from one face of a part to another.
But from how I know this works is that initially the player is mounted onto the wall using BodyVelocity and BodyGyro, this allows the player to face the part and the velocity allows the movement as you see in the script. Now how to move from one face of a part to another is actually simple… we cast a ray from inside the part by offseting the raycast origin by (0,0,-1) from rootpart, which is also shown in the script, using rightVector we can get the direction and cast a ray from within the part. What’s cool is raycasts don’t detect by part, but the surface they hit. So we can get the hit.Normal and hit.Position to turn the body using BodyGyro towards that position. The bug you are having is because of physics in general, where your script doesn’t take into consideration your velocity to turn you at the right time around the corner. I would tamper with the raycast origin, adujusting it in relation to your velocity.

Hope that gave some insight. (I’m making a rogue-like too :smiley: )

2 Likes

Uhm, I have 0 idea how raycasting works so all i have right now is this (yes, i used the open source):

--------------------------------------------------------------------------------
-------------------------------------------------------------- Service Shortcuts
--------------------------------------------------------------------------------

local cas							= game:GetService("ContextActionService")
local ppl							= game:GetService("Players")
local run							= game:GetService("RunService")
local uis							= game:GetService("UserInputService")

--------------------------------------------------------------------------------
--------------------------------------------------------------- Player Shortcuts
--------------------------------------------------------------------------------

local plr							= ppl.LocalPlayer
local char							= plr.Character or plr.CharacterAdded:Wait()
local hum							= char:WaitForChild("Humanoid")
local rootpart						= char:WaitForChild("HumanoidRootPart")

--------------------------------------------------------------------------------
---------------------------------------------------------- Global Raycast Params
--------------------------------------------------------------------------------

local params						= RaycastParams.new()
params.CollisionGroup				= ("Climbable")
params.FilterDescendantsInstances	= {char}
params.FilterType					= Enum.RaycastFilterType.Blacklist
params.IgnoreWater					= true

--------------------------------------------------------------------------------
----------------------------------------------------------- Climbing Body Movers
--------------------------------------------------------------------------------

local climbMove						= Instance.new("BodyVelocity")
climbMove.MaxForce					= Vector3.new(1,1,1)*math.huge
climbMove.P							= math.huge
climbMove.Velocity					= Vector3.new()
climbMove.Name						= ("Climb Speed")

--------------------------------------------------------------------------------

local climbGyro						= Instance.new("BodyGyro")
climbGyro.CFrame					= CFrame.new()
climbGyro.D							= 100
climbGyro.MaxTorque					= Vector3.new(1,1,1)*math.huge
climbGyro.P							= 3000

--------------------------------------------------------------------------------
------------------------------------------------------------- Climbing Variables
--------------------------------------------------------------------------------

local climbing						= false
local face							= nil

--------------------------------------------------------------------------------
------------------------------------------------------------- Climbing Functions
--------------------------------------------------------------------------------

function down(KEY) return uis:IsKeyDown(Enum.KeyCode[KEY]) end

--------------------------------------------------------------------------------
--------------------------------------------------------------------------------

function offClimb()
	climbing=false
	climbGyro.Parent=nil
	climbMove.Parent=nil
	face=nil
end

--------------------------------------------------------------------------------
--------------------------------------------------------------------------------

function onClimb()
	local origin = rootpart.Position
	local direction = rootpart.CFrame.LookVector
	
	local result = workspace:Raycast(origin, direction, params)
	if result then
		climbing=true
		rootpart.CFrame = CFrame.new(rootpart.CFrame.p, Vector3.new(rootpart.Position.X - result.Normal.X, rootpart.Position.Y, rootpart.Position.Z - result.Normal.Z))
		face = CFrame.new(result.Position+result.Normal, result.Position)
		
		hum.AutoRotate=false
		hum.PlatformStand=true
		
		climbMove.Parent=rootpart
		climbGyro.Parent=rootpart
		
		repeat
			run.RenderStepped:Wait()
			climbGyro.CFrame=face or CFrame.new()
			
			--I'm bad with math so I just used this quick solution, sorry to dissapoint.
			if rootpart.Position.Y > result.Instance.Size.Y+3 then
				climbing=false
				offClimb()
			end
			
			--if statements make code look spaghetti, so I used the logical operator: OR
			local sideOrigin = rootpart.CFrame*CFrame.new(0, 0, -1).Position
			local sideDirection = rootpart.CFrame.RightVector*(down("D") and -2 or 2)

			local hit = workspace:Raycast(sideOrigin, sideDirection, params)
			if hit and (down("D") or down("A")) then
				face=CFrame.new(hit.Position+hit.Normal, hit.Position)
			end
		until (not climbing)

		hum.AutoRotate=true
		hum.PlatformStand=false
	end
end

--------------------------------------------------------------------------------
----------------------------------------------------------------- Movement Logic
--------------------------------------------------------------------------------

local actions	= {0;0;	0;0;}
local buttons	= {"A";"D";"S";"W"}
local lastTime
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------

uis.InputBegan:Connect(function(obj)
	local input	= tostring(obj.KeyCode):split('.')[3]
	local key	= table.find(buttons, input)

	if key then
		actions[key]=1
	end
	
	
	--I was lazy to script double tap space
	if input == "Space" then
		local now = tick()
		local difference = (now - lastTime)

		if difference <= 0.2 then
			if (not climbing) then
				onClimb()
			else
				offClimb()
			end
			wait(2)
		end
		lastTime = tick()
	end
end)

--------------------------------------------------------------------------------

uis.InputEnded:Connect(function(obj)
	local input	= tostring(obj.KeyCode):split('.')[3]
	local key	= table.find(buttons, input)

	if key then
		actions[key]=0
	end
end)

--------------------------------------------------------------------------------
--------------------------------------------------------------------------------

run.RenderStepped:Connect(function()
	local strafe = actions[2] - actions[1]
	local surge = actions[3] - actions[4]
	
	climbMove.Velocity=rootpart.CFrame.RightVector*(strafe*8)+Vector3.new(0, surge*-8, 0)
end)

--------------------------------------------------------------------------------
---------------------------------------- Hopefully that was a fun and clean read
--------------------- I know that this is glitchy but that's somewhat on purpose
--------------- This is meant to be a introduction to a climbing system, bt dubs
--------------------------------------------------------------------------------

the only thing i did so far was change L to climb to double tap spacebar

2 Likes

is there a way to check where the edge is, and where the part’s edge ends?

1 Like

Well, matter of fact I don’t think you need to find the edge what should be used in contrast with the current system is maybe AlignPosition | Roblox Creator Documentation, this should prevent the gap between the player and would be a much efficient fix as opposed to finding the sides of a part because you have to include the rotation of a part which is a bunch of math I don’t think worth diving into.

Compared to what @Blend_it has created to find the edge and have the player adjust to the next side I think this much more viable to create a revamp of the original.

All you would have to do is create a attachment for the rootpart and attachment for the raycast hit.Normal, Give these into a AlignPosition instance and I think it should work… I haven’t tested but the logic makes sense.

(Suggest you have a look into raycasting before trying to do this, every roblox dev must know how raycasting works)

2 Likes

Here I felt a little kind today, I quote on quote “assisted” the open source code so that the bug doesn’t occur…

--------------------------------------------------------------------------------
-------------------------------------------------------------- Service Shortcuts
--------------------------------------------------------------------------------

local cas							= game:GetService("ContextActionService")
local ppl							= game:GetService("Players")
local run							= game:GetService("RunService")
local uis							= game:GetService("UserInputService")

--------------------------------------------------------------------------------
--------------------------------------------------------------- Player Shortcuts
--------------------------------------------------------------------------------

local plr							= ppl.LocalPlayer
local char							= plr.Character or plr.CharacterAdded:Wait()
local hum							= char:WaitForChild("Humanoid")
local rootpart						= char:WaitForChild("HumanoidRootPart")

--------------------------------------------------------------------------------
---------------------------------------------------------- Global Raycast Params
--------------------------------------------------------------------------------

local params						= RaycastParams.new()
params.CollisionGroup				= ("Climbable")
params.FilterDescendantsInstances	= {char}
params.FilterType					= Enum.RaycastFilterType.Blacklist
params.IgnoreWater					= true

--------------------------------------------------------------------------------
----------------------------------------------------------- Climbing Body Movers
--------------------------------------------------------------------------------

local climbMove						= Instance.new("BodyVelocity")
climbMove.MaxForce					= Vector3.new(1,1,1)*math.huge
climbMove.P							= math.huge
climbMove.Velocity					= Vector3.new()
climbMove.Name						= ("Climb Speed")

--------------------------------------------------------------------------------

local climbGyro						= Instance.new("BodyGyro")
climbGyro.CFrame					= CFrame.new()
climbGyro.D							= 100
climbGyro.MaxTorque					= Vector3.new(1,1,1)*math.huge
climbGyro.P							= 3000

--------------------------------------------------------------------------------

local alignPos						= Instance.new("AlignPosition",rootpart)
local attachment0 					= Instance.new("Attachment",rootpart)
attachment0.CFrame					= CFrame.new(0,0,-2)
alignPos.Attachment0				= attachment0
alignPos.MaxForce					= Vector3.new(1,1,1)*math.huge
alignPos.MaxVelocity				= Vector3.new(1,1,1)*math.huge
alignPos.Responsiveness				= 20
alignPos.ApplyAtCenterOfMass		= true
alignPos.ReactionForceEnabled		= true
alignPos.RigidityEnabled			= true


--------------------------------------------------------------------------------
------------------------------------------------------------- Climbing Variables
--------------------------------------------------------------------------------

local climbing						= false
local face							= nil

--------------------------------------------------------------------------------
------------------------------------------------------------- Climbing Functions
--------------------------------------------------------------------------------

function down(KEY) return uis:IsKeyDown(Enum.KeyCode[KEY]) end

--------------------------------------------------------------------------------
--------------------------------------------------------------------------------

function offClimb()
	climbing=false
	climbGyro.Parent=nil
	climbMove.Parent=nil
	face=nil
end

function onClimb()
	local origin = rootpart.Position
	local direction = rootpart.CFrame.LookVector

	local result = workspace:Raycast(origin, direction, params)
	if result then
		climbing=true
		rootpart.CFrame = CFrame.new(rootpart.CFrame.p, Vector3.new(rootpart.Position.X - result.Normal.X, rootpart.Position.Y, rootpart.Position.Z - result.Normal.Z))
		face = CFrame.new(result.Position+result.Normal, result.Position)

		hum.AutoRotate=false
		hum.PlatformStand=true

		climbMove.Parent=rootpart
		climbGyro.Parent=rootpart

		repeat
			run.RenderStepped:Wait()
			climbGyro.CFrame=face or CFrame.new()

			if rootpart.Position.Y > result.Instance.Size.Y+3 then
				climbing=false
				offClimb()
			end

			local sideOrigin = rootpart.CFrame*CFrame.new(0, -0.4, -1).Position
			local sideDirection = rootpart.CFrame.RightVector*(down("D") and -2 or 2)

			local hit = workspace:Raycast(sideOrigin, sideDirection, params)
			if hit and (down("D") or down("A")) then
				local attachment1 = Instance.new("Attachment",hit.Instance)
				attachment1.WorldPosition = hit.Position
				alignPos.Attachment1 = attachment1
				face=CFrame.new(hit.Position+hit.Normal, hit.Position)
				game.Debris:AddItem(attachment1,0.005)
			end
		until (not climbing)

		hum.AutoRotate=true
		hum.PlatformStand=false
	end
end


local actions	= {0;0;	0;0;}
local buttons	= {"A";"D";"S";"W"}
local lastTime  = tick()


uis.InputBegan:Connect(function(obj)
	local input	= tostring(obj.KeyCode):split('.')[3]
	local key	= table.find(buttons, input)

	if key then
		actions[key]=1
	end
	
	if input == "Space" then
		local now = tick()
		local difference = (now - lastTime)

		if difference <= 0.2 then
			if (not climbing) then
				onClimb()
			else
				offClimb()
			end
			wait(2)
		end
		lastTime = tick()
	end
end)


uis.InputEnded:Connect(function(obj)
	local input	= tostring(obj.KeyCode):split('.')[3]
	local key	= table.find(buttons, input)

	if key then
		actions[key]=0
	end
end)


run.RenderStepped:Connect(function()
	local strafe = actions[2] - actions[1]
	local surge = actions[3] - actions[4]

	climbMove.Velocity=rootpart.CFrame.RightVector*(strafe*8)+Vector3.new(0, surge*-8, 0)
end)

Note I altered the one you gave so there shouldn’t be any changes to keybinds…
I didn’t include part to part, you would have to do that yourself, the approach I would take to do something like this would be to constantly cast a ray from the rootpart and cast rays depending on which key is being pressed ( A or D ) to cast a ray from either the RightArm/LeftArm in their respective rightVector directions. You would then move from there, understanding how this works and altering it to your desire.

4 Likes

would this be a valid way?

	local origin = rootpart.Position
	local direction = RightArm.CFrame.RightVector+RightArm.CFrame.LookVector

	local result = workspace:Raycast(origin, direction, params)
			local hit = workspace:Raycast(sideOrigin, sideDirection, params)
			if hit and (down("D") or down("A")) then -- right or left button is down
				local anotherDirection
				local leftOrRight = nil
				local anotherOrigin = rootpart.Position
				if down("D") then
					leftOrRight = 10
					anotherDirection = leftArm.CFrame.RightVector * -1 +leftArm.CFrame.LookVector
				elseif down("A") then
					leftOrRight = -10
					anotherDirection = rightArm.CFrame.RightVector+rightArm.CFrame.LookVector
				end
				local anotherClimbable = workspace:Raycast(anotherOrigin, anotherDirection, params)
				
				local attachment1 = Instance.new("Attachment",hit.Instance)
				attachment1.WorldPosition = hit.Position
				alignPos.Attachment1 = attachment1
				face=CFrame.new(hit.Position+hit.Normal, hit.Position)
				game.Debris:AddItem(attachment1,0.005)
				
				if anotherClimbable then
					climbing=false
					offClimb()
					local ve = Instance.new("BodyVelocity")
					ve.MaxForce = Vector3.new(1, 1, 1) * 40000
					ve.Velocity = rootpart.CFrame.RightVector * leftOrRight
					ve.Parent = rootpart
					game.Debris:AddItem(ve, 0.5)
					onClimb()
				end
			end

uhh, i have no idea what i did, and it’s not working… could you explain a bit more of what i should do to fix it?

I attempted to do a part to part

1 Like

Uh no I think you misunderstood, lemme explain…

What you would actually do is maybe casting a ray from each arm depending which direction the player is moving (left/right) this should be done using Right\Vector, reminder that you would need to offset the raycast origin to so that the ray wouldn’t detect the arm.

Example:

local rightarm = char["Right Arm"]
local hit  = workspace:Raycast((rightarm.CFrame*CFrame.new(3,0,0).Position,rightarm,CFrame.RightVector*2,param)
if hit then
    --// use face variable and the attachment code I made to adjust to the new surface

Here is how you would do a left arm

local leftarm = char["Left Arm"]
local hit  = workspace:Raycast((leftarm.CFrame*CFrame.new(-3,0,0).Position,leftarm,CFrame.RightVector*-2,param)
if hit then
    --// like rightarm

Now something you should include is casting a ray from the rootpart if the last touched part and the new part is not the same then use the alignposition code along the the climbGyro to adjust.

2 Likes
			local hit = workspace:Raycast(sideOrigin, sideDirection, params)
			if hit and (down("D") or down("A")) then -- right or left button is down
				local ifThatHit = false
				if down("D") then
					local hit  = workspace:Raycast((leftarm.CFrame*CFrame.new(-3,0,0).Position,leftarm,CFrame.RightVector*-2,param)
					if hit then
						ifThatHit = true
						local attachment1 = Instance.new("Attachment",hit.Instance)
						attachment1.WorldPosition = hit.Position
						alignPos.Attachment1 = attachment1
						face=CFrame.new(hit.Position+hit.Normal, hit.Position)
						game.Debris:AddItem(attachment1,0.005)
					end
				elseif down("A") then
					leftOrRight = -10
					local hit  = workspace:Raycast((rightarm.CFrame*CFrame.new(3,0,0).Position,rightarm,CFrame.RightVector*2,param)
					if hit then
						ifThatHit = true
						local attachment1 = Instance.new("Attachment",hit.Instance)
						attachment1.WorldPosition = hit.Position
						alignPos.Attachment1 = attachment1
						face=CFrame.new(hit.Position+hit.Normal, hit.Position)
						game.Debris:AddItem(attachment1,0.005)
					end
				end
				if ifThatHit == false then
					local attachment1 = Instance.new("Attachment",hit.Instance)
					attachment1.WorldPosition = hit.Position
					alignPos.Attachment1 = attachment1
					face=CFrame.new(hit.Position+hit.Normal, hit.Position)
					game.Debris:AddItem(attachment1,0.005)
				end
			end

so like this…?

1 Like

I mean you tell me did it work… when I gave you the example I didn’t have this in mind but what ever floats your boat man :confused:

1 Like

I fixed a few bugs, it worked, kind of… how would i use the bodygyro to face the current part?

video of my current version vvv
robloxapp-20210703-0832469.wmv (4.6 MB)