Wallclimb system help

Okay sorry for the necrobump but I’ve tried to create my own wall climb system to see if I could well… do it. And I did so I’d like to provide my script here and it might help.

--// Variables \\--

local ReplicatedStorage: ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService: RunService = game:GetService("RunService")
local UserInputService: UserInputService = game:GetService("UserInputService")
local Events: Folder = ReplicatedStorage.Events
local Player: Player = game.Players.LocalPlayer
local Character: Model = Player.Character or Player.CharacterAdded:Wait()
local HumanoidRootPart: Part = Character:WaitForChild("HumanoidRootPart")
local Humanoid: Humanoid = Character:WaitForChild("Humanoid")
local Keys: {Enum.KeyCode} = {
    [Enum.KeyCode.W] = true,
    [Enum.KeyCode.A] = true,
    [Enum.KeyCode.S] = true,
    [Enum.KeyCode.D] = true,
    [Enum.KeyCode.Space] = true
}
local Rayparams: RaycastParams = RaycastParams.new()
local isClimbing: boolean = false
local CLIMB_MULTIPLIER: number = 0.25
Rayparams.FilterDescendantsInstances = Character:GetChildren()
Rayparams.FilterType = Enum.RaycastFilterType.Exclude
Rayparams.RespectCanCollide = true

--// Functions \\--

--// This function is just for seeing where a casted ray hit something
local function VisualizeIntersection(position: Vector3): nil
    local Highlight: Highlight = Instance.new("Highlight")
    local Part: Part = Instance.new("Part")
    Part.Parent = workspace
    Highlight.Parent = Part
    Part.Size = Vector3.new(1, 1, 1)
    Part.Anchored = true
    Part.CanCollide = false
    Part.CanQuery = false
    Part.Position = position
end

--// The main function of this script
local function Climb(): nil
    local HoldingKeys: {any}  = UserInputService:GetKeysPressed()
    --// Go thru every key that they're currently holding
    for i: number, key: Enum.KeyCode in pairs(HoldingKeys) do
        --// We are detecting if they are pressing WASD or the space bar.. if not, then the function is gonna stop here
        if not Keys[key.KeyCode] then return end
        local Cast: RaycastResult = workspace:Raycast(HumanoidRootPart.Position, HumanoidRootPart.CFrame.LookVector, Rayparams)
        if not Cast and isClimbing then
            if key.KeyCode == Enum.KeyCode.W then
                HumanoidRootPart.Anchored = false
                HumanoidRootPart:ApplyImpulse(HumanoidRootPart.CFrame.UpVector * 1000)
            end
                isClimbing = false
                HumanoidRootPart.Anchored = false
            return
                elseif not Cast then return
                end
        if key.KeyCode == Enum.KeyCode.W then
            if isClimbing then
                HumanoidRootPart.CFrame = CFrame.lookAt(HumanoidRootPart.Position, Cast.Position - Cast.Normal) + HumanoidRootPart.CFrame.UpVector * CLIMB_MULTIPLIER
            else
                HumanoidRootPart.Anchored = true
                HumanoidRootPart.CFrame = CFrame.lookAt(HumanoidRootPart.Position, Cast.Position - Cast.Normal)
                isClimbing = true
            end
        elseif key.KeyCode == Enum.KeyCode.A then
            if isClimbing then
                local LeftAdjacentCast: RaycastResult = workspace:Raycast(HumanoidRootPart.Position + (HumanoidRootPart.CFrame.LookVector * 1.5) + -HumanoidRootPart.CFrame.RightVector, HumanoidRootPart.CFrame.RightVector, Rayparams)
                local LeftPerpendicularCast: RaycastResult = workspace:Raycast(HumanoidRootPart.Position, -HumanoidRootPart.CFrame.RightVector, Rayparams)
                if LeftAdjacentCast and not LeftPerpendicularCast then
                    HumanoidRootPart.CFrame = CFrame.lookAt(LeftAdjacentCast.Position + LeftAdjacentCast.Normal / 1.25, LeftAdjacentCast.Position)
                    return
                end
                if LeftPerpendicularCast then
                    HumanoidRootPart.CFrame = CFrame.lookAt(LeftPerpendicularCast.Position + LeftPerpendicularCast.Normal / 1.25, LeftPerpendicularCast.Position)
                    return
                end
                HumanoidRootPart.CFrame = CFrame.lookAt(HumanoidRootPart.Position, Cast.Position - Cast.Normal) + -HumanoidRootPart.CFrame.RightVector * CLIMB_MULTIPLIER
            end
        elseif key.KeyCode == Enum.KeyCode.S then
            if isClimbing then
                HumanoidRootPart.CFrame = CFrame.lookAt(HumanoidRootPart.Position, Cast.Position - Cast.Normal) + -HumanoidRootPart.CFrame.UpVector * CLIMB_MULTIPLIER
            end
        elseif key.KeyCode == Enum.KeyCode.D then
            if isClimbing then
                local RightAdjacentCast: RaycastResult = workspace:Raycast(HumanoidRootPart.Position + (HumanoidRootPart.CFrame.LookVector * 1.5) + HumanoidRootPart.CFrame.RightVector, -HumanoidRootPart.CFrame.RightVector, Rayparams)
                local RightPerpendicularCast: RaycastResult = workspace:Raycast(HumanoidRootPart.Position, HumanoidRootPart.CFrame.RightVector, Rayparams)
                if RightAdjacentCast and not RightPerpendicularCast then
                    HumanoidRootPart.CFrame = CFrame.lookAt(RightAdjacentCast.Position + RightAdjacentCast.Normal / 1.25, RightAdjacentCast.Position)
                    return
                end
                if RightPerpendicularCast then
                    HumanoidRootPart.CFrame = CFrame.lookAt(RightPerpendicularCast.Position + RightPerpendicularCast.Normal / 1.25, RightPerpendicularCast.Position)
                    return
                end
                HumanoidRootPart.CFrame = CFrame.lookAt(HumanoidRootPart.Position, Cast.Position - Cast.Normal) + HumanoidRootPart.CFrame.RightVector * CLIMB_MULTIPLIER
            end
        elseif key.KeyCode == Enum.KeyCode.Space then
            if isClimbing then
                isClimbing = false
                HumanoidRootPart.Anchored = false
            end
        end
    end
