Context: I made a movement system last week that was utterly terrible, so I rewrote it and it actually turned out way better, but still just terrible
My question is what is the best way to make a movement/state system? My current one has two different types of checks, one for when the player is on the ground and another one for when the player is in the air, and then it does logic stuff and sets the player’s state accordingly (if they’re on the ground, state can only be running, sprinting or idle. if they’re in the air, it can only be jumping or climbing. dashing overrides both of these because the player can dash mid air and on the ground)
It sucks. I can slowly feel it getting more unorganized as I add more things to it (want to add a wall climb and crouching)
So here’s my question again, what’s the best way to go about making a movement system and state machine? Should you be doing state logic every frame or only when there’s input? (For the record I’m doing this all on the client right now because it’s already enough of a headache worrying about making it work I don’t wanna have to worry about server validation rn)
I’m going to be pasting my code at the bottom of this post, don’t read it if you don’t want to I’m just putting the code there for those who want to read it and see what I mean, I just want to be pointed in the right direction
local MovementController = {}
local StateControllerModule = require(script.Parent.StateController)
local AnimationControllerModule = require(script.Parent.AnimationController)
local UIS = game:GetService("UserInputService")
local Debris = game:GetService("Debris")
local RS = game:GetService("RunService")
MovementController.__index = MovementController
function MovementController.new(player : Player)
local self = setmetatable({}, MovementController)
self.Character = player.Character or player.CharacterAdded:Wait()
self.Humanoid = self.Character:WaitForChild("Humanoid")
self.Player = player
self.Animations = AnimationControllerModule.new(self.Humanoid.Animator)
self.HRP = self.Character:WaitForChild("HumanoidRootPart")
self.StateController = StateControllerModule.new(player)
self.shiftLocked = false
self.facingCamera = false
self.WalkSpeed = 16
self.SprintSpeed = 30
self.Camera = workspace.CurrentCamera
self.climbing = false
self.sprintToggled = false
self.isJumping = false
self.canJump = true
self.jumpDelay = 1
self.dashing = false
self.canDash = true
self.dashTime = 0.267
self.dashSpeed = 47
self.dashCD = 2
self.lastState = nil
self.canWallClimb = true
self.movementKeys = {
[Enum.KeyCode.LeftAlt] = function() self:ToggleSprint() end,
[Enum.KeyCode.LeftShift] = function() self:ToggleShiftLock() end,
[Enum.KeyCode.Space] = function () self:Jump() end,
[Enum.KeyCode.Q] = function() self:Dash() end
}
return self
end
function MovementController:Init()
UIS.InputBegan:Connect(function(input, gpe)
if gpe then return end
if self.movementKeys[input.KeyCode] then
self.movementKeys[input.KeyCode]()
end
end)
RS.RenderStepped:Connect(function()
self:Update()
local newState = self.StateController:GetState()
if newState ~= self.lastState then
self.lastState = newState
print(self.lastState)
end
end)
end
function MovementController:Update()
self:CheckAirStates()
self:CheckGroundStates()
self.Animations:UpdateAnimations(self.currentDirection, self.StateController:GetState())
if self.facingCamera then
self.HRP.CFrame = CFrame.lookAt(self.HRP.Position, self.HRP.Position + Vector3.new(self.Camera.CFrame.LookVector.X, 0, self.Camera.CFrame.LookVector.Z))
end
end
function MovementController:CheckGroundStates()
if self.Humanoid.FloorMaterial == Enum.Material.Air then return end
if self.dashing then return end
local moving = self.Humanoid.MoveDirection.Magnitude > 0.05
local moveDirection = self.Humanoid.moveDirection
local dot = self.HRP.CFrame.LookVector:Dot(moveDirection)
local sideDot = self.HRP.CFrame.RightVector:Dot(moveDirection)
self.canJump = true
self.facingCamera = false
self.climbing = false
if moving then
if self.sprintToggled then
self.StateController:SetState("Sprinting")
self.Humanoid.WalkSpeed = self.SprintSpeed
else
self.Humanoid.WalkSpeed = self.WalkSpeed
self.StateController:SetState("Walking")
if self.shiftLocked then
self.facingCamera = true
if dot > 0.5 then
self.lastDirection = self.currentDirection
self.currentDirection = "Forward"
elseif dot < -0.5 then
self.lastDirection = self.currentDirection
self.currentDirection = "Backward"
end
if sideDot > 0.5 then
self.lastDirection = self.currentDirection
self.currentDirection = "Right"
elseif sideDot < -0.5 then
self.lastDirection = self.currentDirection
self.currentDirection = "Left"
end
else--not shiftLocked
self.lastDirection = self.currentDirection
self.currentDirection = "Forward"
end
end
else
self.StateController:SetState("Idle")
end
end
function MovementController:CheckAirStates()
if self.Humanoid.FloorMaterial ~= Enum.Material.Air then return end
if self.dashing then return end
if self.climbing then
self.StateController:SetState("Climbing")
return
end
if self.Humanoid:GetState() == Enum.HumanoidStateType.Jumping then
self.StateController:SetState("Jumping")
elseif self.Humanoid:GetState() == Enum.HumanoidStateType.Freefall then
self.StateController:SetState("Jumping")
end
end
function MovementController:ToggleSprint()
self.sprintToggled = not self.sprintToggled
end
function MovementController:ToggleShiftLock()
self.shiftLocked = not self.shiftLocked
if self.shiftLocked then
workspace.CurrentCamera.CameraType = Enum.CameraType.Custom
local t = game:GetService("TweenService"):Create(self.Humanoid, TweenInfo.new(0.1, Enum.EasingStyle.Sine, Enum.EasingDirection.Out), {CameraOffset = Vector3.new(0, 3, 0)}):Play()
game:GetService("UserInputService").MouseBehavior = Enum.MouseBehavior.LockCenter
else
workspace.CurrentCamera.CameraType = Enum.CameraType.Custom
local t = game:GetService("TweenService"):Create(self.Humanoid, TweenInfo.new(0.1, Enum.EasingStyle.Sine, Enum.EasingDirection.Out), {CameraOffset = Vector3.new(0, 0, 0)}):Play()
game:GetService("UserInputService").MouseBehavior = Enum.MouseBehavior.Default
end
end
function MovementController:Jump()
if self.canJump then
--if self.Humanoid.FloorMaterial == Enum.Material.Air then
-- RS.RenderStepped:Connect(function()
-- local topRay = workspace:Raycast(self.Character.Head.Position + Vector3.new(0, 5, 0), self.Character.Head.CFrame.LookVector * 4 + Vector3.new(0, 5, 0))
-- local middleRay = workspace:Raycast(self.Character.Head.Position + Vector3.new(0, 3, 0), self.Character.Head.CFrame.LookVector * 4 + Vector3.new(0, 3, 0))
-- local bottomRay = workspace:Raycast(self.Character.Head.Position + Vector3.new(0, 1, 0), self.Character.Head.CFrame.LookVector * 4 + Vector3.new(0, 1, 0))
-- if middleRay and bottomRay and not topRay then
-- print("ledge")
-- end
-- end)
--end
if self.Humanoid.FloorMaterial == Enum.Material.Air then
local ray = workspace:Raycast(self.HRP.Position + Vector3.new(0, 5, 0), self.Character.Head.CFrame.LookVector * 2)
if ray then
--local p = Instance.new("Part")
--p.Anchored = true
--p.CanCollide = false
--p.Size = Vector3.new(0.2, 0.2, self.Character.Head.CFrame.LookVector * 2)
--p.CFrame = CFrame.lookAt(self.Character.Head.Position, ray.Position) * CFrame.new(0, 0, -self.Character.Head.CFrame.LookVector * 2/2)
--p.Material = Enum.Material.Neon
--p.Color = Color3.new(1, 0, 0)
--p.Parent = workspace
--self.HRP.AssemblyLinearVelocity = Vector3.new(self.HRP.AssemblyLinearVelocity.X, 67, self.HRP.AssemblyLinearVelocity.Z)
local Lv = Instance.new("LinearVelocity")
Lv.Parent = self.HRP
Lv.MaxForce = math.huge
Lv.Attachment0 = self.HRP:WaitForChild("RootAttachment")
Lv.VectorVelocity = Vector3.new(0, 52, 0)
local Ao = Instance.new("AlignOrientation")
Ao.Parent = self.HRP
Ao.Mode = Enum.OrientationAlignmentMode.OneAttachment
Ao.Attachment0 = self.HRP:WaitForChild("RootAttachment")
Ao.RigidityEnabled = true
Ao.CFrame = self.HRP.CFrame
self.sprintToggled = false
self.canJump = false
self.climbing = true
self.canDash = false
self.StateController:SetState("Climbing")
task.wait(0.4)
Lv:Destroy()
Ao:Destroy()
self.climbing = false
self.canDash = true
self.HRP.AssemblyLinearVelocity = Vector3.new(0,15,0)
end
else
self.Humanoid.Jump = true
self.canJump = false
self.StateController:SetState("Jumping")
end
end
end
function MovementController:Dash()
if self.canDash then
self.canDash = false
self.dashing = true
self.StateController:SetState("Dashing")
local Lv = Instance.new("LinearVelocity")
Lv.Parent = self.HRP
Lv.MaxForce = math.huge
Lv.Attachment0 = self.HRP:WaitForChild("RootAttachment")
Lv.VectorVelocity = (self.Humanoid.MoveDirection.Magnitude > 0 and self.Humanoid.MoveDirection or self.HRP.CFrame.LookVector) * self.dashSpeed
local Ao = Instance.new("AlignOrientation")
Ao.Parent = self.HRP
Ao.Mode = Enum.OrientationAlignmentMode.OneAttachment
Ao.Attachment0 = self.HRP:WaitForChild("RootAttachment")
Ao.RigidityEnabled = true
Ao.CFrame = self.HRP.CFrame
Debris:AddItem(Lv, self.dashTime)
Debris:AddItem(Ao, self.dashTime)
task.wait(self.dashTime)
self.dashing = false
task.wait(self.dashCD)
self.canDash = true
end
end
return MovementController
edit: I know i’ve been posting on the devforum a lot recently so someone has probably seen me before, apologies for that I, again, just wanna be pointed in the right direction. Still have a lot to learn