Numerous Issues with a Climbing System

So I’ve been trying to make a climbing system for a while now, and I’ve run into several issues with it that I haven’t been able to solve, the main one being climbing around corners, which I haven’t been able to find any information about. There are other issues with it, which I will mention latter.

Ok, so the way that the system is set up prevents it from detecting walls angled at about 90 degrees away from the wall that the player is currently climbing on, causing the player to just fall of walls with sharp corners. The way that I tried to fix this is rotating the player 90 degrees depending on whether the player is moving to the left or to the right. The problem with this is that it looks really glitchy, and also if you stop at a corner, the player bugs out as if they’re having a seizure. I know why this happens (or at least I think I do), but I have no clue how to fix it. There’s also the fact that this method doesn’t work for climbing sharp corners angled 90 degrees towards the wall the player is currently climbing on.

Here’s an example of these things:

Yeah.
As you could see, the method that I’m using to climb around corners absolutely sucks and barely works, so I wanted to try a different method. One problem. I don’t have another method. So, that’s an issue.

I looked all over YouTube and the Devforum, but couldn’t find anything on climbing around corners and stuff like that. The closest thing that I could find were open sourced ledge grabbing systems. At first I thought, “Oh boy! I can just steal the climbing-around-corners part of these systems, and use it in my climbing system!” The problem was that my brain was waaaaaay too tiny to understand anything that was going on within the systems, so I couldn’t locate the parts responsible for climbing around corners. There’s.

That brings me to my request. I was hoping one of you big-brained developers could at least give me an example of how I should go about updating my climbing system so that players can climb around sharp corners. I would also request that someone give me some insight on how ledge grabbing systems locate the ledges of walls. I’ve looked at some code depicting this, and I know it has something to do with the ToWorldSpace and ToObjectSpace functions, but again, my brain is too small to understand it. Even after I watched a video explaining the ToWorldSpace and ToObjectSpace functions, which I now understand, I still don’t get how they have anything to do with locating the ledge of a wall.

One of the minor problems with my climbing system is that when climbing around smooth corners, the player tends to be pushed away from the wall. Eventually, the player gets pushed so far away from the wall that they end up falling off. I’ve already tried fixing this but I couldn’t :frowning:, although I will probably find a solution eventually. Another minor problem with my climbing system is the fact that I got no clue what to do for when the player is moving diagonally, actually, this is probably one of the more major problems. Huh. There’s also the fact that the entire script breaks when the player dies… I’ll get to that eventually. Here’s the script for my climbing system.

local c = script.Parent
local SH = require(game.ReplicatedStorage.Statehadler) --A module that keeps track of what the player is doing
local hrp = c:WaitForChild("HumanoidRootPart")
local hum = c:WaitForChild("Humanoid")
local UIS = game:GetService("UserInputService")
local speed = 10
local Pos = {} --Postion table
local climb --Event variable

local XAxis = {
	0; --Left
	0 --Right
}
local YAxis = {
	0; --Up
	0 --Down
}

do
--Movement handler stuff
	local inputTask = {
		[Enum.KeyCode.W] = function(state)
			YAxis[1] = (state and 1) or 0
		end;

		[Enum.KeyCode.S] = function(state)
			YAxis[2] = (state and 1) or 0
		end;

		[Enum.KeyCode.A] = function(state)
			XAxis[1] = (state and 1) or 0
		end;

		[Enum.KeyCode.D] = function(state)
			XAxis[2] = (state and 1) or 0
		end;
	}

	UIS.InputBegan:Connect(function(input: InputObject)
		if inputTask[input.KeyCode] then
			inputTask[input.KeyCode](true)
		end
	end)

	UIS.InputEnded:Connect(function(input: InputObject)
		if inputTask[input.KeyCode] then
			inputTask[input.KeyCode](false)
		end
	end)
end


local function StopClimbing()
	--Stops the player from climbing
	if climb == nil then return end
	climb:Disconnect()
	climb = nil
	if hum:GetState() == Enum.HumanoidStateType.Climbing then
		hum:SetStateEnabled(Enum.HumanoidStateType.Running, true)
		hum.AutoRotate = true
		hrp:FindFirstChild("climbingattachment"):Destroy()
	end
end

