How to make a wallhang system?

Hello, I was wondering how to make a wallhang system where you can move left/right between on the wall when you press a and d when you are hanging on the wall, also when you reach the edge of the wall, you switch between the sides of the wall, so that you dont fall over.

I have already made a script that allows the player to hang, I just need to allow left/right movement while wallhanging.

1 Like

You will need to raycast in front of the character for that. If the player press A or D then you could use a body mover (align position or something like this?) or a tween which could be less efficient

1 Like

Do you have a example that I could use or base my script upon?

Heres my script:

local UIS = game:GetService("UserInputService")
local player = game.Players.LocalPlayer
local char = script.Parent

local iswallclimbing = false

local function findPartInFront()
	
	local rayParams = RaycastParams.new()
	rayParams.FilterType = Enum.RaycastFilterType.Exclude
	rayParams.FilterDescendantsInstances = {char}
	
	local origin = char.Humanoid.RootPart.Position
	local direction = char.Humanoid.RootPart.CFrame.LookVector
	local rayLength = 1 

	local result = game.Workspace:Raycast(origin, direction * rayLength, rayParams)
	if result then
		return result.Instance
	end

	return nil
end

UIS.InputBegan:Connect(function(i, g)
	if not g and i.KeyCode == Enum.KeyCode.Space then
		
		if iswallclimbing == true then
			
			char.HumanoidRootPart.Anchored = false
			iswallclimbing = false
			
			char.HumanoidRootPart.Velocity = Vector3.new(0, 50, 0)
			
		else
			local humanoid : Humanoid = char:WaitForChild("Humanoid")

			if humanoid:GetState() == Enum.HumanoidStateType.Freefall and findPartInFront() then
				local partinfront = findPartInFront()

				local playerHeadPosition = char:FindFirstChild("Head")
				local wallTopPosition = partinfront.Position + partinfront.Size/2  

				if playerHeadPosition and wallTopPosition then
					if playerHeadPosition.Position.Y > wallTopPosition.Y then

						char.HumanoidRootPart.Anchored = true
						iswallclimbing = true

					end
				end

			end
		end
		
	end
end)

Also, align position moves 2 attachments closer to each other (I think), which is not my goal, are there any other body movers?

Keep in mind, I need to keep the humanoidRootPart anchored so that the player doesn’t fall down to the floor, since I need the player to be attached to the wall.

I needed a wall climb for a game so i did it, this is not the whole script but here is how i keep the player against the wall. It is important to note that my need case was for flat faces and i also use deprecated body movers:

local a, w, s, d = 0, 0, 0, 0
local n = 500
local l = 0
			
if left then
 a = -n
 l = n
				
end

if front then
 w = n * 2

end

if back then
 s = -(n * 2)

end

if right then
 d = n
 l = n

end

local torque = 999999999999999
local bg = root:FindFirstChild("BodyGyro") or Instance.new("BodyGyro")
bg.Parent = root
bg.CFrame = CFrame.new(root.Position, result.Position)
bg.MaxTorque = Vector3.new(0, torque, 0)
bg.P = torque
bg.D = 0
			
local upvec = CFrame.new(root.Position, result.Position).UpVector * (w + s)
local rightvec = CFrame.new(root.Position, result.Position).RightVector * (a + d)
local lookvec = CFrame.new(root.Position, result.Position).LookVector * l
			
local bv = root:FindFirstChild("BodyVelocity") or Instance.new("BodyVelocity")
bv.Parent = root
bv.Velocity = upvec + rightvec + lookvec

Before that, i use UserInputService to check which input i press. Then, i use a bool variable for each 4 movement keys. If the input Start then InputBegan, if the input ends then InputEnded.

Once you got it, you will need to raycast in front of the character. If result and result.Normal.Y == 0 then it means there is a wall of a part in front of you. if not result or result.Normal.Y ~= 0 then it means you have to delete the body mover (or align movers or waterever the name is). Also, if all the 4 bool variables are set to false then delete the body movers or align movers.

After that, you will always find someone to say “This is not the right way to do that, there is a better way”. But for me it works fine. Hope it will helps you!

in my use case, i needed to delete the body movers or align movers when the player doesnt press any key, in your case, you dont need to delete every time which means the code will be a bit different

This above solution will keep the player attached to the wall. You still need an animation and have to place the character accordingly for the full illusion.

As i said, my solution works perfectly for my use case because i just need to give the player more movement. And as i said, the code will be different for his game. I just gave one possible solution

Apologies, I am still new to the post. OP, do you want a free climbing system, or a controlled climbing system?

If you want a controlled climb, I remember a post talking about how you make invisible waypoints where if the player pushes a or d it lerps/tweens the character to the adjacent waypoint in line with a animation. I will see if I can find such topic.

Ok, during my search I found something completely different, I modified the script from this topic solved by @httpDerpyy, https://devforum.roblox.com/t/help-with-ledge-moving-system/1466172:

local rayLeft = Ray.new(hrp.CFrame.p, hrp.CFrame.lookVector * 3)

		local part, pos, normal = workspace:FindPartOnRay(rayLeft, Character)

		local cross = Vector3.new(0, 1, 0):Cross(normal)


		if uis:IsKeyDown(Enum.KeyCode.A) then
			if not holding then return end
			hrp.Anchored = false --This allows the body gyro to move the character
			local Vel = hrp.Vel --Call the hrp's velocity object
			local Gyro = hrp.Gyro -- Get the bodyGyro form the HRP
            Gyro.CFrame = hrp.CFrame * CFrame.new(-1, 0, 0) --This makes sure the character is facing foward
			Gyro.MaxTorque = Vector3.new(1 ,1 ,1) * math.huge --No need for change
			Vel.MaxForce = Vector3.new(1, 1, 1) * math.huge -- No need for change
			Vel.Velocity = cross * -10 -- change this to the according to the climb speed
         end

This is not the full script, but feel free to personalize the velocity and body gyro system.

Okay so, I’ve figured out my own way of doing it, but one slight issue, the position changes dont replace in the server, only in the client.

Code:

local UIS = game:GetService("UserInputService")
local player = game.Players.LocalPlayer
local char = script.Parent
local RS = game:GetService("RunService")

local iswallclimbing = false
local canmoveleft = true
local canmoveright = true

local function findPartInFront(pos)
	
	if not pos then 
		local rayParams = RaycastParams.new()
		rayParams.FilterType = Enum.RaycastFilterType.Exclude
		rayParams.FilterDescendantsInstances = {char}

		local origin = char.Humanoid.RootPart.Position
		local direction = char.Humanoid.RootPart.CFrame.LookVector
		local rayLength = 1 

		local result = game.Workspace:Raycast(origin, direction * rayLength, rayParams)
		if result then
			return result.Instance
		end

		return nil
	else
		local rayParams = RaycastParams.new()
		rayParams.FilterType = Enum.RaycastFilterType.Exclude
		rayParams.FilterDescendantsInstances = {char}

		local origin = pos
		local direction = char.Humanoid.RootPart.CFrame.LookVector
		local rayLength = 1 

		local result = game.Workspace:Raycast(origin, direction * rayLength, rayParams)
		if result then
			return result.Instance
		end

		return nil
	end
end

local x = 0

UIS.InputBegan:Connect(function(i, g)
	if not g and i.KeyCode == Enum.KeyCode.Space then
		
		if iswallclimbing == true then
			
			char.HumanoidRootPart.Anchored = false
			iswallclimbing = false
			char.Humanoid.PlatformStand = false
			char.Humanoid.AutoRotate = true
			
			char.HumanoidRootPart.Velocity = Vector3.new(0, 50, 0)
			canmoveleft = true
			canmoveright = true
			
		else
			local humanoid : Humanoid = char:WaitForChild("Humanoid")
			

			if humanoid:GetState() == Enum.HumanoidStateType.Freefall and findPartInFront() then
				local partinfront = findPartInFront()

				local playerHeadPosition = char:FindFirstChild("Head")
				local wallTopPosition = partinfront.Position + partinfront.Size/2  
				

				if playerHeadPosition and wallTopPosition then
					if playerHeadPosition.Position.Y > wallTopPosition.Y then
						
						char.HumanoidRootPart.Anchored = true
						iswallclimbing = true
						humanoid.PlatformStand = true
						humanoid.AutoRotate = false
						
					end
				end

			end
		end
		
	elseif not g and i.KeyCode == Enum.KeyCode.A then
		x = -1
	elseif not g and i.KeyCode == Enum.KeyCode.D then
		x = 1
	end
end)	

UIS.InputEnded:Connect(function(i, g)
	if not g and i.KeyCode == Enum.KeyCode.A then
		x = 0
	elseif not g and i.KeyCode == Enum.KeyCode.D then
		x = 0
	end
end)


RS.Stepped:Connect(function()
	if iswallclimbing == true and findPartInFront() then
		
		if x == 1 and canmoveleft == false then 
			return
		elseif x == -1 and canmoveright == false then
			return
		end
		
		char.HumanoidRootPart.CFrame = char.HumanoidRootPart.CFrame * CFrame.new(x*0.1, 0, 0)
		
		if x == 1 and canmoveright == false then
			canmoveright = true
		elseif x == -1 and canmoveleft == false then
			canmoveleft = true
		end
		
	elseif iswallclimbing == true and not findPartInFront() then
		
		if x == -1 then
			canmoveright = false
		else
			canmoveleft = false
		end
		
		char.HumanoidRootPart.CFrame = char.HumanoidRootPart.CFrame * CFrame.new(-x*0.1, 0, 0)
		
	end
end)

Also, how do I snap the players orientation to the wall?

1 Like

You can use the BodyGyro (it is now deprecated, I forgot what the new Instance was) to handle the orientation as shown above.