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