Wall Climbing System, Orientation Issue

Hello, I came across trying to make a wall climbing system, and wrote a script with some help, but I don’t know how my logic turned out, and I can’t seem to make the orientation right, every frame, the character turns using the “face” variable

Here is a video:

Here is the function:

function onClimb()
	local origin = rootpart.Position
	local direction = rootpart.CFrame.LookVector

	local result = workspace:Raycast(origin, direction, params)
	if result then
		climbing=true
		script.RemoteEvent:FireServer(char.ForceShiftLock, true)
		
		rootpart.CFrame = CFrame.new(rootpart.CFrame.p, Vector3.new(rootpart.Position.X - result.Normal.X, rootpart.Position.Y, rootpart.Position.Z - result.Normal.Z))
		face = CFrame.new(result.Position+result.Normal, result.Position)

		hum.AutoRotate=false
		hum.PlatformStand=true

		climbMove.Parent=rootpart
		climbGyro.Parent=rootpart

		repeat
			run.RenderStepped:Wait()
			climbGyro.CFrame=face or CFrame.new()
			
			if rootpart.Position.Y > result.Instance.Size.Y then
				LedgeClimb()
				climbing=false
				offClimb()
				local ve = Instance.new("BodyVelocity")
				ve.MaxForce = Vector3.new(1, 1, 1) * 40000
				ve.Velocity = rootpart.CFrame.lookVector * 10 + Vector3.new(0, 7, 0)
				ve.Parent = rootpart
				game.Debris:AddItem(ve, 0.5)
				game.TweenService:Create(ve, TweenInfo.new(0.5, Enum.EasingStyle.Sine), {
				Velocity = rootpart.CFrame.lookVector * 10 + Vector3.new(0, 12, 0)
				}):Play()
			end
			
			local sideOrigin = rootpart.CFrame*CFrame.new(0, -0.4, -1).Position
			local sideDirection = rootpart.CFrame.RightVector*(down("D") and -2 or 2)
			
			local hit1 = workspace:Raycast(sideOrigin, sideDirection, params)
			local hit2 = nil
			if down("D") or down("A") then
				if down("D") then
					local hit2  = workspace:Raycast(leftArm.CFrame*CFrame.new(-3,0,0).Position,leftArm.CFrame.RightVector*-2,params)
				elseif down("A") then
					local hit2  = workspace:Raycast(rightArm.CFrame*CFrame.new(3,0,0).Position,rightArm.CFrame.RightVector*2,params)
				end
				print("Gottem")
			end
			
			if (hit1 or hit2) and (down("D") or down("A")) then -- right or left button is down, fires on switch edge
				local hit
				if hit2 ~= nil then
					hit = hit2
				else
					hit = hit1
				end
				print(tostring(hit))
				local attachment1 = Instance.new("Attachment",hit.Instance)
				attachment1.WorldPosition = hit.Position
				alignPos.Attachment1 = attachment1
				face=CFrame.new(hit.Position+hit.Normal, hit.Position)
				game.Debris:AddItem(attachment1,0.005)
			end
		until (not climbing)
		
		script.RemoteEvent:FireServer(char.ForceShiftLock, false)
		hum.AutoRotate=true
		hum.PlatformStand=false
	end
end
1 Like

Oh, and i forgot to mention, it turns every button press as of now

I think that even if you were to fix this specific issue here, you have what’s called a “specfic, complex solution to a simple problem.”

In a sense, I would think using RunService.Heartbeat or something of the sort to always update the state would be a better play, that way, you don’t get stuck in a single loop.

what do you mean by this? Wouldn’t it still be the same?