local function CalculateDirection(startpos,endpos,object)
	--Uses the Dot function to find the direction the player is moving. Probably what im most proud of in this system lol.
	local product
	local Dir = (endpos - startpos).Unit
	if Dir:Dot(-object.CFrame.UpVector) > 0.50 then
		product = "Up"
	elseif Dir:Dot(object.CFrame.UpVector) > 0.50 then
		product = "Down"
	elseif Dir:Dot(-object.CFrame.RightVector) > 0.50 then
		product = "Right"
	elseif Dir:Dot(object.CFrame.RightVector) > 0.50 then
		product = "Left"
	end
	return product
end

UIS.InputBegan:Connect(function(input,gpe)
	if input.KeyCode == Enum.KeyCode.Space and SH[c].Grounded == false then
		climb = game:GetService("RunService").Heartbeat:Connect(function()
			local NewRaycastParams = RaycastParams.new()
			NewRaycastParams.FilterType = Enum.RaycastFilterType.Exclude
			NewRaycastParams.FilterDescendantsInstances = {c}

			local Origin = hrp.Position
			local Direction = hrp.CFrame.LookVector * 2
			local Raycast = workspace:Raycast(Origin, Direction, NewRaycastParams)
			if Raycast then
				local climbingattachment = hrp:FindFirstChild("climbingattachment")
				local movement
				local gyro
				if climbingattachment == nil then
					--Creates all the stuff needed for climbing
					climbingattachment = Instance.new("Attachment",hrp)
					climbingattachment.Name = "climbingattachment"
					
					movement = Instance.new("LinearVelocity")
					movement.MaxForce = math.huge
					movement.Attachment0 = climbingattachment
					movement.RelativeTo = Enum.ActuatorRelativeTo.Attachment0
					movement.VectorVelocity = Vector3.new()
					movement.Parent = climbingattachment
				
					gyro = Instance.new("AlignOrientation")
					gyro.Mode = Enum.OrientationAlignmentMode.OneAttachment
					gyro.Attachment0 = climbingattachment
					gyro.RigidityEnabled = true
					gyro.Parent = climbingattachment
				else
					movement = climbingattachment:FindFirstChild("LinearVelocity")
					gyro = climbingattachment:FindFirstChild("AlignOrientation")
				end 
				local RaycastCFrame = CFrame.lookAt(Raycast.Position + (Raycast.Normal/2), Raycast.Position) --The orentation of the raycast postion
				--Saves the last two postions the player was located at in a table
				if #Pos < 2 then                          
					table.insert(Pos,Raycast.Position)
				else
					table.remove(Pos,1)
					table.insert(Pos,Raycast.Position)
				end
				gyro.CFrame = RaycastCFrame
				hum.AutoRotate = false
				hum:ChangeState(Enum.HumanoidStateType.Climbing)
				hum:SetStateEnabled(Enum.HumanoidStateType.Running, false)
			else
				if hum:GetState() == Enum.HumanoidStateType.Climbing then
					local Dir = CalculateDirection(Pos[2],Pos[1],hrp)
					if Dir == "Up" then
						StopClimbing()
						hrp:ApplyImpulse(Vector3.new(0,1000,0)) --Place holder
					elseif Dir == "Right" then
						hrp.CFrame = hrp.CFrame:ToWorldSpace(CFrame.Angles(0,math.rad(90),0)) --Rotates player
					elseif Dir == "Left" then
						hrp.CFrame = hrp.CFrame:ToWorldSpace(CFrame.Angles(0,math.rad(-90),0)) --Rotates player
					end
				else
					StopClimbing()
				end
			end
		end)
	end
end)

UIS.InputEnded:Connect(function(input,gpe)
	if (input.KeyCode == Enum.KeyCode.Space or SH[c].Grounded == true) and climb ~= nil then
		StopClimbing()
	end
end)

game:GetService("RunService").Heartbeat:Connect(function()
	--Moves the player
	local climbingattachment = hrp:FindFirstChild("climbingattachment")
	if hum:GetState() ~= Enum.HumanoidStateType.Climbing or climbingattachment == nil then return end
	local movement = climbingattachment:FindFirstChild("LinearVelocity")
	local X = XAxis[2]-XAxis[1]
	local Y = YAxis[1]-YAxis[2]
	movement.VectorVelocity = Vector3.new(X*speed, Y*speed, 0)
end)

Thank you in advance :slight_smile:.

3 Likes

Hey, so firstly, you should have this script on character scripts, so it loads everytime you character respawn, so it will work after the player dies.

