-
-
Notifications
You must be signed in to change notification settings - Fork 223
Fix camera movement during window resizing on mobile and desktop #1909
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
b42b103
27926d5
11871dd
edc118a
030ed48
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -50,6 +50,10 @@ return function(Vargs, GetEnv) | |||||||||||||||||||||||
| end | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| local function RunLast() | ||||||||||||||||||||||||
| -- Initialize camera stabilization to prevent unwanted movement during window resizing | ||||||||||||||||||||||||
| if Functions.InitCameraStabilization then | ||||||||||||||||||||||||
| Functions.InitCameraStabilization() | ||||||||||||||||||||||||
| end | ||||||||||||||||||||||||
| Functions.RunLast = nil; | ||||||||||||||||||||||||
| end | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
@@ -264,6 +268,155 @@ return function(Vargs, GetEnv) | |||||||||||||||||||||||
| end | ||||||||||||||||||||||||
| end; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| -- Camera stabilization to prevent unwanted camera movement during window resizing | ||||||||||||||||||||||||
| InitCameraStabilization = function() | ||||||||||||||||||||||||
| local cam = workspace.CurrentCamera | ||||||||||||||||||||||||
| local lastCameraState = { | ||||||||||||||||||||||||
| CFrame = cam.CFrame, | ||||||||||||||||||||||||
| CameraType = cam.CameraType, | ||||||||||||||||||||||||
| CameraSubject = cam.CameraSubject, | ||||||||||||||||||||||||
| FieldOfView = cam.FieldOfView, | ||||||||||||||||||||||||
| ViewportSize = cam.ViewportSize, | ||||||||||||||||||||||||
| LastUpdate = os.clock() | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| local isResizing = false | ||||||||||||||||||||||||
| local stabilizationEnabled = true | ||||||||||||||||||||||||
| local resizeDebounce = false | ||||||||||||||||||||||||
| local lastUserCameraChange = 0 | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| -- Function to check if camera change was likely user-initiated | ||||||||||||||||||||||||
| local function isUserCameraChange() | ||||||||||||||||||||||||
| local now = os.clock() | ||||||||||||||||||||||||
| -- If camera changed recently (within 2 seconds), it might be user input | ||||||||||||||||||||||||
| return (now - lastUserCameraChange) < 2 | ||||||||||||||||||||||||
| end | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| -- Function to save current camera state (only if not user-initiated) | ||||||||||||||||||||||||
| local function saveCameraState() | ||||||||||||||||||||||||
| if cam and cam.Parent and not isResizing and stabilizationEnabled then | ||||||||||||||||||||||||
| local now = os.clock() | ||||||||||||||||||||||||
| -- Only save if camera type is Custom (normal gameplay) and enough time has passed | ||||||||||||||||||||||||
| if cam.CameraType == Enum.CameraType.Custom and not isUserCameraChange() then | ||||||||||||||||||||||||
| lastCameraState.CFrame = cam.CFrame | ||||||||||||||||||||||||
| lastCameraState.CameraType = cam.CameraType | ||||||||||||||||||||||||
| lastCameraState.CameraSubject = cam.CameraSubject | ||||||||||||||||||||||||
| lastCameraState.FieldOfView = cam.FieldOfView | ||||||||||||||||||||||||
| lastCameraState.LastUpdate = now | ||||||||||||||||||||||||
| end | ||||||||||||||||||||||||
| end | ||||||||||||||||||||||||
| end | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| -- Function to restore camera state (with additional safety checks) | ||||||||||||||||||||||||
| local function restoreCameraState() | ||||||||||||||||||||||||
| if cam and cam.Parent and stabilizationEnabled and isResizing then | ||||||||||||||||||||||||
| local now = os.clock() | ||||||||||||||||||||||||
| -- Only restore if the saved state is recent (within 5 seconds) and camera is in Custom mode | ||||||||||||||||||||||||
| if lastCameraState.LastUpdate and (now - lastCameraState.LastUpdate) < 5 | ||||||||||||||||||||||||
| and cam.CameraType == Enum.CameraType.Custom then | ||||||||||||||||||||||||
| cam.CFrame = lastCameraState.CFrame | ||||||||||||||||||||||||
| if lastCameraState.CameraType then | ||||||||||||||||||||||||
| cam.CameraType = lastCameraState.CameraType | ||||||||||||||||||||||||
| end | ||||||||||||||||||||||||
| if lastCameraState.CameraSubject then | ||||||||||||||||||||||||
| cam.CameraSubject = lastCameraState.CameraSubject | ||||||||||||||||||||||||
| end | ||||||||||||||||||||||||
| if lastCameraState.FieldOfView then | ||||||||||||||||||||||||
| cam.FieldOfView = lastCameraState.FieldOfView | ||||||||||||||||||||||||
| end | ||||||||||||||||||||||||
| end | ||||||||||||||||||||||||
| end | ||||||||||||||||||||||||
| end | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| -- Track user input that might affect camera | ||||||||||||||||||||||||
| service.UserInputService.InputBegan:Connect(function(input, gameProcessed) | ||||||||||||||||||||||||
| if not gameProcessed then | ||||||||||||||||||||||||
| -- Track mouse and touch input that could move camera | ||||||||||||||||||||||||
| if input.UserInputType == Enum.UserInputType.MouseMovement or | ||||||||||||||||||||||||
| input.UserInputType == Enum.UserInputType.Touch or | ||||||||||||||||||||||||
| input.UserInputType == Enum.UserInputType.MouseButton2 then | ||||||||||||||||||||||||
| lastUserCameraChange = os.clock() | ||||||||||||||||||||||||
| end | ||||||||||||||||||||||||
| end | ||||||||||||||||||||||||
| end) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| -- Monitor viewport size changes (window resizing) | ||||||||||||||||||||||||
| cam:GetPropertyChangedSignal("ViewportSize"):Connect(function() | ||||||||||||||||||||||||
| if stabilizationEnabled and not resizeDebounce then | ||||||||||||||||||||||||
| local newViewportSize = cam.ViewportSize | ||||||||||||||||||||||||
| local oldViewportSize = lastCameraState.ViewportSize | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| -- Check if this is actually a resize (size changed significantly) | ||||||||||||||||||||||||
| if oldViewportSize and | ||||||||||||||||||||||||
| (math.abs(newViewportSize.X - oldViewportSize.X) > 10 or | ||||||||||||||||||||||||
| math.abs(newViewportSize.Y - oldViewportSize.Y) > 10) then | ||||||||||||||||||||||||
| resizeDebounce = true | ||||||||||||||||||||||||
| isResizing = true | ||||||||||||||||||||||||
| lastCameraState.ViewportSize = newViewportSize | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| -- Use task.defer to avoid blocking the event handler | ||||||||||||||||||||||||
| task.defer(function() | ||||||||||||||||||||||||
| -- Wait for resize to settle | ||||||||||||||||||||||||
| task.wait(0.15) | ||||||||||||||||||||||||
| -- Only restore if no recent user camera input | ||||||||||||||||||||||||
| if not isUserCameraChange() then | ||||||||||||||||||||||||
| restoreCameraState() | ||||||||||||||||||||||||
| end | ||||||||||||||||||||||||
| task.wait(0.15) | ||||||||||||||||||||||||
| isResizing = false | ||||||||||||||||||||||||
| resizeDebounce = false | ||||||||||||||||||||||||
| end) | ||||||||||||||||||||||||
| else | ||||||||||||||||||||||||
| lastCameraState.ViewportSize = newViewportSize | ||||||||||||||||||||||||
| end | ||||||||||||||||||||||||
| end | ||||||||||||||||||||||||
| end) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| -- Periodically save camera state when stable (less frequent) | ||||||||||||||||||||||||
| service.StartLoop("CameraStabilization", 1.0, function() | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
| saveCameraState() | ||||||||||||||||||||||||
| end) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| -- Return functions to control stabilization | ||||||||||||||||||||||||
| Variables.CameraStabilization = { | ||||||||||||||||||||||||
| Enable = function() | ||||||||||||||||||||||||
| stabilizationEnabled = true | ||||||||||||||||||||||||
| end, | ||||||||||||||||||||||||
| Disable = function() | ||||||||||||||||||||||||
| stabilizationEnabled = false | ||||||||||||||||||||||||
| end, | ||||||||||||||||||||||||
| IsEnabled = function() | ||||||||||||||||||||||||
| return stabilizationEnabled | ||||||||||||||||||||||||
| end, | ||||||||||||||||||||||||
| Cleanup = function() | ||||||||||||||||||||||||
| service.StopLoop("CameraStabilization") | ||||||||||||||||||||||||
| stabilizationEnabled = false | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
| stabilizationEnabled = false | |
| stabilizationEnabled = false | |
| -- Disconnect event connections to avoid memory leaks | |
| if Variables.InputBeganConnection then | |
| Variables.InputBeganConnection:Disconnect() | |
| Variables.InputBeganConnection = nil | |
| end | |
| if Variables.ViewportSizeConnection then | |
| Variables.ViewportSizeConnection:Disconnect() | |
| Variables.ViewportSizeConnection = nil | |
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1238,5 +1238,42 @@ return function(Vargs, env) | |
| return args | ||
| end | ||
| }; | ||
|
|
||
| CameraStabilization = { | ||
| Prefix = Settings.PlayerPrefix; | ||
| Commands = {"camerastab", "camerastabilization", "fixcamera", "stabilizecamera"}; | ||
| Args = {"on/off/status"}; | ||
|
||
| Description = "Toggle camera stabilization to prevent unwanted movement during window resizing"; | ||
| AdminLevel = "Players"; | ||
| Function = function(plr: Player, args: {string}) | ||
| local action = string.lower(args[1] or "status") | ||
|
|
||
| if action == "on" or action == "enable" or action == "true" then | ||
| Remote.Send(plr, "Function", "CameraStabilization", "Enable") | ||
| Remote.MakeGui(plr, "Output", { | ||
| Color = Color3.new(0, 1, 0); | ||
| Message = "Camera stabilization enabled"; | ||
| Title = "Camera Stabilization"; | ||
| }) | ||
| elseif action == "off" or action == "disable" or action == "false" then | ||
| Remote.Send(plr, "Function", "CameraStabilization", "Disable") | ||
| Remote.MakeGui(plr, "Output", { | ||
| Color = Color3.new(1, 0.5, 0); | ||
| Message = "Camera stabilization disabled"; | ||
| Title = "Camera Stabilization"; | ||
| }) | ||
| else | ||
| -- Show status | ||
| Remote.LoadCode(plr, [[ | ||
| local status = Variables.CameraStabilization and Variables.CameraStabilization.IsEnabled and Variables.CameraStabilization.IsEnabled() or false | ||
| Remote.MakeGui(service.Player, "Output", { | ||
| Color = status and Color3.new(0, 1, 0) or Color3.new(1, 0.5, 0); | ||
| Message = "Camera stabilization is currently " .. (status and "enabled" or "disabled"); | ||
| Title = "Camera Stabilization Status"; | ||
| }) | ||
| ]]) | ||
| end | ||
| end | ||
| }; | ||
| }; | ||
| end | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Extract the pixel threshold (
10) and timing parameters (0.15,2,5) into named constants or configuration entries so their purpose is clear and easily adjustable.