How to Force Enable Shift-Lock (Without changing the PlayerModule)

Introduction

Hello developers :slight_smile:!
I have seen many games accomplish some kind of “Force shift locking”, and most threads I’ve seen change the PlayerModule.

This method accomplishes about the same result without changing the PlayerModule by CFraming.

I believe this method is simpler, and if the PlayerModule changes again, this will still work.

End Result:

This Method Utilizies:

UserInputService.MouseBehavior
Humanoid.CameraOffset
RunService.RenderStepped / RunService.PreRender


Tutorial:

Step 0 (Before Scripting)

Firstly, since we use MouseBehavior (A UserInputService “client” property), we use a LocalScript and not a script.
I prefer to put it in StarterCharacterScripts Since it’s easier to reference the character, but you can do this in every place where local scripts can run, just reference the Character accordingly.
image

Step 1 (The Mouse):

Let’s start writing the script, the following script will center the mouse.
We RunService.RenderStepped to center it on every frame so it won’t revert back to normal.

Local script that centers mouse:
--Local script in StarterPlayerCharacter
local plr = game:GetService("Players").LocalPlayer

function shiftLock(active) --Toggle shift lock function
	if active then --Whether to enable or disable
		---------------------------------------------------------
		game:GetService("RunService"):BindToRenderStep("ShiftLock", Enum.RenderPriority.Character.Value, function()
			game:GetService("UserInputService").MouseBehavior = Enum.MouseBehavior.LockCenter --Set the mouse to center every frame.
		end) 
		---------------------------------------------------------
	end
end

Step 2 (The Camera):

After we centered the mouse, we now want the camera to be a bit above and right from the character, like the Shift-Lock effect.
For this, we use Humanoid.CameraOffset. It will move the camera farther from the Humanoid (X,Y,Z) Studs away.

Let’s assume the shift-lock moves the camera 1.75 Studs to the X.
The local script now looks like this:

Local script that centers the mouse and moves the camera:
--Local script in StarterPlayerCharacter
local plr = game:GetService("Players").LocalPlayer
local char = script.Parent
local hum = char:WaitForChild("Humanoid")

function shiftLock(active) --Toggle shift lock function
	if active then --Whether to enable or disable
		---------------------------------------------------------
		hum.CameraOffset = Vector3.new(1.75,0,0) -- Set the Camera's center distance from character
		---------------------------------------------------------

		game:GetService("RunService"):BindToRenderStep("ShiftLock", Enum.RenderPriority.Character.Value, function()
			game:GetService("UserInputService").MouseBehavior = Enum.MouseBehavior.LockCenter --Set the mouse to center every frame.
		end) 
	end
end

Step 3 (The Character):

Now lastly, and most importantly, *the character isn’t facing to the mouse/camera’s direction.
As far as I’ve seen, this is why most people don’t use MouseBehavior for force-Enabling shift lock.

However, the solution for that is quite simple.
If we were to cancel the humanoid’s automatic rotation (Humanoid.AutoRotate), we can just CFrame the Character towards the Camera’s Rotation.

This will force the character to face the Camera’s rotation.

Here's how we'll do that:

Firstly, let’s define the HumanoidRootPart, since we will be altering its’ CFrame.

local root = hum.RootPart -- The HumanoidRootPart

Secondly, we’ll disable the humanoid’s automatic rotation so we can set it ourselves.

hum.AutoRotate = false --Disable the automatic rotation since we are the ones setting it.

Now let’s get the Camera’s angles. But since we want to rotate it only on one axis (the Y axis), we’ll use _ as a placeholder for the X axis.

local _, y = workspace.CurrentCamera.CFrame.Rotation:ToEulerAnglesYXZ() --Get the angles of the camera

Lastly, we got the angles, now all left is to rotate the HumanoidRootPart.

root.CFrame = CFrame.new(root.Position) * CFrame.Angles(0,y,0) --Set the root part's CFrame to the current position, but with the camera's rotation.

Local script:

Local script that centers the mouse, moves the camera and rotates the player to camera:
--Local script in StarterPlayerCharacter
--Variables:
local plr = game:GetService("Players").LocalPlayer
local char = script.Parent
local hum = char:WaitForChild("Humanoid")
---------------------------------------------------------
local root = hum.RootPart -- The HumanoidRootPart
---------------------------------------------------------

--Toggle Function:
function shiftLock(active) --Toggle shift lock function
	if active then
		---------------------------------------------------------
		hum.AutoRotate = false --Disable the automatic rotation since we are the ones setting it.
		---------------------------------------------------------

		hum.CameraOffset = Vector3.new(1.75,0,0)
		game:GetService("RunService"):BindToRenderStep("ShiftLock", Enum.RenderPriority.Character.Value, function()
			game:GetService("UserInputService").MouseBehavior = Enum.MouseBehavior.LockCenter --Set the mouse to center.
			---------------------------------------------------------
			local _, y = workspace.CurrentCamera.CFrame.Rotation:ToEulerAnglesYXZ() --Get the angles of the camera
			root.CFrame = CFrame.new(root.Position) * CFrame.Angles(0,y,0) --Set the root part to the camera's rotation
			---------------------------------------------------------
		end) 
	end
