I’ve read a bit of the new camera and character control modules source code and came up with an implementation for center-locking the mouse while controlling the camera that also supports shift lock.
Shift lock has properties for developers to disable it in their games, but those properties are read-only in runtime, so you can’t use them to disable shift lock temporarily to stop it from affecting the camera. I’ve found a loophole, though: a StringValue named
BoundKeys is available to change which keys will activate shift lock if it is enabled. Setting the value to an empty string disables the toggle.
SetIsMouseLocked in the CameraController module behaves just like shift lock. The mouse gets center-locked, moving it moves the camera, and the character faces in the direction the camera faces.
SetMouseLockOffset sets the camera offset from the center; shift lock uses
1.75, 0, 0.
local PlayerModule = game.Players.LocalPlayer.PlayerScripts:WaitForChild("PlayerModule")
local cameras = require(PlayerModule):GetCameras()
local BoundKeys = PlayerModule.CameraModule.MouseLockController:WaitForChild("BoundKeys")
local OldBoundKeys = BoundKeys.Value
local CenterLocked = false
local function ToggleLock()
local CameraController = cameras.activeCameraController
local MouseLockController = cameras.activeMouseLockController
CenterLocked = not CenterLocked
if CenterLocked then
if MouseLockController:GetIsMouseLocked() then -- toggle shift lock off
BoundKeys.Value = "" -- disables shift lock toggle
BoundKeys.Value = OldBoundKeys -- restores shift lock toggle
-- example use, press T to toggle center lock
if gameProcessedEvent then return end
if input.KeyCode == Enum.KeyCode.T then
NOW THIS… is an absolute lifesaver, thank you so much for making this, helped me alot ;D
Bumping this for everyones sake
Hi, sorry for the bump, but this is important!
Just wanted to make a note here in this code:
MouseLockController:GetIsMouseLocked() will generally return false. You’re polluting a class structure, instead of using it as an actual service.
Thus, MouseLockController:OnMouseLockToggled() is used to fix the mouse icon, but in reality, this call should just be done by setting mouse.Icon = “” or mouse.Icon = the mouse shift lock icon
Otherwise, this solution is good!
The purpose of having the
if MouseLockController:GetIsMouseLocked() then block is to check whether the player is already using regular shift lock.
MouseLockController:OnMouseLockToggled() takes them out of shift lock first before turning on the custom mouse lock. The mouse controller internally handles states and mouse icons, so I just tell it to turn itself off instead of hacking around it. If you were to remove this block and just set the mouse icon to
"" then if you go into shift lock and press T to switch into this custom center lock state, when you press T again to turn off the custom center lock, if you try to press shift to enable regular mouse lock, you have to press it twice because it thinks it’s still on the first time and turns it off when it was already indirectly disabled. I wanted to support shift lock at the same time as this, and that’s what that’s for.
OnMouseLockToggled function in the MouseLockController module:
self.isMouseLocked = not self.isMouseLocked
if self.isMouseLocked then
local cursorImageValueObj = script:FindFirstChild("CursorImage")
if cursorImageValueObj and cursorImageValueObj:IsA("StringValue") and cursorImageValueObj.Value then
self.savedMouseCursor = Mouse.Icon
Mouse.Icon = cursorImageValueObj.Value
if cursorImageValueObj then
cursorImageValueObj = Instance.new("StringValue")
cursorImageValueObj.Name = "CursorImage"
cursorImageValueObj.Value = DEFAULT_MOUSE_LOCK_CURSOR
cursorImageValueObj.Parent = script
self.savedMouseCursor = Mouse.Icon
Mouse.Icon = DEFAULT_MOUSE_LOCK_CURSOR
if self.savedMouseCursor then
Mouse.Icon = self.savedMouseCursor
self.savedMouseCursor = nil
The first line internally switches the
isMouseLocked state. If this function weren’t called and you switched into the custom center lock while shift lock is on then it would think it’s still in shift lock mode, even when you switch out. The rest of the code handles restoring the original mouse icon.
isMouseLocked could have been written to instead of calling the function but I found it cleaner to just have the module do its thing instead of doing it for it.
Ah, I misread the original code. I thought the code was retrieving the module by requiring the module, but it’s using “cameras.activeMouseLockController”. Sorry about the confusion, it all makes sense!
This code is lifesaving! I just shortened my sub-par shoulder cam from 60 lines to ~10. Thanks!
Oh my what a life saver!!!
For some reason this is no longer working,
“attempt to index local ‘MouseLockController’(a nil value)” playing around with the Camera is very frustrating.
After messing around a bit I have successfully recreated this by setting the conditions of the toggle within the camera module.
Can you share how here?
As that will help the people who stumble across this now.
I’m just going to make a whole new post explaining it when I get around to it, but you could message me on discord Drac#5808 if you needed a quick explanation.
This saved me a lot of time. Thanks
It doesn’t work, maybe a fix? Or anyone?
Yeah, basically Roblox removed this API so you’re going to probably need to fork tne camera scripts to disable this.
Pinging @Fractality @Fractality_alt
Sorry about the bump but I ran into an error with this because Roblox had updated the Camera Module so this no longer worked luckily I was able to find a copy of the old Camera Module so by putting the old Camera Module in StarterPlayerScripts the above code works just fine.
PlayerModule.rbxm (109.5 KB)
If you’re unable to find any different way to get the Shift Lock experience then try this.
Where can I find the toggleable setting for your module?
Follow the original post example but replace the camera module with the one I provided and it should work
Is there any way to specifically make it turn off/on instead of just toggling it? Like a function that turns it on and one that turns it off? I tried doing it myself but the code just got messed up.