Help with Player Move with the Wall Dynamically

Hello,

Currently, I have a system that uses raycasting and alignment constraints to allow the player to wall climb, but the player does not move dynamically with the wall.
Here’s a video demonstrating the issue:

I’ve tried a few different approaches but haven’t been able to get it to work correctly. Any advice or suggestions would be greatly appreciated!
Here’s the code:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local SoundService = game:GetService("SoundService")

local WallClimb = {}
WallClimb.__index = WallClimb

function WallClimb.new(character)
	local self = setmetatable({}, WallClimb)

	self.Character = character
	self.ClimbCooldownTime = 0.25
	self.RaycastLength = 4
	self.DistanceFromWall = 1.5
	self.ClimbSpeed = 15
	self.AnimationSpeed = 1
	self.MoveInterpolationFactor = 0.2
	self.LastClimbTimestamp = 0

	self.ClimbingAnimation = self.Character.Animator:LoadAnimation(ReplicatedStorage.Animations.Climbing)

	-- Position and Orientation Alignment
	self.PositionAlign = Instance.new("AlignPosition")
	self.PositionAlign.Mode = Enum.PositionAlignmentMode.OneAttachment
	self.PositionAlign.Parent = self.Character.HumanoidRootPart
	self.PositionAlign.Attachment0 = self.Character.HumanoidRootPart.RootAttachment
	self.PositionAlign.Enabled = false

	self.OrientationAlign = Instance.new("AlignOrientation")
	self.OrientationAlign.Mode = Enum.OrientationAlignmentMode.OneAttachment
	self.OrientationAlign.Parent = self.Character.HumanoidRootPart
	self.OrientationAlign.Attachment0 = self.Character.HumanoidRootPart.RootAttachment
	self.OrientationAlign.Enabled = false
	
	return self
end

function WallClimb:Update(deltaTime)
	local raycastResult = self:CastRay()

	if raycastResult and self:IsCooldownOver() and not self.Character.Humanoid.Jump then
		self:BeginClimbing(raycastResult, deltaTime)
	else
		self:EndClimbing()
	end
end

function WallClimb:CastRay()
	if not self.CurrentCFrame then
		self.CurrentCFrame = self.Character.HumanoidRootPart.CFrame
	end

	local origin = self.CurrentCFrame.Position
	local direction = self.CurrentCFrame.LookVector

	local rayParams = RaycastParams.new()
	rayParams.FilterType = Enum.RaycastFilterType.Include
	rayParams.FilterDescendantsInstances = {unpack(game:GetService("CollectionService"):GetTagged("Climbable"))}

	return workspace:Raycast(origin, direction * self.RaycastLength, rayParams)
end

function WallClimb:ClimbCFrame(raycastResult)
	local wallNormal = raycastResult.Normal * -1
	local rightVector = wallNormal:Cross(Vector3.yAxis).Unit
	local upVector = rightVector:Cross(wallNormal).Unit
	local holdPosition = raycastResult.Position + (raycastResult.Normal * self.DistanceFromWall)

	return CFrame.fromMatrix(holdPosition, rightVector, upVector)
end

function WallClimb:MovementVector(climbCFrame)
	local adjustedCFrame = climbCFrame * CFrame.Angles(math.pi / -2, 0, 0)
	local moveDirection = self.Character.Humanoid.MoveDirection
	local relativeMoveDirection = workspace.CurrentCamera.CFrame.Rotation:PointToObjectSpace(moveDirection)

	relativeMoveDirection *= Vector3.new(1, 0, -1)

	if relativeMoveDirection.Magnitude > 0 then
		relativeMoveDirection = relativeMoveDirection.Unit
	end

	return adjustedCFrame.Rotation:PointToWorldSpace(relativeMoveDirection)
end

