Custom movement problem

Hi there.

So I’m trying to make an advanced movement system for a RPG top down game im working on. The movement script is okay. What’s not is how the player are moving. I don’t know what makes the player move that way. Like evrytime you try to walk diagonally or you spam the WASD keys, it just glitches the entire movement system. Like the player get stuck when you try to walk left but not when the player want to walk right. Im stressing out huhu. Sorry for the bad english btw. Im typing this post pretty fast so yeah.

Play the game so you can see what I meant. Or maybe download the file and read the scripts? idk
Here’s the game link:

This is the scrips that I suspected are the reason why the glitch happens:

ControlModule (ReplicatedStorage.Modules
--//SERVICES
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

--//SYSTEM FOLDERS
local RemoteEvents = ReplicatedStorage.RemoteEvents
local RemoteFunctions = ReplicatedStorage.RemoteFunctions
local Bindables = ReplicatedStorage.Bindables

--//VARAIBLES
local module = {}
local axises = { -- direction = {x, z}
	Forward = {0,-1},
	Left = {-1,0},
	Backward = {0,1},
	Right = {1,0},}
local rotations = { -- direction = angle
	Forward = 0,
	Left = 90,
	Backward = 180,
	Right = -90,
	D1 = -45,
	D2 = 45,
	D3 = 135,
	D4 = -135,}

-- DIAGONAL THINGY
-- local x, z = (math.sin(45) * math.pi/4) * -1, (math.sin(45) * math.pi/4 ) * 1

--//FUNCTIONS
function getAxis(direction : string)
	-- check if requesting main axis
	if axises[direction] then
		--print("A")
		return axises[direction]
	else
		-- check if there's a rotation value of the direction in the rotations table
		if rotations[direction] then
			--print("B")
			local direction1, direction2
			local axis1, axis2
			local axis
			if direction == "D1" then
				direction1 = "Forward"
				direction2 = "Right"
			elseif direction == "D2" then
				direction1 = "Forward"
				direction2 = "Left"
			elseif direction == "D3" then
				direction1 = "Backward"
				direction2 = "Left"
			elseif direction == "D4" then
				direction1 = "Backward"
				direction2 = "Right"
			end
			-- combine bypass axis
			axis1 = axises[direction1]
			axis2 = axises[direction2]
			local x1, z1 = axis1[1], axis1[2]
			local x2, z2 = axis2[1], axis2[2]
			local alpha, beta = x1 + x2, z1 + z2
			--print(alpha, beta)
			--print(alpha, beta)
			-- get bypass coordinate
			local x = (math.sin(45) * math.pi/4) * alpha
			local z = (math.sin(45) * math.pi/4) * beta
			return {x, z}
		end
	end
end

local hb = 0
function module.MovePlayer(player : Player, move : boolean, direction : string)
	
	-- TEST CODE
	
	if move == true then
		-- move
		-- check if there's running connection
		local MovementConnection = Bindables.GetInfo:Invoke(player, "MovementConnection")
		local Facing = Bindables.GetInfo:Invoke(player, "Facing")
		--print(MovementConnection, Facing)
		-- set infos
		Bindables.SetInfo:Fire(player, "Facing", direction)
		Bindables.SetInfo:Fire(player, "State", "Moving")
		-- if player don't have renderer function running, create one
		--print("connecting")
		Bindables.SetInfo:Fire(player, "MovementConnection", RunService.Heartbeat:Connect(function(deltaT)
			hb += 1
			-- if player left
			if player.Character == nil then
				return
			end
			-- if standing
			if Bindables.GetInfo:Invoke(player, "State") == "Moving" then
				-- move
				local speed = Bindables.GetInfo:Invoke(player, "Speed")
				-- set position
				local axis = getAxis(direction)
				local alpha = speed * deltaT
				local x, z = player.Character.Root.Position.X + (alpha * axis[1]), player.Character.Root.Position.Z + (alpha * axis[2])
				--print(direction, axis)
				local position = CFrame.new(x,player.Character.Root.Position.Y,z)
				local rotation = CFrame.Angles(0,math.rad(rotations[direction]),0)
				player.Character.Root.CFrame = position
				
				-- check wth is the problem :(
				if hb >= 20 then
					hb = 0
					local P2 = (player.Character.Root.Position + Vector3.new((alpha * axis[1]), 0, player.Character.Root.Position.Z + (alpha * axis[2])))
					print((player.Character.Root.Position - P2).Unit)
				end
			end
		end))
	else
		-- stop movement
		Bindables.SetInfo:Fire(player, "State", "Standing")
		--print("stop")
		-- disconnect movement connection
		local MovementConnection = Bindables.GetInfo:Invoke(player, "MovementConnection")
		if MovementConnection then
			--print("disconnecting")
			MovementConnection:Disconnect()
			Bindables.SetInfo:Fire(player, "MovementConnection", nil)
		end
	end
end

--//CONNECTIONS
RemoteEvents.MovePlayer.OnServerEvent:Connect(module.MovePlayer)

return module

ControlClient (StarterPlayerScripts
--//SERVICES
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")

--//SYSTEM FOLDERS
local RemoteEvents = ReplicatedStorage:WaitForChild("RemoteEvents")
local RemoteFunctions = ReplicatedStorage:WaitForChild("RemoteFunctions")

--//OBJECTS
local localPlayer = Players.LocalPlayer
local mouse = localPlayer:GetMouse()

--//VARIABLES
local keys = {"w","a","s","d",}
local pressedKeys = {w = false, a = false, s = false, d = false,}
local lastKey, curKey
local controlConnection1
local controlConnection2
local mainDirections = {
	["W"] = "Forward",
	["A"] = "Left",
	["S"] = "Backward",
	["D"] = "Right",}

--//FUNCTIONS
local function getUserDevice()
	local device
	-- if keyboard enabled
	if UserInputService.KeyboardEnabled and not UserInputService.TouchEnabled then
		device = "COMPUTER"
		-- if touch enabled
	elseif UserInputService.TouchEnabled and not UserInputService.KeyboardEnabled then
		device = "MOBILE"
		-- default
	else
		device = "COMPUTER"
	end
	return device
end

-- get pressed keys
function getPressedKeys()
	local pack = {}
	for key, pressed in pairs(pressedKeys) do
		if pressed == true then
			-- set weight
			if key == curKey then
				pack[1] = curKey
			elseif key == lastKey then
				pack[2] = lastKey
			else
				local index = #pack + 1
				pack[index] = key
			end
		end
	end
	--print(pack)
	return pack
end

function getDirection(key : any)
	-- if single key
	if typeof(key) == "string" and mainDirections[key] then
		return mainDirections[key]
	end
	-- if diagonal request
	if typeof(key) == "table" then  -- D stands for 'diagonal'
		if table.find(key, "W") and table.find(key, "D") then -- top right corner
			return "D1"
		elseif table.find(key, "W") and table.find(key, "A") then -- top left corner
			return "D2"
		elseif table.find(key, "A") and table.find(key, "S") then -- bottom left corner
			return "D3"
		elseif table.find(key, "S") and table.find(key, "D") then -- bottom right corner
			return "D4"
			-- impossible combination
		elseif (table.find(key, "A") and table.find(key, "D")) or
			(table.find(key, "W") and table.find(key, "S"))
		then
			return mainDirections[key[1]]
		end
	end
end

local function toggleCamera(mode : string)
	-- access module
	local CameraModule = require(script.CameraModule)
	
	-- activate camera base on the mode
	if mode == "Game" then -- GAME CAMERA MODE
		CameraModule.ActivateGameCamera(localPlayer)
	end
end

-- Computer only controller
local function toggleControl(toggle : boolean, device : string)
	if device == "COMPUTER" then
		if toggle then
			-- key pressed
			controlConnection1 = mouse.KeyDown:Connect(function(key)
				if table.find(keys, key) then
					--
					lastKey = curKey
					curKey = key
					pressedKeys[key] = true
					-- movement conditions
					local results = getPressedKeys()
					local direction = getDirection(string.upper(key))
					if #results > 1 then
						-- diagonal movement
						--print("Diagonal!!!")
						local keySet = {} -- only send two keys
						keySet[1] = string.upper(results[1])
						keySet[2] = string.upper(results[2])
						direction = getDirection(keySet)
					else
						-- straight line movement
						--print("STRAIGHTHTHT!!!")
					end
					RemoteEvents:WaitForChild("MovePlayer"):FireServer(true, direction)
				end
			end)
			-- key released
			controlConnection2 = mouse.KeyUp:Connect(function(key)
				if table.find(keys, key) then
					--
					pressedKeys[key] = false
					-- check if want to continue movement
					local results = getPressedKeys()
					if #results == 0 then
						-- stop completely
						--print("STOP")
						curKey = nil
						RemoteEvents:WaitForChild("MovePlayer"):FireServer(false)
					elseif #results == 1 then
						-- continue moving
						--print("CONTINUE")
						local pressedKey = results[1]
						local direction = getDirection(string.upper(pressedKey))
						curKey = pressedKey
						RemoteEvents:WaitForChild("MovePlayer"):FireServer(true, direction)
					end
				end
			end)
		else
			-- if there's existing connecton, disconnect
			if controlConnection1 then
				controlConnection1:Disconnect()
				controlConnection1 = nil
			end
			if controlConnection2 then
				controlConnection2:Disconnect()
				controlConnection2 = nil
			end
		end
	end
end

--//CONNECTIONS
RemoteEvents:WaitForChild("ToggleCamera").OnClientEvent:Connect(toggleCamera)
RemoteEvents:WaitForChild("ToggleControl").OnClientEvent:Connect(toggleControl)
RemoteFunctions:WaitForChild("GetUserDevice").OnClientInvoke = getUserDevice
2 Likes

Hello, sorry for replying kind of late.

Anyways, why don’t you try making this code simpler? I looked at it and lots of stuff can easily be replaced with simpler code, though I don’t know your intentions so maybe not.

Also also, I don’t recommend doing player movement on the server, sure it’s more secure but it can really suck with lag.

1 Like

I agree, the best method depends on your goal. If you want smooth movement with minimal lag, the best approach is a client-authoritative system with server validation. Right now, with your scripts, the server fully controls movement. This means there’s a considerably larger delay between pressing a key and seeing movement, especially with high ping.

1 Like

The character you see in the current testing place version is the server side character. I’m planning on using the server for positioning and rotating. The client side is to render the character model and animate them. Plus, there’ll be customizable character so I want to handle the character appearance on the client

(does that make sense? I hope so)

2 Likes

You can keep it how it is it’s just gonna be delayed with high ping users, also roblox automatically updates the position and rotation if the NetworkOwnership is set to the player.

Anyways I recommend making the code less messy (No offense btw I do it all the time) and it might fix in the process

1 Like

Aight. I’ll see what I can simplify

1 Like

Alright, if you need help still don’t hesitate to ask!