end
--Disable and Enable:
shiftLock(true) -- Toggle shift lock
print("Shift lock turned on!")

Step 4:(Disabling the Shift-Lock)

Now when you press play, you should be in the “Shift lock mode” that we created.

But now, how shall we disable it?

Very simple, just revert all the changes we’ve done, and stop altering the CFrame :slight_smile:

Note: I’ve created variables for the services due to us using them more than once

All of our progress so far, with an option to disable:
--Local script in StarterPlayerCharacter
--Services:
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
--Variables:
local plr = game:GetService("Players").LocalPlayer
local char = script.Parent
local hum = char:WaitForChild("Humanoid")
local root = hum.RootPart -- The HumanoidRootPart


--Toggle Function:
function shiftLock(active) --Toggle shift.lock function
	if active then		
		hum.CameraOffset = Vector3.new(1.75,0,0) -- I assume this is about the right camera offset.
		hum.AutoRotate = false --Disable the automatic rotation since we are the ones setting it.

		RunService:BindToRenderStep("ShiftLock", Enum.RenderPriority.Character.Value, function()
			UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter --Set the mouse to center every frame.

			local _, y = workspace.CurrentCamera.CFrame.Rotation:ToEulerAnglesYXZ() --Get the angles of the camera
			root.CFrame = CFrame.new(root.Position) * CFrame.Angles(0,y,0) --Set the root part to the camera's rotation
		end) 
	else
		hum.AutoRotate = true --Let the humanoid handle the camera rotations again.
		hum.CameraOffset = Vector3.new(0,0,0) --Move the camera back to normal.
		RunService:UnbindFromRenderStep("ShiftLock") -- Allow mouse to move freely.
		UserInputService.MouseBehavior = Enum.MouseBehavior.Default -- Let the mouse move freely
	end
end
--Disable and Enable:
shiftLock(true) -- Toggle shift lock
print("Shift lock turned on!")

task.wait(7)
shiftLock(false) --Toggle off shift Lock
print("Shift lock turned off!")

To disable it, we can now use shiftLock(false)

Congratulations! We're finished🥳!

You should now have a functioning script that forces the shift-lock.

Final Script:

Open script...
--Local script in StarterPlayerCharacter
--Services:
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
--Variables:
local plr = game:GetService("Players").LocalPlayer
local char = script.Parent
local hum = char:WaitForChild("Humanoid")
local root = hum.RootPart -- The HumanoidRootPart


--Toggle Function:
function shiftLock(active) --Toggle shift.lock function
	if active then		
		hum.CameraOffset = Vector3.new(1.75,0,0) -- I assume this is about the right camera offset.
		hum.AutoRotate = false --Disable the automatic rotation since we are the ones setting it.

		RunService:BindToRenderStep("ShiftLock", Enum.RenderPriority.Character.Value, function()
			UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter --Set the mouse to center every frame.

			local _, y = workspace.CurrentCamera.CFrame.Rotation:ToEulerAnglesYXZ() --Get the angles of the camera
			root.CFrame = CFrame.new(root.Position) * CFrame.Angles(0,y,0) --Set the root part to the camera's rotation
		end) 
	else

		hum.CameraOffset = Vector3.new(0,0,0) --Move the camera back to normal.
		RunService:UnbindFromRenderStep("ShiftLock") -- Allow mouse to move freely.
		UserInputService.MouseBehavior = Enum.MouseBehavior.Default -- Let the mouse move freely
		hum.AutoRotate = true --Let the humanoid handle the camera rotations again.
	end
end
--Disable and Enable:
shiftLock(true) -- Toggle shift lock
--[[
shiftLock(false) --Toggle off shift Lock
]]

Uses of Shift-Lock forcing:

  1. Mobile Phone Shift-Lock for Obbies
  2. Third Person guns.
  3. Tools in general
  4. Default in-game character view.

Final Notes:

This tutorial has been fully re-written (April 2022) due to the deprecation of BodyGyro.

Some of the replies/questions may have related to the previous very of the tutorial, so not all of them might be relevant to the current version.

Additional Credits:

@CrystalFlxme - For alerting me about the Humanoid.AutoRotate property to solve some rotation issues that occurred.
@SaturdayScandal - For alerting me of the deprecation of BodyGyro and the need to update this tutorial.
@xdeno - For alerting me about the behavior change of UserInputService.MouseBehavior and providing the accurate Shift Lock’s offset camera