end

--// To constantly detect when the player is climbing.. yeah (Enum.RenderPriority.Camera was chosen since it is when all input is rendered)
RunService:BindToRenderStep("ClimbDetector", Enum.RenderPriority.Camera.Value, Climb)

And here’s the actual adjacent detection part in case you don’t feel like reading:


This is for A


elseif key.KeyCode == Enum.KeyCode.A then
            if isClimbing then
                local LeftAdjacentCast: RaycastResult = workspace:Raycast(HumanoidRootPart.Position + (HumanoidRootPart.CFrame.LookVector * 1.5) + -HumanoidRootPart.CFrame.RightVector, HumanoidRootPart.CFrame.RightVector, Rayparams)
                local LeftPerpendicularCast: RaycastResult = workspace:Raycast(HumanoidRootPart.Position, -HumanoidRootPart.CFrame.RightVector, Rayparams)
                if LeftAdjacentCast and not LeftPerpendicularCast then
                    HumanoidRootPart.CFrame = CFrame.lookAt(LeftAdjacentCast.Position + LeftAdjacentCast.Normal / 1.25, LeftAdjacentCast.Position)
                    return
                end
                if LeftPerpendicularCast then
                    HumanoidRootPart.CFrame = CFrame.lookAt(LeftPerpendicularCast.Position + LeftPerpendicularCast.Normal / 1.25, LeftPerpendicularCast.Position)
                    return
                end
                HumanoidRootPart.CFrame = CFrame.lookAt(HumanoidRootPart.Position, Cast.Position - Cast.Normal) + -HumanoidRootPart.CFrame.RightVector * CLIMB_MULTIPLIER
            end

