A strange side effect of my wall climb implementation

Recently I’ve been scripting movement system for my game. The script is as follows:


---- Player related variables
local player = game.Players.LocalPlayer
local character = player.CharacterAdded:Wait()
local humanoid: Humanoid = character.Humanoid
local humanoidRootPart: BasePart = character.HumanoidRootPart
local animator = humanoid.Animator

---- Movement system related variables
local isClimbing = false
local climbSpeed = 9
local climbAttachment
local climbAlignPosition
local climbAnimation = Instance.new("Animation")
climbAnimation.AnimationId = "rbxassetid://88696739277660"
local climbAnimationTrack = animator:LoadAnimation(climbAnimation)

local isRunning = false
local doubleTapCheck
local walkSpeed = game.StarterPlayer.CharacterWalkSpeed 
local runSpeed = 16

---- Functions
local function stopClimbing()
	humanoid.AutoRotate = true
	
	climbAlignPosition:Destroy()
	climbAttachment:Destroy()
	climbAnimationTrack:Stop()	
	
	isClimbing = false
end

local function getWallInFront()
	local raycastOrigin = humanoidRootPart.Position
	local raycastDirection = humanoidRootPart.CFrame.LookVector * 1.5
	return workspace:Raycast(raycastOrigin, raycastDirection)
end

local function tryClimb(wall)
	if not wall then return end
	
	-- Setup character position and orientation
	humanoidRootPart.CFrame = CFrame.new(wall.Position + (wall.Normal // 1), wall.Position)
	humanoid.AutoRotate = false
	
	-- Setup forces for climbing
	climbAttachment = Instance.new("Attachment")
	climbAttachment.Parent = humanoidRootPart
	
	climbAlignPosition = Instance.new("AlignPosition")
	climbAlignPosition.Parent = humanoidRootPart
	climbAlignPosition.Mode = Enum.PositionAlignmentMode.OneAttachment
	climbAlignPosition.RigidityEnabled = true
	climbAlignPosition.Attachment0 = climbAttachment
	climbAlignPosition.Position = humanoidRootPart.CFrame.Position
	
	climbAnimationTrack:Play()
	
	isClimbing = true
end

local function onInputBegan(input, gameProcessedEvent)
	if gameProcessedEvent then return end
	
	if input.KeyCode == Enum.KeyCode.LeftControl then 
		if not isClimbing then
			local wall = getWallInFront()
			tryClimb(wall) 
		elseif isClimbing then stopClimbing() end
	end
end

local function onHeartbeat(deltaTime)
	if not isClimbing then return end 	-- Check if currently climbing

	local wall = getWallInFront()
	if not wall then stopClimbing() end -- Check if there's a wall to climb
	
	-- Calculate climbing movement for character
	local xMovement = humanoidRootPart.CFrame.RightVector.X * deltaTime * (
		(game.UserInputService:IsKeyDown(Enum.KeyCode.A) and -climbSpeed or 0) + 
		(game.UserInputService:IsKeyDown(Enum.KeyCode.D) and climbSpeed or 0)
	)
	local yMovement = deltaTime * (
		(game.UserInputService:IsKeyDown(Enum.KeyCode.W) and climbSpeed or 0) + 
		(game.UserInputService:IsKeyDown(Enum.KeyCode.S) and -climbSpeed or 0)
	)
	local zMovement = humanoidRootPart.CFrame.RightVector.Z * deltaTime * (
		(game.UserInputService:IsKeyDown(Enum.KeyCode.A) and -climbSpeed or 0) + 
		(game.UserInputService:IsKeyDown(Enum.KeyCode.D) and climbSpeed or 0)
	)
	climbAlignPosition.Position += Vector3.new(xMovement, yMovement, zMovement)
end

---- Init
game.UserInputService.InputBegan:Connect(onInputBegan)
game["Run Service"].Heartbeat:Connect(onHeartbeat)

The current implementation behaves as expected except for one oddity (which I believe has something to do with not disabling default character controls when climbing) - as you attempt to move to right or left while climbing, the character proceeds to sink into the wall.
Video demonstration:

I’ve tried enabling PlatformStanding/Physics, which ended up partially fixing the issue, but alas introducing another one - the character would wiggle uncontrollably in accordance to physics and everything would eventually break.

Now, my questions for this topic - what are the possible solutions to fixing this issue using my current approach? Are there actually any other objectively better approaches that I’m not aware of?

Please enlighten me and thank you for your time spent reading this post.

1 Like

I believe what’s happening is the humanoids ground-movement code is still running every physics step:

  • Pressing A / D sets Humanoid.MoveDirection sideways in local space
  • Since you’ve rotated the RootPart to face the wall, that sideways vector points into the wall’s normal
  • The humanoid applies an internal impulse to reach MoveDirection * WalkSpeed each frame; the wall pushes back, so the solver compromises and your root slowly tunnels inward

Try setting the humanoid state to humanoid:ChangeState(Enum.HumanoidStateType.Physics), this should remove the walk forces

Let me know if this works out

1 Like

Just make it physics based or if you want to hardcode it then use raycasts

Unfortunately, doesn’t help, as I mentioned that I have anticipated some inner engine workings and tried to resolve them by doing exactly what you suggested:

1 Like

On your original recording (before doing anything with PlatformStanding/Physics), is the character phasing into the wall? Like as in, if you kept holding D to go to the right – would you phase through the wall or would it just bug you out?

After some testing with your original code, I found that the .CFrame code sometimes orients the character the wrong way (and sometimes triggers an automatic wall-climbing cancel). Instead of

humanoidRootPart.CFrame = CFrame.new(wall.Position + (wall.Normal // 1), wall.Position)

could you try:

humanoidRootPart.CFrame = CFrame.lookAlong(wall.Position + (wall.Normal // 1), -wall.Normal)
1 Like

Hello, sorry for a late reply

I assume by “phasing through the wall” you mean completely go into it, in which case - no, it does not happen. The character only has it’s arms and legs submerged and generally positions much closer to the wall, but doesn’t straight up go through it.

I also tried your suggestion, and it seems to have no impact on the problematic behaviour described previously in my post.

Since the time of OP, I have also discovered another abnormality - for whatever reason, when holding W while climbing, the character rotates specifically in the right direction, it’s especially easy to observe when disabling climb animation.

Hope this helps thinking of a solution. If there’s anything you don’t understand about my code, please let me know and thanks for sticking with me on this one!

I think your problem is that you are using humanoidRootPart.CFrame.RightVector (and other hrp direction vectors) which will give you slightly inaccurate results if the root part moves in some unexpected way, that then add up overtime giving you the weird sinking behavior.

Try saving your initial wall CFrame that you set the character to when it first gets on the wall, and use that CFrame’s direction vectors instead.

And the slow rotating as you are only moving up could be simply because align position does not align orientation. You probably need an align orientation as well.

Edit: Now that I think about it, the crux of the issue is probably what I just said, the character is not being aligned to any orientation so it can drift slightly which creates these weird effects.

I lowkey don’t know what’s happening here – would you be willing to send your place file? Because I can’t seem to replicate your issues, or I probably missed something.

1 Like

Sure thing, here it is
place.rbxl (1.5 MB)

Quick notice - I tested your solution, and it indeed worked.

I will mark your post as solution for now until @Downrest comes up with solution of their own and if it turns out to be more elegant I’ll transfer the solution award to them

Edit: …upon further testing, I found out this also introduced yet another bug, which I assume is fixable by introducing more raycasts, but I’d rather not add layers upon layers of “crutches” if possible, so definitely will wait for Downrest to respond.

2 Likes

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