Secondly, probably the easiest way to solve climbing from one wall to another at a 90 degrees angle is to cast multiple rays, one alternative to choose origin and direction of the extra two rays (each one going opposite lateral directions), is to take the variable which defines the lengtth of the ray you already have, and use that to diagonally transverse to the origin.

Example, your ray max length is 10, then take the player position, and move 10 units forward and later 10 units left or right (So use a loop for it, with a variable i ranging from -1 to 1 (-1, 0, 1), and multiply the lateral translation by it.

When you detect 90 degree angle, get distance between player and ray hit position along the wall normal, when it hits a threshold and the player wants move even further to the 90 degrees angle wall, run a code to climb the player onto that wall (you could get pos where character will be at the wall by getting position player is on the wall plus the character width along the normal of the wall the character currently is.

Another thing, you could probably just take the normal of the wall, and use that to get a movement plane (There must be a constraint to reestrain the movement to a plane), and use dot product betweenn camera directtion and the movementt direction to get final direction on the plane (more on this if you didnttt understand, cuz i gotta think about it more though)

2 Likes

Thank you for the reply! The script is already in StarterCharacterScripts for a start, and I kind of understood most of what you said, but I’m having trouble comprehending the last few parts of your reply. I get the part about using multiple ray cast (kinda), but I don’t really understand the parts about actually moving the player to the other side of the wall, the movement plane, and the camera dot product. Could you explain those parts in more detail please? Maybe use a couple more examples? Also, should the extra ray casts be towards the wall or facing away form the right/left side of the player? Thank you! :slight_smile:

1 Like

Have you tried boosting the player slightly in the direction once they have turned the corner to give it the effect that they quickly swing around it. You can try adding a secondary body velocity or modifying the current one to give it a short boost once the corner has been reached, the direction will be based off the keys that are being held down on the client

2 Likes

Thanks for the suggestion, but I don’t think that would fix the main issues with the system. Gonna try it anyway tho.

1 Like

Hey, so let’s clear out these parts.

First about the movement plane: it is basically a constraint to the movement of the player, where it can only move relative to the wall’s surface, not being able to move outwards or inwards to the wall (along the surface normal of the wall), and to do that, we need to realize two steps:

1 - Get movement direction from associated key and camera direction
You can probably use what you have already

2 - Project movement direction into wall’s surface using wall’s normal vector
I’m not at home atm so I would need to test some stuff, but there are plenty of algorithms to project a vector onto a plane (A simple Google search would do)

Now, you got this movement vector and you can move around the wall, but you can’t turn at corners, which is due to a limitation in wall detection, and that can be solved by adding two extra rays that have their origins at the left or right to the player and forward to it, and point laterally in direction to the player. Heres an illustration:

Sorry for bad image but its the best I can do at this school’s laptop, but basically I drew the 3 rays youll need to detect any kind of wall, then from these lateral rays, youll get a position on the wall after the corner, then you can compute the distance from this point to player along player lateral axis (Using dot product)

Then you take this distance and if its less than some threshold, disable climbing system for a moment to allow a transition from one wall to another to occur, you can do this by emulating a circular movement around wall (which will fit any kind of wall), you can set a time for the transition to happen and map the time elapsed from start to a velocity vector.

More on this later if you cant do that, but may there be some posts on how to do that.

And sorry for not helping any further but as I said im not at home atm, but it would be cool to help more cuz its something I will probably need to code later on my game.

1 Like

Thank you so much for additional information! I was able to make the climbing system a lot better, although there are still some issues with it. For example, I was able to make it so that you can climb around sharp corners, but I was unable to project the movement direction onto the walls surface. Its not that I didn’t find any algorithms that worked (at least I think they did), its that every time I tried to plug the algorithm into the linear velocity I was using for movement, It seemed like Roblox was having a seizure.

The algorithms that I tried were this one,

local vec1 = direction:Cross(normal)
local vec2 = normal:Cross(vec1).Unit
Linearvelocity.VectorVelocity = vec2 * speed

and this one.

local vec = (direction - direction:Dot(normal.Unit) * normal.Unit).Unit
Linearvelocity.VectorVelocity = vec * speed

When I tried to use either one, this happened.


I have no idea why this is, but because of it, I decided to just go with my original method of moving.

Other issue I had was emulating a circular movement around the wall. To keep it short, I had no idea how to do it lol.

The final and main issue that I’m still having is that I still don’t know what to do for moving diagonally. I would appreciate your help if your willing :slight_smile:.