This is for D


 elseif key.KeyCode == Enum.KeyCode.D then
            if isClimbing then
                local RightAdjacentCast: RaycastResult = workspace:Raycast(HumanoidRootPart.Position + (HumanoidRootPart.CFrame.LookVector * 1.5) + HumanoidRootPart.CFrame.RightVector, -HumanoidRootPart.CFrame.RightVector, Rayparams)
                local RightPerpendicularCast: RaycastResult = workspace:Raycast(HumanoidRootPart.Position, HumanoidRootPart.CFrame.RightVector, Rayparams)
                if RightAdjacentCast and not RightPerpendicularCast then
                    HumanoidRootPart.CFrame = CFrame.lookAt(RightAdjacentCast.Position + RightAdjacentCast.Normal / 1.25, RightAdjacentCast.Position)
                    return
                end
                if RightPerpendicularCast then
                    HumanoidRootPart.CFrame = CFrame.lookAt(RightPerpendicularCast.Position + RightPerpendicularCast.Normal / 1.25, RightPerpendicularCast.Position)
                    return
                end
                HumanoidRootPart.CFrame = CFrame.lookAt(HumanoidRootPart.Position, Cast.Position - Cast.Normal) + HumanoidRootPart.CFrame.RightVector * CLIMB_MULTIPLIER
            end

And finally, you can try it but you need to be on PC cuz I didn’t make mobile support (and the place is uncopylocked)



Logic (for A):

local LeftAdjacentCast: RaycastResult = workspace:Raycast(HumanoidRootPart.Position + (HumanoidRootPart.CFrame.LookVector * 1.5) + -HumanoidRootPart.CFrame.RightVector, HumanoidRootPart.CFrame.RightVector, Rayparams)

Here, I’m just moving the starting position from where the ray is being shot a little forward and to the right. Then since the starting position is to the left of the wall’s side, I shoot the ray to the left.

HumanoidRootPart.CFrame = CFrame.lookAt(LeftAdjacentCast.Position + LeftAdjacentCast.Normal / 1.25, LeftAdjacentCast.Position)

Then I just set the character’s position to the hit surface and the surface normal (divided by 1.25 so the player is still touching the wall, you don’t need to add it probably). And I just make the character look at the hit position (intersection position).

For D it’s literally the same thing but reversed



I looked at a couple of posts like you did but nothing really explicitly helped. So I just though of a couple of solutions and eventually I found the solution above. Took about a day to create and fix


You’ll probably noticed that I did all of this on the client side. But you should still probably be able to use it

I tested in my game, it does work however it is also buggy, when switching falls the player will often just start spinning or face thru it. That is copying your code without modifications. Ill try to adapt it to work with mine but ill give you solution anyways as is by far the most helpful reply i got

1 Like

Yeah and I just finished recreating it modularly too but haven’t really ran into that problem you just mentioned.

And you see that / 1.25? That’s probably the issue. I just did that since it would stop my system from working if the player ain’t close enough to the wall

And I didn’t make animations or lerp or tween so yeah it’s probably a little choppy

I modified the code a bit and with the approach you made it worked as intended

local flyEvent = game.ReplicatedStorage:WaitForChild("Wall")
local UIS = game:GetService("UserInputService")

local RS = game:GetService("RunService")

local RParams = RaycastParams.new()
RParams.FilterType = Enum.RaycastFilterType.Exclude

local nearWall = false

local adj = false

UIS.InputBegan:Connect(function(input, gp)
	if gp or nearWall == false then return end

	if input.KeyCode == Enum.KeyCode.W then
		flyEvent:FireServer("Up", true)
	end

	if input.KeyCode == Enum.KeyCode.D then

		flyEvent:FireServer("Right", true)
	end

	if input.KeyCode == Enum.KeyCode.A then

		flyEvent:FireServer("Left", true)
	end

	if input.KeyCode == Enum.KeyCode.S then
		flyEvent:FireServer("Down", true)
	end

end)

UIS.InputEnded:Connect(function(input, gp)
	if gp or nearWall == false then return end

	if input.KeyCode == Enum.KeyCode.W then
		flyEvent:FireServer("Up", false)
	end

	if input.KeyCode == Enum.KeyCode.D then
		flyEvent:FireServer("Right", false)
	end

	if input.KeyCode == Enum.KeyCode.A then

		flyEvent:FireServer("Left", false)
	end

	if input.KeyCode == Enum.KeyCode.S then
		flyEvent:FireServer("Down", false)
	end

end)