I hope this tutorial helped you or taught you something new :slight_smile:
Please do alert me if you notice any issues regarding the tutorial and its’ content :wink:

Have a wonderful rest of your day/night :large_blue_circle:!

168 Likes

That wouldn’t replicate yeah?
If so then whats the best way to replicate it.

3 Likes

It does replicate, the client has control (Network ownership) over their character, therefore the BodyGyro rotates it just fine.

9 Likes

Why all this when you can easily make the HumanoidRootPart look towards the camera’s CFrame.LookVector? (It’s not to offend, but I just wanna know the reason you prefer using a BodyGyro and looking at the mouse’s pointer instead of where the camera is looking).

1 Like

The BodyGyro is what rotates the HumanoidRootPart, so we won’t have to do the CFraming. It also has properties so the turning can be customized (like how quickly, and etc), and is easy to disable from other scripts.

As I already “messed” with the mouse to put it in the center, it just came naturally to me to rotate to the mouse (since it’s already in the center).

It is most definitely possible with the Camera too.
Just for the sake of possibility, here you go :stuck_out_tongue::

rotation.CFrame = workspace.CurrentCamera.CFrame

Same result as:

rotation.CFrame = mouse.Origin

Don’t worry, none taken :ok_hand:

5 Likes

Often, when the mouse is in the center, and you rotate, the Mouse and Camera’s look Vector are often equal:

print(mouse.Origin.LookVector == workspace.CurrentCamera.CFrame.LookVector)

image

1 Like

I see, makes more sense. I prefer using CFrame instead of using BodyGyros, but it’s just a preference of mine. Great guide still, especially for new developers who wanna have an over-the-shoulders camera. :wink:

4 Likes

why does it show a weird rotation when walking:
https://gyazo.com/990429d3bcc20364b48d88f1e4eaf4db

Are you walking backwards? The rotation seems to be normal (I can’t determine as I don’t see the face).

Shift Lock is a state that makes the character face to the direction the Camera is facing, so the character is rotating on camera move.

What do you find weird in this rotation?

yes, look how the torso rotates…

1 Like

it rotates a weird angle rotation that is not supposed to rotate, try walking back wards its supposed to look straight, but thats not what happing here

1 Like

That’s strange, not 100% sure why. The character seemed to be rotated correctly when standing, but rotate strangely backward.

However, I do not think that is a replication issue. Probably just the force coming from the walk.
I’ll try to resolve it.

Edit (April 2022):
This issue no longer occurs with the new version of the tutorial.

1 Like

i never said it’s replication problem btw, tho i can’t wrap my head around how to solve it i tried change the angle based on the move direction but it would ruin forward walking.


I have a few ideas in mind to fix the issue. I will update the guide and let you know when I am able to try them.

July 7th update: Still no solution. However, the turning seems to just be visual, not actually changing the direction of the walking

1 Like

no this was old i was asking before i impelement it it was the first time me seeing this. this has nothing to do with my second questions like my first question doesn’t relate to my second one.

yeah sure good luck fixing it.

I like this but it would be better if you put it in a module

local LockModule = {}

function LockModule.ShiftLock(Active, Plr, Character)
	local mouse = Plr:GetMouse()
	local hum = Character:WaitForChild("Humanoid")
	local rotation = Instance.new("BodyGyro")
	rotation.P = 1000000
	rotation.Parent = hum.RootPart
	local conn
	
	if Active == true then
		hum.CameraOffset = Vector3.new(1,0.5,0) 
		rotation.MaxTorque = Vector3.new(0, math.huge, 0) 
		conn = game:GetService("RunService").RenderStepped:Connect(function()
			rotation.CFrame = mouse.Origin
			game:GetService("UserInputService").MouseBehavior = Enum.MouseBehavior.LockCenter
			
		end)
	elseif Active == false then
		hum.CameraOffset = Vector3.new(0,0,0) 
		rotation.MaxTorque = Vector3.new(0, 0, 0) 
		warn("Camera has been Unlocked or has been unlocked")

		if conn then conn:Disconnect() end 
		
	
	end
end

return LockModule
3 Likes

I’ve been criticized a few times for using ModuleScripts rather than functions in tutorials.

The module mentioned wouldn’t work as :GetMouse() and UserInputService only work in local scripts, making the player parameter meaningless.

You sould fire a remote event to the player instead.


Thanks for the suggestion :blush:

1 Like

But the module does work

The script would be called from the client

3 Likes

Ah, I see :slightly_smiling_face:.
Just letting you know there’s no need for the Plr parameter because it only works on the local player.

Does it also deactivate? (The connection variable you made is local, I do not think the connection disconnects properly).

(I do not mean to offend, just wondering whether it works properly with the module you sent and suggesting some)

2 Likes