function onClimb()
	local origin = rootpart.Position
	local direction = rootpart.CFrame.LookVector

	local result = workspace:Raycast(origin, direction, params)
	if result then
		climbing=true
		script.RemoteEvent:FireServer(char.ForceShiftLock, true)
		
		rootpart.CFrame = CFrame.new(rootpart.CFrame.p, Vector3.new(rootpart.Position.X - result.Normal.X, rootpart.Position.Y, rootpart.Position.Z - result.Normal.Z))
		face = CFrame.new(result.Position+result.Normal, result.Position)

		hum.AutoRotate=false
		hum.PlatformStand=true

		climbMove.Parent=rootpart
		climbGyro.Parent=rootpart

		repeat
			run.RenderStepped:Wait()
			climbGyro.CFrame=face or CFrame.new()
			
			if rootpart.Position.Y > result.Instance.Size.Y then
				LedgeClimb()
				climbing=false
				offClimb()
				local ve = Instance.new("BodyVelocity")
				ve.MaxForce = Vector3.new(1, 1, 1) * 40000
				ve.Velocity = rootpart.CFrame.lookVector * 10 + Vector3.new(0, 7, 0)
				ve.Parent = rootpart
				game.Debris:AddItem(ve, 0.5)
				game.TweenService:Create(ve, TweenInfo.new(0.5, Enum.EasingStyle.Sine), {
				Velocity = rootpart.CFrame.lookVector * 10 + Vector3.new(0, 12, 0)
				}):Play()
			end
			
			local sideOrigin = rootpart.CFrame*CFrame.new(0, -0.4, -1).Position
			local sideDirection = rootpart.CFrame.RightVector*(down("D") and -2 or 2)
			
			local hit1 = workspace:Raycast(sideOrigin, sideDirection, params)
			local hit2 = nil
			local oldhit = nil
			local newhit = nil
			
			if (down("D") or down("A")) or (HoldingA == true or HoldingD == true) then
				if down("D") or HoldingD == true then
					HoldingD = true
					HoldingA = false
					local hit2  = workspace:Raycast(leftArm.CFrame*CFrame.new(-3,0,0).Position,leftArm.CFrame.RightVector*-2,params)
				elseif down("A") or HoldingA == true then
					HoldingA = true
					HoldingD = false
					local hit2  = workspace:Raycast(rightArm.CFrame*CFrame.new(3,0,0).Position,rightArm.CFrame.RightVector*2,params)
				end
			end
			if hit2 ~= nil then
				newhit = hit2
			else
				newhit = hit1
			end
			if oldhit ~= newhit and oldhit ~= nil then
				oldhit = newhit
				face=CFrame.new(newhit.Position+newhit.Normal, newhit.Position)
			end
			if (hit1 or hit2) and (down("D") or down("A")) then -- right or left button is down, fires on switch edge
				local hit = newhit
				print(tostring(hit))
				local attachment1 = Instance.new("Attachment",hit.Instance)
				attachment1.WorldPosition = hit.Position
				alignPos.Attachment1 = attachment1
				face=CFrame.new(hit.Position+hit.Normal, hit.Position)
				game.Debris:AddItem(attachment1,0.005)
				oldhit = hit
			end
		until (not climbing)
		
		script.RemoteEvent:FireServer(char.ForceShiftLock, false)
		hum.AutoRotate=true
		hum.PlatformStand=false
	end
end

updated code, still not working

Not quite. It’s no longer yielding when you don’t use a loop.

What should I change to fix it?

Ive tried altering the logic, still not working…

I think it mostly just needs a redo, which shouldn’t be too hard :stuck_out_tongue:

Basically, every frame, check if you’re on a wall.

  • If you’re on a wall, call a function named OnWall()
  • If you’re off a wall, call a function named OffWall()

In the OnWall function,

  • Enable the velocity that allows the player to be on the wall
  • DO NOT create a new velocity instance; have a pre-made one, and either parent it to the character OR enable it.

In the OffWall function,

  • Disable the velocity that allows the player to not be on the wall
  • If the player was previously on the wall, disable the velocity
  • If the player was not previously on the wall, do nothing

Hope this helps~!

i finally got it to work, but it’s a bit glitchy, how would i fix this? the code for the part to part is this:

			local hit1 = workspace:Raycast(sideOrigin, sideDirection, params)
			local hit2 = nil
			local debounceThing = false
			
			if down("D") or down("A") then
				if down("D") then
					local hit2  = workspace:Raycast(rootpart.CFrame.Position,rootpart.CFrame.RightVector*2,params)
					if hit2 and debounceThing == false then
						debounceThing = true
						print("D hit")
						face=CFrame.new(hit2.Position+hit2.Normal, hit2.Position)
						wait(0.2)
						debounceThing = false
					end
				elseif down("A") then
					local hit2  = workspace:Raycast(rootpart.CFrame.Position,rootpart.CFrame.RightVector*-2,params)
					if hit2 and debounceThing == false then
						debounceThing = true
						print("A hit")
						face=CFrame.new(hit2.Position+hit2.Normal, hit2.Position)
						wait(0.4)
						debounceThing = false
					end
				end
			end

I did add a debounce, but it didn’t really help

I’m not sure. I can’t tell what the problem is by reading it at the moment.

You’re making things more complicated than they should be by using AlightPosition etc.

In my case, I use BodyVelocity and BodyGyro, and when a valid wall is found using raycasting I update the variables to match that wall and then update the body movers.

Edit: With BodyGyro you can modify the dampening making rotation movement when switching walls faster or slower.

wait so how would i change the code to make it simpler and less buggy?

yeah the problem might be with raycasting, what im guessing is, it returns 2 raycasts, so it glitches, and tries to attach to both, but it can’t

1 Like

These are legacy body movers, I don’t recommend using those.

2 Likes

@prime_kavv @iGottic so you’re saying i should revamp the whole thing?

Unfortunately, yes. I don’t mean to give such a bland a generic solution, but it may be for the best.

1 Like

I’ll try this and see if it works

1 Like