repeat task.wait()

until game.Players.LocalPlayer.Character

RParams.FilterDescendantsInstances = {game.Players.LocalPlayer.Character}

local animationPlaying = false

local track = game.Players.LocalPlayer.Character.HumanoidRootPart.Parent.Humanoid.Animator:LoadAnimation(script.Animation)

local SwitchingWalls = false


RS.Heartbeat:Connect(function()
	local humanoidRootPart = game.Players.LocalPlayer.Character:FindFirstChild("HumanoidRootPart")
	if not humanoidRootPart then return end

	local raycastResult = workspace:Raycast(
		humanoidRootPart.Position,
		humanoidRootPart.CFrame.LookVector * 1.3, 
		RParams
	)

	local raycastResultRight = workspace:Raycast(
		humanoidRootPart.Position,
		humanoidRootPart.CFrame.RightVector * 1.3, 
		RParams
	)


	local raycastResultLeft = workspace:Raycast(
		humanoidRootPart.Position,
		humanoidRootPart.CFrame.RightVector * -1.3, 
		RParams
	)
	
	local RightAdjacentCast = workspace:Raycast(humanoidRootPart.Position + (humanoidRootPart.CFrame.LookVector * 2) + humanoidRootPart.CFrame.RightVector, -humanoidRootPart.CFrame.RightVector * 5, RParams)
	local LeftAdjacentCast = workspace:Raycast(humanoidRootPart.Position + (humanoidRootPart.CFrame.LookVector * 2) + -humanoidRootPart.CFrame.RightVector, humanoidRootPart.CFrame.RightVector * 5, RParams)
	
	if raycastResult and raycastResult.Instance then
		if raycastResult.Instance:GetAttribute("CanClimb") then
			if animationPlaying == false then
				track:Play()
				animationPlaying = true
			end
			nearWall = true
			if raycastResultLeft and raycastResultLeft.Instance and raycastResultLeft.Instance:GetAttribute("CanClimb") and SwitchingWalls == false  then
				SwitchingWalls = true
				--humanoidRootPart.CFrame = CFrame.new(humanoidRootPart.Position, humanoidRootPart.Position + -raycastResultLeft.Normal)
				humanoidRootPart.CFrame = CFrame.lookAt(raycastResultLeft.Position + raycastResultLeft.Normal / 2, raycastResultLeft.Position)
				task.wait(1)
				SwitchingWalls = false
			elseif raycastResultRight and raycastResultRight.Instance and raycastResultRight.Instance:GetAttribute("CanClimb") and SwitchingWalls == false then
				SwitchingWalls = true
				--humanoidRootPart.CFrame = CFrame.new(humanoidRootPart.Position, humanoidRootPart.Position + -raycastResultRight.Normal)
				humanoidRootPart.CFrame = CFrame.lookAt(raycastResultRight.Position + raycastResultRight.Normal / 2, raycastResultRight.Position)
				task.wait(1)
				SwitchingWalls = false
			elseif RightAdjacentCast and RightAdjacentCast.Instance and RightAdjacentCast.Instance:GetAttribute("CanClimb") and SwitchingWalls == false  then
				humanoidRootPart.CFrame = CFrame.lookAt(RightAdjacentCast.Position + RightAdjacentCast.Normal / 2, RightAdjacentCast.Position)
				
			elseif LeftAdjacentCast and LeftAdjacentCast.Instance and LeftAdjacentCast.Instance:GetAttribute("CanClimb") and SwitchingWalls == false then
				humanoidRootPart.CFrame = CFrame.lookAt(LeftAdjacentCast.Position + LeftAdjacentCast.Normal / 2, LeftAdjacentCast.Position)
			else
				humanoidRootPart.CFrame = CFrame.lookAt(raycastResult.Position + raycastResult.Normal / 2, raycastResult.Position)
			end


		end


	else
		if not raycastResult and not raycastResultLeft and not raycastResultRight and not RightAdjacentCast then
			print("drop client")
			nearWall = false
			animationPlaying = false
			track:Stop()
			flyEvent:FireServer("Stop")
		end

	end
end)
1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.