How do I snap the Character to the cardinal directions no matter the platform (PC, console, mobile)?

Hi, I’m making a 2.5D type of game, and I need the character’s Humanoid.MoveDirection to exclusively face one of these directions. This means, the directions can be something like (1,0,0) or something like (-0.707,0,0.707).
image

I can’t seem to get Analog-type inputs (mobile thumbstick and console) to work properly. They are just changing the MoveDirection to whatever random analog direction the player is facing. PLEASE HELP

What solutions have you tried?

I got it working for keyboard with this script, but mobile and console don’t work:

local function snapPlayerToAxis(plr)
	local Character
	
	while not plr.Character do
		task.wait()
		Character = plr.Character
		print(`SnapToAxis :: Waiting for {plr.Name}'s Character`)
	end

	local hum = Character:WaitForChild("Humanoid")
	local rootPart = Character.HumanoidRootPart

	hum:GetPropertyChangedSignal("MoveDirection"):Connect(function()--fires when the humanoid's move direction changes
		local rootPos = rootPart.CFrame.p--get root position
		local moveDir = hum.MoveDirection--get character's move direction
		moveDir = Vector3.new(moveDir.X, 0, moveDir.Z)--remove the y component
		
		if moveDir.Magnitude > 0 then--make sure we're not standing still
			rootPart.CFrame = CFrame.new(rootPos, rootPos + moveDir)--rotate the player
		end
	end)
end

for _,plr in game.Players:GetPlayers() do
	snapPlayerToAxis(plr)
end

game.Players.PlayerAdded:Connect(function(plr)
	snapPlayerToAxis(plr)
end)
2 Likes

You’ll have to find another way. On the documentation, it says humanoid MoveDirection is read only.
image

You’re likely going to have to make your own custom system here. I’d recommend disabling controls and working from there.

PC (Just throwing Ideas out there)

Say you get an input from W and A, then you can figure out which sector you’re working from, say 7. Then you can change the orientation of the HumanoidRootPart. Getting the humanoid walking is interesting, and you can probably just unbind A and D, and then monitor all of the movement keys for input. Other than that, not sure how you would do it.

Console

For console, you probably are gonna need to do some math. Assuming you get an (X,Y) value from the thumbstick with a range of -1,1, you would use that information to assign what sector you need. See this post on how to figure that out. First start off with the simplest thing, finding which four of the sectors you need to use.

Imagine a circle is split into four sectors (as can be seen in this lovely diagram I made myself):
pie-chart-diagram-set-three-and-four-sections-or-steps-circle-icons-for-infographic-presentation-web-design-user-interface-simple-business-chart-vector

From your magnitude information, you then know which of the sectors its in.

Positive X and Positive Y: Sector 1
Negative X and Positive Y: Sector 2
Negative X and Negative Y: Sector 3
Positive X and Negative Y: Sector 4.

Now, we need to determine which direction we want to face. Say we’re looking at both positive X and Y values, for example a value of (0.7, 0.7).

This means that the magnitude or (delta) of X and Y are the same. This means that we should point them on a diagonal direction (or direction 1), as per your diagram. (Still assuming positive values) If the X is bigger than the Y (significantly), then you move it to the right, whereas in the opposite circumstance, you move it directly forward (or 0).

Hope this explains this. Do let me know if you have any questions.

My solution for this is to fork the default PlayerModule script and modify one of the functions to return a rounded output.

In case you don’t know how to fork PlayerModule, here’s a quick step-by-step:

  1. Press “Play” or F5 to start a play test
  2. Go into StarterPlayer → StarterPlayerScripts in the explorer
  3. Copy “PlayerScriptsLoader” and “PlayerModule”
  4. Leave the play test
  5. Paste both scripts into StarterPlayerScripts

This lets you override the default PlayerModule scripts and make your own changes.

The function you want to look for is GetMoveVector() inside of the BaseCharacterController module. Since it returns the move vector that the main movement scripts use, you can intercept it and make whatever changes you want.

This function uses atan2 to get the movevector’s angle, rounds that angle to make the snap effect, then converts it back into a movevector. You should be able to replace the original GetMoveVector function with this new one and it’ll work perfectly.

local SNAP_ANGLE = math.rad(45) -- Set snap angle to whatever you want here
function BaseCharacterController:GetMoveVector(): Vector3
    local oldMoveVector = self.moveVector 
    
    local radius = oldMoveVector.Magnitude
    local theta = math.atan2(oldMoveVector.Z, oldMoveVector.X) -- Get the MoveVector's angle
    
    theta = math.round(theta / SNAP_ANGLE) * SNAP_ANGLE -- Round it to specified snap angle
    
    return Vector3.new(radius * math.cos(theta), 0, radius * math.sin(theta)) -- Return new MoveVector
end

From what I’ve heard, atan2 is a somewhat expensive function so if there’s a better solution out there let me know.

Now all movement should be snapped to 45 degrees! Note that this doesn’t take camera direction into account, so if you want to limit that as well you’d need to do some extra work (like overriding IsMoveVectorCameraRelative to return false).

Video examples of the solution:

Mobile:

Controller:

60 degree version:

1 Like