Disabling diagonal movemement in a 3D game

I’m making a first person PC game with purposefully clunky controls. I want the player to only be able to move in one direction at a time, effectively limiting them to cardinal directions (relative to their rotation). How could I achieve this in the simplest way possible?

The easiest way I know of to do this is to hijack the controls system under the PlayerModule. If you add this code to a LocalScript under StarterPlayerScripts, it should accomplish what you’re looking for:

local playerScripts = script.Parent
local playerModule = require(playerScripts:WaitForChild("PlayerModule"))
local controls = playerModule:GetControls()

--Override the default Roblox move function
controls.moveFunction = function(player : Player, direction : Vector3, relative : boolean)
	local absoluteDirection = direction:Abs()
	
	--Test to see what axis there is greater movement in
	if (absoluteDirection.X > absoluteDirection.Z) then
		--Prioritize X movement by zeroing out the Z component
		player.Move(player, direction * Vector3.xAxis, relative)
	else
		--Prioritize Z movement by zeroing out the X component
		player.Move(player, direction * Vector3.zAxis, relative)
	end
end
2 Likes

Tried your script. Movement wasn’t relative to player orientation at first but was easily fixed by replacing “relative” on lines 12 and 15 with “true”.

Now there’s a new issue. While movement is relative and restricted to cardinal directions, your orientation changes which key is considered ‘forward’. For instance, if I were to rotate 180 degrees, now I would have to press S to move forward instead of W, and D to move left instead of A, etc.

Oops! Stepped away for a second and realized that I had forgotten to make it relative. This should work:

local playerScripts = script.Parent
local playerModule = require(playerScripts:WaitForChild("PlayerModule"))
local controls = playerModule:GetControls()

local camera = workspace.CurrentCamera

--Override the default Roblox move function
controls.moveFunction = function(player : Player, direction : Vector3, relative : boolean)
	--Convert our direction from world space to camera space (thus making it relative)
	local trueDirection = camera.CFrame:VectorToObjectSpace(direction)
	local absoluteTrueDirection = trueDirection:Abs()
	
	--Test to see what axis there is greater movement in
	if (absoluteTrueDirection.X > absoluteTrueDirection.Z) then
		--Prioritize X movement by zeroing out the Z component
		player.Move(player, trueDirection * Vector3.xAxis, true)
	else
		--Prioritize Z movement by zeroing out the X component
		player.Move(player, trueDirection * Vector3.zAxis, true)
	end
end
1 Like

It’s near-perfect but I was still able to strafe diagonally at some super specific angles. Fixed it by rounding on line 14. Thanks for all of your help!

local playerScripts = script.Parent
local playerModule = require(playerScripts:WaitForChild("PlayerModule"))
local controls = playerModule:GetControls()

local camera = workspace.CurrentCamera

--Override the default Roblox move function
controls.moveFunction = function(player : Player, direction : Vector3, relative : boolean)
	--Convert our direction from world space to camera space (thus making it relative)
	local trueDirection = camera.CFrame:VectorToObjectSpace(direction)
	local absoluteTrueDirection = trueDirection:Abs()
	
	--Test to see what axis there is greater movement in
	if (math.round(absoluteTrueDirection.X) > math.round(absoluteTrueDirection.Z)) then
		--Prioritize X movement by zeroing out the Z component
		player.Move(player, trueDirection * Vector3.xAxis, true)
	else
		--Prioritize Z movement by zeroing out the X component
		player.Move(player, trueDirection * Vector3.zAxis, true)
	end
end
2 Likes

Update: turns out there’s another problem I didn’t notice.

When the camera is panned up or down, the player will move slower. Oddly enough this only happens on the Z axis. I fixed it by replacing trueDirection * Vector3.zAxis with Vector3.new(0,0,math.sign(trueDirection.Z). Functions the same exept the Z axis can now only be 1, -1, or 0. It’s pretty scuffed but hey it works

local playerScripts = script.Parent
local playerModule = require(playerScripts:WaitForChild("PlayerModule"))
local controls = playerModule:GetControls()
local camera = workspace.CurrentCamera

controls.moveFunction = function(player : Player, direction : Vector3, relative : boolean)
	local trueDirection = camera.CFrame:VectorToObjectSpace(direction)
	local absoluteTrueDirection = trueDirection:Abs()
	if (math.round(absoluteTrueDirection.X) > math.round(absoluteTrueDirection.Z)) then
		player.Move(player, trueDirection * Vector3.xAxis, true)
	else
		player.Move(player, Vector3.new(0,0,math.sign(trueDirection.Z)), true)
	end
end

There’s probably a way to fix that by creating a new CFrame that only accounts for the camera’s yaw and using that instead of the camera CFrame when declaring trueDirection. I would definitely advise to NOT use math.sign there since:

  1. It’ll interfere with movement sensitivity on controllers and mobile virtual joystick.
  2. Controls moveFunction is framerate dependent, so I believe players with framerates above 60 FPS will move significantly faster than others.

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