Super messy movement module that definitely can be cleaned up

Hi,

I made a movement module for my parkour game and the more I write code to expand on the module, the more messy it gets. Like really messy. All I really want to clean is the Wallrun part of the module but everything else is managable.

Here’s the entire module:

--!strict

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")
local CollectionService = game:GetService("CollectionService")
local SoundService = game:GetService("SoundService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

local PlayerSettings = require(ReplicatedStorage.PlayerSettings)
local Trove = require(ReplicatedStorage.Packages._Index["sleitnick_trove@1.1.0"]["trove"])
local Speedlines = require(ReplicatedStorage.Source.Classes.Speedlines)
local RaycastUtil = require(script.RaycastUtil)
local PlaySound = require(ReplicatedStorage.Source.Dependencies.Util.playSound)

local Assets = ReplicatedStorage.Assets
local Animations = Assets.Animations.Movement
local CurrentCamera = workspace.CurrentCamera
local Player = game.Players.LocalPlayer

local moveDirectionDB = false
local WaitTimer = PlayerSettings.BaseYield
local WallrunUpdate = nil :: RBXScriptConnection?

local Tags = {
	Wallrun = "Wallrun",
	Walljump = "Walljump",
};

local Blacklisted = {} :: any
local LoadedAnimations = {} :: any

local Movement = {}
Movement.__index = Movement

export type ClassType = typeof( setmetatable({} :: {
	isWallrunning: boolean,
	isEnabled: boolean,
	character: Model?,
	speedlines: Speedlines.ClassType?,

	--// wall run
	lastJump: number,
	lastWallDirection: string?, -- wall run and wall jump..
	jumpDebounceDelay: number,
	wallrunParameters: RaycastParams,
	previousWall: Instance?,

	Connections: Trove.ClassType,
}, Movement) )


function Movement.new(): ClassType
	local self = {
		isWallrunning = false,
		--isWalljumping = false,
		isEnabled = false,
		character = nil,
		speedlines = nil,

		lastJump = tick(),
		lastWallDirection = nil,
		jumpDebounceDelay = 0.05,
		wallrunParameters = RaycastParams.new(),
		previousWall = nil,

		Connections = Trove.new(),
	};

	setmetatable(self, Movement)

	self:_init()

	return self
end

function Movement._init(self: ClassType): ()
	self:_setupCharacterAdded()
end

function Movement._setupCharacterAdded(self: ClassType): ()
	Players.LocalPlayer.CharacterAppearanceLoaded:Connect(function(character: Model) 
		self.character = character

		if self.isEnabled and self.character then
			self:Stop()
			self:Start()

			for _, v in pairs(self.character:GetChildren()) do
				if not v:IsA("BasePart") then
					continue
				end

				table.insert(Blacklisted, v)
			end

			self.wallrunParameters.FilterType = Enum.RaycastFilterType.Exclude
			self.wallrunParameters.FilterDescendantsInstances = Blacklisted
		end
	end)

	if Players.LocalPlayer then
		if not self.character then
			self.character = Players.LocalPlayer.Character:: Model
		end
	end

	if self.character then	
		for _, v in pairs(self.character:GetChildren()) do
			if not v:IsA("BasePart") then
				continue
			end

			table.insert(Blacklisted, v)
		end

		self.wallrunParameters.FilterType = Enum.RaycastFilterType.Exclude
		self.wallrunParameters.FilterDescendantsInstances = Blacklisted
	end
end

function Movement.Start(self: ClassType): ()
	self.isEnabled = true

	if self.character then
		local Humanoid = self.character:FindFirstChild("Humanoid") :: Humanoid
		local RootPart = self.character:FindFirstChild("HumanoidRootPart") :: BasePart
		local Animator = Humanoid:FindFirstChild("Animator") :: Animator

		self.Connections:Connect(Humanoid:GetPropertyChangedSignal("MoveDirection"), function(...: any) 
			self:onMoveDirection(Humanoid, RootPart)
		end)

		if UserInputService.TouchEnabled then -- mobile jump detection
			local TouchControlFrame = Player
				:WaitForChild("PlayerGui")
				:FindFirstChild("TouchGui")
				:FindFirstChild("TouchControlFrame")

			local JumpButton = TouchControlFrame:FindFirstChild("JumpButton") :: ImageButton

			self.Connections:Connect(JumpButton.Activated, function(inputObject: InputObject, clickCount: number)
				self:onJumpRequest(Humanoid, RootPart)
			end)
		end

		self.Connections:Connect(UserInputService.InputBegan, function(input: InputObject, gameProcessedEvent: boolean)
			if gameProcessedEvent then return end

			if input.KeyCode == Enum.KeyCode.Space then
				self:onJumpRequest(Humanoid, RootPart)
			end
		end)

		self.Connections:Connect(Humanoid.StateChanged, function(old: Enum.HumanoidStateType, new: Enum.HumanoidStateType) 
			self:onStateChanged(old, Humanoid, RootPart)
		end)

		if not self.speedlines then
			self.speedlines = Speedlines.new(self.character, PlayerSettings.SpeedlinesMinimumSpeed, PlayerSettings.SpeedlinesMinimumRate)
		end

		if self.speedlines then
			self.speedlines:Start()
		end

		for _, animation in pairs(Animations:GetChildren()) do
			LoadedAnimations[animation.Name] = Animator:LoadAnimation(animation)
		end
	end
end

function Movement.Stop(self: ClassType): ()
	self.Connections:Clean()
	self.isEnabled = false

	if self.character then
		local Humanoid = self.character:FindFirstChild("Humanoid") :: Humanoid
		Humanoid.WalkSpeed = PlayerSettings.CharacterWalkSpeed
	end

	if self.speedlines then
		self.speedlines:Destroy()
		self.speedlines = nil
	end

	table.clear(LoadedAnimations)
end

function Movement.onMoveDirection(self: ClassType, Humanoid, Root): ()
	local isGrounded = if Humanoid.FloorMaterial == Enum.Material.Air then false else true
	local velocity = Root.CFrame:Inverse() * (Root.Position + Root.AssemblyLinearVelocity)
	local yDirection = math.atan2(velocity.X, -velocity.Z)
	local roundedDirection = math.ceil(math.deg(yDirection) - 0.5)

	local isStrafingOrBackwards = if roundedDirection >= 70 and roundedDirection <= 100 or 
		roundedDirection <= -70 and roundedDirection >= -100 or 
		roundedDirection <= -135 or roundedDirection >= 135 
		then true else false

	local isBackwards = if roundedDirection <= -135 or roundedDirection >= 135
		then true else false

	if Humanoid:GetState() ~= Enum.HumanoidStateType.Jumping 
		and Humanoid:GetState() ~= Enum.HumanoidStateType.Freefall
		and isGrounded then

		if Humanoid.MoveDirection ~= Vector3.new(0, 0, 0) and not isStrafingOrBackwards and moveDirectionDB == false and Humanoid.WalkSpeed < PlayerSettings.AccelerationMaxSpeed then
			moveDirectionDB = true

			while Humanoid.MoveDirection ~= Vector3.new(0, 0, 0) and Humanoid.WalkSpeed < PlayerSettings.AccelerationMaxSpeed do
				Humanoid.WalkSpeed = Humanoid.WalkSpeed + 1
				task.wait(WaitTimer)
				WaitTimer = WaitTimer / 1.1
			end

			moveDirectionDB = false
		elseif Humanoid.MoveDirection == Vector3.new(0, 0, 0) or isStrafingOrBackwards then
			WaitTimer = PlayerSettings.BaseYield
			Humanoid.WalkSpeed = PlayerSettings.CharacterWalkSpeed
		end
	end
end

function Movement.onJumpRequest(self: ClassType, Humanoid, Root): ()
	local currentTime = tick()
	local isGrounded = if Humanoid.FloorMaterial == Enum.Material.Air then false else true
	local velocity = Root.AssemblyLinearVelocity.Magnitude

	if currentTime - self.lastJump >= self.jumpDebounceDelay then
		self.lastJump = currentTime

		if self.isWallrunning and WallrunUpdate then
			WallrunUpdate:Disconnect()
			
			Humanoid:ChangeState(Enum.HumanoidStateType.Jumping) -- force jump
			Humanoid.WalkSpeed = PlayerSettings.AccelerationMaxSpeed -- keep their speed

			PlaySound("rbxassetid://5864343876", Root, SoundService.Character, 0.3)

			for _, v in pairs(LoadedAnimations) do
				if not v:IsA("AnimationTrack") then
					continue
				end

				if v.IsPlaying then
					v:Stop()
				end
			end

			self.isWallrunning = false
		end

		if not isGrounded and velocity >= 21 then
			self:Wallrun(Humanoid, Root)
		end

		if not isGrounded then
			self:Walljump(Humanoid, Root)
		end
	end

end

function Movement.Walljump(self: ClassType, Humanoid, Root)
	local isGrounded = if Humanoid.FloorMaterial == Enum.Material.Air or Humanoid:GetState() == Enum.HumanoidStateType.Freefall then false else true

	local _front = RaycastUtil.FrontCheck(Root, PlayerSettings.FrontRayLength, self.wallrunParameters) :: RaycastResult

	if _front and CollectionService:HasTag(_front.Instance, Tags.Walljump )then
		local Rotate = _front.Instance:GetAttribute("Rotate") or false

		RaycastUtil.DebugAttatchment(_front.Position)
		RaycastUtil.VisibleRay(Root, _front)

		PlaySound("rbxassetid://1724363484", _front.Instance, SoundService.Character, 0.3)

		Humanoid:ChangeState(Enum.HumanoidStateType.Jumping) -- force jump

		if Rotate then
			CurrentCamera.CFrame = CFrame.fromEulerAnglesXYZ(0, math.rad(180) , 0) * CurrentCamera.CFrame
		end
		
		Humanoid.WalkSpeed = PlayerSettings.AccelerationMaxSpeed -- keep their speed
	end
end

function Movement.Wallrun(self: ClassType, Humanoid, Root)
	local isGrounded = if Humanoid.FloorMaterial == Enum.Material.Air or Humanoid:GetState() == Enum.HumanoidStateType.Freefall then false else true
	local velocity = Root.AssemblyLinearVelocity.Magnitude

	if self.previousWall then
		local LeftWallCheck = RaycastUtil.LeftCheck(Root, PlayerSettings.WallrunRayLength, self.wallrunParameters) :: RaycastResult
		local RightWallCheck = RaycastUtil.RightCheck(Root, PlayerSettings.WallrunRayLength, self.wallrunParameters) :: RaycastResult

		local Result = LeftWallCheck or RightWallCheck

		if Result and self.previousWall == Result.Instance and not isGrounded then
			--print("Player tried to wallrun while freefalling on previous wall")
			return
		end
	end

	local _left = RaycastUtil.LeftCheck(Root, PlayerSettings.WallrunRayLength, self.wallrunParameters) :: RaycastResult
	local _right = RaycastUtil.RightCheck(Root, PlayerSettings.WallrunRayLength, self.wallrunParameters) :: RaycastResult
	local _result = _left or _right

	if _result and not CollectionService:HasTag(_result.Instance, Tags.Wallrun) then
		if WallrunUpdate then
			WallrunUpdate:Disconnect()
			self.isWallrunning = false
			
			Humanoid.WalkSpeed = PlayerSettings.AccelerationMaxSpeed

			for _, v in pairs(LoadedAnimations) do
				if not v:IsA("AnimationTrack") then
					continue
				end

				if v.IsPlaying then
					v:Stop()
				end
			end
		end

		return
	end

	task.spawn(function()
		WallrunUpdate = RunService.RenderStepped:Connect(function(deltaTime: number) 
			local vectorVelocity = Root.CFrame:Inverse() * (Root.Position + Root.AssemblyLinearVelocity)
			local yDirection = math.atan2(vectorVelocity.X, -vectorVelocity.Z)
			local roundedDirection = math.ceil(math.deg(yDirection) - 0.5)

			local LeftWallCheck = RaycastUtil.LeftCheck(Root, PlayerSettings.WallrunRayLength, self.wallrunParameters) :: RaycastResult
			local RightWallCheck = RaycastUtil.RightCheck(Root, PlayerSettings.WallrunRayLength, self.wallrunParameters) :: RaycastResult

			local Result = LeftWallCheck or RightWallCheck

			if Result == LeftWallCheck then
				self.lastWallDirection = "Left" -- shiftlock
				LoadedAnimations.WallrunLeft:Play()

				if roundedDirection > 37 and WallrunUpdate then
					WallrunUpdate:Disconnect()
					self.isWallrunning = false

					for _, v in pairs(LoadedAnimations) do
						if not v:IsA("AnimationTrack") then
							continue
						end

						if v.IsPlaying then
							v:Stop()
						end
					end
				end
			else
				self.lastWallDirection = "Right" -- shiftlock
				LoadedAnimations.WallrunRight:Play()

				if roundedDirection < -37 and WallrunUpdate then
					WallrunUpdate:Disconnect()
					self.isWallrunning = false

					for _, v in pairs(LoadedAnimations) do
						if not v:IsA("AnimationTrack") then
							continue
						end

						if v.IsPlaying then
							v:Stop()
						end
					end
				end
			end

			if Result and velocity >= 21 and CollectionService:HasTag(Result.Instance, Tags.Wallrun) then
				self.isWallrunning = true

				RaycastUtil.DebugAttatchment(Result.Position)
				RaycastUtil.VisibleRay(Root, Result)

				Root.AssemblyLinearVelocity = Vector3.new(Root.AssemblyLinearVelocity.X,-1,Root.AssemblyLinearVelocity.Z)

				self.previousWall = Result.Instance
			elseif not Result 
				or velocity < 21
				or Humanoid:GetState() ~= Enum.HumanoidStateType.Freefall then

				self.isWallrunning = false

				if WallrunUpdate then
					for _, v in pairs(LoadedAnimations) do
						if not v:IsA("AnimationTrack") then
							continue
						end

						if v.IsPlaying then
							v:Stop()
						end
					end

					WallrunUpdate:Disconnect()
					self.isWallrunning = false
					
					--Humanoid.WalkSpeed = PlayerSettings.AccelerationMaxSpeed
				end
			end

		end)
	end)
end

function Movement.onStateChanged(self: ClassType, old, Humanoid, Root): ()
	if old == Enum.HumanoidStateType.Landed then

		if Humanoid.MoveDirection == Vector3.new(0,0,0) then -- tanding still after jump
			Humanoid.WalkSpeed = PlayerSettings.CharacterWalkSpeed
		end

		self.previousWall = nil
		if WallrunUpdate then
			WallrunUpdate:Disconnect()

			for _, v in pairs(LoadedAnimations) do
				if not v:IsA("AnimationTrack") then
					continue
				end

				if v.IsPlaying then
					v:Stop()
				end
			end

			self.isWallrunning = false
		end
	end
end

function Movement.Destroy(self: ClassType): ()
	self.Connections:Destroy()

	if self.speedlines then -- destroy 
		self.speedlines:Destroy()
	end
end

return Movement

Thank you for reading

1 Like

Hello,

I have done some changes to your module (albeit rushed). I haven’t touched alot on the Wallrun just did some little changes there and there that i think could improve your code readability.

If I have the time i’ll get down and try to tackle the Wallrun part since it is quite messy and confusing.

--!strict

local Movement = {}
Movement.__index = Movement

--Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")
local CollectionService = game:GetService("CollectionService")
local SoundService = game:GetService("SoundService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

--Constants
local Player = Players.LocalPlayer
local Assets = ReplicatedStorage.Assets
local Packages = ReplicatedStorage.Packages
local Source = ReplicatedStorage.Source
local Animations = Assets.Animations.Movement
local CurrentCamera = workspace.CurrentCamera

--Imports
local PlayerSettings = require(ReplicatedStorage.PlayerSettings)
local RaycastUtil = require(script.RaycastUtil)
local Trove = require(Packages._Index["sleitnick_trove@1.1.0"]["trove"])
local Speedlines = require(Source.Classes.Speedlines)
local PlaySound = require(Source.Dependencies.Util.playSound)

--Types
export type ClassType = typeof(setmetatable({} :: {
	isWallrunning: boolean,
	isEnabled: boolean,
	character: Model?,
	speedlines: Speedlines.ClassType?,

	--// wall run
	lastJump: number,
	lastWallDirection: string?, -- wall run and wall jump..
	jumpDebounceDelay: number,
	wallrunParameters: RaycastParams,
	previousWall: Instance?,

	_Trove: Trove.ClassType,
}, Movement))

--Variables
local moveDirectionDB = false
local WaitTimer = PlayerSettings.BaseYield
local WallrunUpdate = nil :: RBXScriptConnection?
local Vec3Zero = Vector3.zero
local atan2, ceil, deg, rad = math.atan2, math.ceil, math.deg, math.rad

local Tags = { Wallrun = "Wallrun", Walljump = "Walljump" }

local Blacklisted = {} :: any
local LoadedAnimations = {} :: any

--Functions
local function SetupCharacter(character: Model)
    for _, v in pairs(character:GetChildren()) do
        if not v:IsA("BasePart") then
            continue
        end

        table.insert(Blacklisted, v)
    end
end

local function StopAllAnimTracks()
    for _, v in pairs(LoadedAnimations) do
        if not v:IsA("AnimationTrack") then
            continue
        end

        if v.IsPlaying then
            v:Stop()
        end
    end
end

--Class
function Movement.new(): ClassType
	return setmetatable({
		isWallrunning = false,
		--isWalljumping = false,
		isEnabled = false,
		character = nil,
		speedlines = nil,

		lastJump = tick(),
		lastWallDirection = nil,
		jumpDebounceDelay = 0.05,
		wallrunParameters = RaycastParams.new(),
		previousWall = nil,

		_Trove = Trove.new(),
	}, Movement)
end

function Movement._init(self: ClassType): ()
	self:_setupCharacterAdded()
end

function Movement._setupCharacterAdded(self: ClassType): ()
    if Player.Character then
        self.character = Player.Character :: Model

        SetupCharacter(Player.Character)

		self.wallrunParameters.FilterType = Enum.RaycastFilterType.Exclude
		self.wallrunParameters.FilterDescendantsInstances = Blacklisted
    end

	Player.CharacterAppearanceLoaded:Connect(function(character: Model) 
		self.character = character
        
		if self.isEnabled and self.character then
			self:Stop()
			self:Start()

            SetupCharacter(character)
		end
	end)
end

function Movement.Start(self: ClassType): ()
	self.isEnabled = true

	local Humanoid = self.character.Humanoid :: Humanoid
	local RootPart = self.character.HumanoidRootPart :: BasePart
	local Animator = Humanoid.Animator :: Animator

	self._Trove:Connect(Humanoid:GetPropertyChangedSignal("MoveDirection"), function(...: any) 
		self:onMoveDirection(Humanoid, RootPart)
	end)
    
	if UserInputService.TouchEnabled then -- mobile jump detection
		local TouchControlFrame = Player
			:WaitForChild("PlayerGui")
			:FindFirstChild("TouchGui")
			:FindFirstChild("TouchControlFrame")

		local JumpButton = TouchControlFrame:FindFirstChild("JumpButton") :: ImageButton

		self._Trove:Connect(JumpButton.Activated, function(inputObject: InputObject, clickCount: number)
			self:onJumpRequest(Humanoid, RootPart)
		end)
	end

	self._Trove:Connect(UserInputService.InputBegan, function(input: InputObject, gameProcessedEvent: boolean)
		if gameProcessedEvent then
            return
        end

		if input.KeyCode == Enum.KeyCode.Space then
			self:onJumpRequest(Humanoid, RootPart)
		end
	end)

	self._Trove:Connect(Humanoid.StateChanged, function(old: Enum.HumanoidStateType, new: Enum.HumanoidStateType) 
		self:onStateChanged(old, Humanoid, RootPart)
	end)

	if not self.speedlines then
		self.speedlines = Speedlines.new(self.character, PlayerSettings.SpeedlinesMinimumSpeed, PlayerSettings.SpeedlinesMinimumRate)
        self.speedlines:Start()
    else
        self.speedlines:Start()
    end

	for _, animation in ipairs(Animations:GetChildren()) do
		LoadedAnimations[animation.Name] = Animator:LoadAnimation(animation)
	end
end

function Movement.Stop(self: ClassType): ()
	self.Connections:Clean()
	self.isEnabled = false

	if self.character then
		local Humanoid = self.character.Humanoid :: Humanoid
		Humanoid.WalkSpeed = PlayerSettings.CharacterWalkSpeed
	end

	if self.speedlines then
		self.speedlines:Destroy()
		self.speedlines = nil
	end

	table.clear(LoadedAnimations)
end

function Movement.onMoveDirection(self: ClassType, Humanoid, Root): ()
    local isGrounded = Humanoid.FloorMaterial == Enum.Material.Air and false or true
	local velocity = Root.CFrame:Inverse() * (Root.Position + Root.AssemblyLinearVelocity)
	local yDirection = atan2(velocity.X, -velocity.Z)
	local roundedDirection = ceil(deg(yDirection) - 0.5)

	local isStrafingOrBackwards = if roundedDirection >= 70 and roundedDirection <= 100 or 
		roundedDirection <= -70 and roundedDirection >= -100 or 
		roundedDirection <= -135 or roundedDirection >= 135 
		then true else false
    
	local isBackwards = if roundedDirection <= -135 or roundedDirection >= 135
		then true else false

	if Humanoid:GetState() ~= Enum.HumanoidStateType.Jumping 
		and Humanoid:GetState() ~= Enum.HumanoidStateType.Freefall
		and isGrounded then

		if Humanoid.MoveDirection ~= Vec3Zero and
            not isStrafingOrBackwards and
            not moveDirectionDB and 
            Humanoid.WalkSpeed < PlayerSettings.AccelerationMaxSpeed then
			
            moveDirectionDB = true

			while Humanoid.MoveDirection ~= Vec3Zero and Humanoid.WalkSpeed < PlayerSettings.AccelerationMaxSpeed do
				Humanoid.WalkSpeed = Humanoid.WalkSpeed + 1
				task.wait(WaitTimer)
				WaitTimer = WaitTimer / 1.1
			end

			moveDirectionDB = false
		elseif Humanoid.MoveDirection == Vec3Zero or isStrafingOrBackwards then
			WaitTimer = PlayerSettings.BaseYield
			Humanoid.WalkSpeed = PlayerSettings.CharacterWalkSpeed
		end
	end
end

function Movement.onJumpRequest(self: ClassType, Humanoid, Root): ()
	local currentTime = tick()
	local isGrounded = Humanoid.FloorMaterial == Enum.Material.Air and false or true
	local velocity = Root.AssemblyLinearVelocity.Magnitude

	if currentTime - self.lastJump >= self.jumpDebounceDelay then
		self.lastJump = currentTime

		if self.isWallrunning and WallrunUpdate then
			WallrunUpdate:Disconnect()
			
			Humanoid:ChangeState(Enum.HumanoidStateType.Jumping) -- force jump
			Humanoid.WalkSpeed = PlayerSettings.AccelerationMaxSpeed -- keep their speed

			PlaySound("rbxassetid://5864343876", Root, SoundService.Character, 0.3)
            
            StopAllAnimTracks()

			self.isWallrunning = false
		end

        if not isGrounded then
            if velocity >= 21 then
                self:Wallrun(Humanoid, Root)
            else
                self:Walljump(Humanoid, Root)
            end
        end
	end
end

function Movement.Walljump(self: ClassType, Humanoid, Root)
	local _front = RaycastUtil.FrontCheck(Root, PlayerSettings.FrontRayLength, self.wallrunParameters) :: RaycastResult

	if _front and CollectionService:HasTag(_front.Instance, Tags.Walljump )then
		local Rotate = _front.Instance:GetAttribute("Rotate") or false

		RaycastUtil.DebugAttatchment(_front.Position)
		RaycastUtil.VisibleRay(Root, _front)

		PlaySound("rbxassetid://1724363484", _front.Instance, SoundService.Character, 0.3)

		Humanoid:ChangeState(Enum.HumanoidStateType.Jumping) -- force jump

		if Rotate then
			CurrentCamera.CFrame = CFrame.fromEulerAnglesXYZ(0, rad(180) , 0) * CurrentCamera.CFrame
		end
		
		Humanoid.WalkSpeed = PlayerSettings.AccelerationMaxSpeed -- keep their speed
	end
end

function Movement.Wallrun(self: ClassType, Humanoid, Root)
	local isGrounded = Humanoid.FloorMaterial == Enum.Material.Air and false or true
	local velocity = Root.AssemblyLinearVelocity.Magnitude

	if self.previousWall then
		local LeftWallCheck = RaycastUtil.LeftCheck(Root, PlayerSettings.WallrunRayLength, self.wallrunParameters) :: RaycastResult
		local RightWallCheck = RaycastUtil.RightCheck(Root, PlayerSettings.WallrunRayLength, self.wallrunParameters) :: RaycastResult

		local Result = LeftWallCheck or RightWallCheck

		if Result and self.previousWall == Result.Instance and not isGrounded then
			--print("Player tried to wallrun while freefalling on previous wall")
			return
		end
	end

	local _left = RaycastUtil.LeftCheck(Root, PlayerSettings.WallrunRayLength, self.wallrunParameters) :: RaycastResult
	local _right = RaycastUtil.RightCheck(Root, PlayerSettings.WallrunRayLength, self.wallrunParameters) :: RaycastResult
	local _result = _left or _right

	if _result and not CollectionService:HasTag(_result.Instance, Tags.Wallrun) then
		if WallrunUpdate then
			WallrunUpdate:Disconnect()
			self.isWallrunning = false
			
			Humanoid.WalkSpeed = PlayerSettings.AccelerationMaxSpeed
            
            StopAllAnimTracks()
		end

		return
	end

	task.spawn(function()
		WallrunUpdate = RunService.RenderStepped:Connect(function(deltaTime: number) 
			local vectorVelocity = Root.CFrame:Inverse() * (Root.Position + Root.AssemblyLinearVelocity)
			local yDirection = atan2(vectorVelocity.X, -vectorVelocity.Z)
			local roundedDirection = ceil(deg(yDirection) - 0.5)

			local LeftWallCheck = RaycastUtil.LeftCheck(Root, PlayerSettings.WallrunRayLength, self.wallrunParameters) :: RaycastResult
			local RightWallCheck = RaycastUtil.RightCheck(Root, PlayerSettings.WallrunRayLength, self.wallrunParameters) :: RaycastResult

			local Result = LeftWallCheck or RightWallCheck

			if Result == LeftWallCheck then
				self.lastWallDirection = "Left" -- shiftlock
				LoadedAnimations.WallrunLeft:Play()

				if roundedDirection > 37 and WallrunUpdate then
					WallrunUpdate:Disconnect()
					self.isWallrunning = false

                    StopAllAnimTracks()
				end
			else
				self.lastWallDirection = "Right" -- shiftlock
				LoadedAnimations.WallrunRight:Play()

				if roundedDirection < -37 and WallrunUpdate then
					WallrunUpdate:Disconnect()
					self.isWallrunning = false

                    StopAllAnimTracks()
				end
			end

			if Result and velocity >= 21 and CollectionService:HasTag(Result.Instance, Tags.Wallrun) then
				self.isWallrunning = true

				RaycastUtil.DebugAttatchment(Result.Position)
				RaycastUtil.VisibleRay(Root, Result)

				Root.AssemblyLinearVelocity = Vector3.new(Root.AssemblyLinearVelocity.X,-1,Root.AssemblyLinearVelocity.Z)

				self.previousWall = Result.Instance
			elseif not Result 
				or velocity < 21
				or Humanoid:GetState() ~= Enum.HumanoidStateType.Freefall then

				self.isWallrunning = false

				if WallrunUpdate then
                    StopAllAnimTracks()

					WallrunUpdate:Disconnect()
					self.isWallrunning = false
					
					--Humanoid.WalkSpeed = PlayerSettings.AccelerationMaxSpeed
				end
			end

		end)
	end)
end

function Movement.onStateChanged(self: ClassType, old, Humanoid, Root): ()
	if old == Enum.HumanoidStateType.Landed then
		if Humanoid.MoveDirection == Vec3Zero then -- tanding still after jump
			Humanoid.WalkSpeed = PlayerSettings.CharacterWalkSpeed
		end

		self.previousWall = nil
		if WallrunUpdate then
			WallrunUpdate:Disconnect()

            StopAllAnimTracks()

			self.isWallrunning = false
		end
	end
end

function Movement.Destroy(self: ClassType): ()
	self.Connections:Destroy()

	if self.speedlines then -- destroy 
		self.speedlines:Destroy()
        self.speedlines = nil
	end
end

return Movement
2 Likes

Why are you using functions . instead of methods :

2 Likes

The ones with a self parameter are actually methods. That’s how it works when you use !strict mode.

--// this is a method (self: ClassType): ()
function Movement._setupCharacterAdded(self: ClassType): ()

end

--// this is not a method (): ()
function Movement._setupCharacterRemoved(): ()

end

I use strict mode because it makes it easier to catch errors.

1 Like