function WallClimb:BeginClimbing(raycastResult, deltaTime)
	if not self.IsClimbing then
		self.IsClimbing = true
		self.ClimbingAnimation:Play()
		SoundService:PlayLocalSound(SoundService.Action.Stick)
	end

	self.PositionAlign.Enabled = true
	self.OrientationAlign.Enabled = true
	self.Character.Humanoid.AutoRotate = false

	local frameRate = 60
	local timeFactor = deltaTime * frameRate

	local climbCFrame = self:ClimbCFrame(raycastResult)
	local movementVector = self:MovementVector(climbCFrame)

	self.CurrentCFrame = climbCFrame + movementVector * self.ClimbSpeed * deltaTime
	self.Character.HumanoidRootPart.CFrame = self.Character.HumanoidRootPart.CFrame:Lerp(self.CurrentCFrame, self.MoveInterpolationFactor * timeFactor)

	self.PositionAlign.Position = self.Character.HumanoidRootPart.Position
	self.OrientationAlign.CFrame = self.Character.HumanoidRootPart.CFrame

	self.ClimbingAnimation:AdjustSpeed(movementVector.Magnitude * self.AnimationSpeed)
end

function WallClimb:EndClimbing()
	if self:IsCooldownOver() then
		self.LastClimbTimestamp = tick()
	end

	self.PositionAlign.Enabled = false
	self.OrientationAlign.Enabled = false
	self.Character.HumanoidRootPart.Anchored = false
	self.Character.Humanoid.AutoRotate = true
	self.CurrentCFrame = nil

	if self.IsClimbing then
		self.IsClimbing = false
		self.Character.Humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
		self.ClimbingAnimation:Stop()
		SoundService:PlayLocalSound(SoundService.Action.Detatch)
	end
end

function WallClimb:IsCooldownOver()
	local currentTime = tick()
	local timeSinceLastClimb = currentTime - self.LastClimbTimestamp

	return timeSinceLastClimb >= self.ClimbCooldownTime
end

return WallClimb
3 Likes

I think you want to use train platforming, here is a tutorial: Jailbreak train platform system? - #35 by Kord_K

It’s simply CFrame math that you can use

1 Like

I think the issue is in the BeginClimbing method when you set self.PositionAlign.Position to itself

self.PositionAlign.Position = self.Character.HumanoidRootPart.Position
1 Like

Does AssemblyLinearVelocity help?

1 Like

if this section makes u climb and stays then your align instances are being turned to false when enabled.

1 Like

Maybe use a weld :man_shrugging:

Haven’t tried, it’s just a suggestion, not guaranteed

Tho could work :ok_hand:

1 Like

Thank you! I managed to get it working after some time, here’s the result and modified code:

function WallClimb:BeginClimbing(raycastResult, deltaTime)
	local wall = raycastResult.Instance

	if not self.IsClimbing then
		self.IsClimbing = true
		self.ClimbingAnimation:Play()
		SoundService:PlayLocalSound(SoundService.Action.Stick)
	end

	self.PositionAlign.Enabled = true
	self.OrientationAlign.Enabled = true
	self.Character.Humanoid.AutoRotate = false
	
	if self.LastCFrame == nil then
		self.LastCFrame = wall.CFrame 
	end
	
	local WallCFrame = wall.CFrame
	local Rel = WallCFrame * self.LastCFrame:inverse()
	self.LastCFrame = wall.CFrame
	
	local frameRate = 60
	local timeFactor = deltaTime * frameRate

	local climbCFrame = self:ClimbCFrame(raycastResult)
	local movementVector = self:MovementVector(climbCFrame)
	local adjustedClimbCFrame = climbCFrame + movementVector * self.ClimbSpeed * deltaTime

	self.CurrentCFrame = Rel * adjustedClimbCFrame
	self.Character.HumanoidRootPart.CFrame = self.Character.HumanoidRootPart.CFrame:Lerp(self.CurrentCFrame, self.MoveInterpolationFactor * timeFactor)
	self.Character.HumanoidRootPart.CFrame = Rel * self.Character.HumanoidRootPart.CFrame

	self.PositionAlign.Position = self.Character.HumanoidRootPart.Position
	self.OrientationAlign.CFrame = self.Character.HumanoidRootPart.CFrame

	self.ClimbingAnimation:AdjustSpeed(movementVector.Magnitude * self.AnimationSpeed)
end
2 Likes

Pretty much entire quest is to detect if player touches platform, rest is adding relative CFrame soo player position will update

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