-- Written by Kip Turner, Copyright ROBLOX 2015 local TextService = game:GetService('TextService') local CoreGui = game:GetService("CoreGui") local GuiService = game:GetService('GuiService') local RunService = game:GetService('RunService') local ContextActionService = game:GetService("ContextActionService") local PlatformService = nil pcall(function() PlatformService = game:GetService('PlatformService') end) local GameOptionsSettings = settings():FindFirstChild("Game Options") local GuiRoot = CoreGui:FindFirstChild("RobloxGui") local Modules = GuiRoot:FindFirstChild("Modules") local Utility = require(Modules:FindFirstChild('Utility')) local GlobalSettings = require(Modules:FindFirstChild('GlobalSettings')) local ScreenManager = require(Modules:FindFirstChild('ScreenManager')) local AssetManager = require(Modules:FindFirstChild('AssetManager')) local Strings = require(Modules:FindFirstChild('LocalizedStrings')) local BACKGROUND_COLOR = Color3.new(3/255,3/255,3/255) local START_EDGE_SIZE = UDim2.new(0.9,0,0.9,0) local MIN_EDGE_SIZE = UDim2.new(0.85,0,0.85,0) local MAX_EDGE_SIZE = UDim2.new(1,0,1,0) local ZERO_VEC2 = Vector2.new(0,0) local THUMBSTICK_BORDER_SENSITIVITY = 0.05 local MAX_STICK_ACCELERATION = 3 local ACCELERATION_RATE = 1 local DPAD_STEP_AMOUNT = 2 local DPAD_CODE_TO_EDGE_PUSH = { [Enum.KeyCode.DPadDown] = Vector2.new(0.0, DPAD_STEP_AMOUNT); [Enum.KeyCode.DPadUp] = Vector2.new(0.0, -DPAD_STEP_AMOUNT); [Enum.KeyCode.DPadLeft] = Vector2.new(-DPAD_STEP_AMOUNT, 0.0); [Enum.KeyCode.DPadRight] = Vector2.new(DPAD_STEP_AMOUNT, 0.0); } local function CreateOverscanAdjustmentScreen(parent) local this = {} this.StickPosition = ZERO_VEC2 this.StickAcceleration = 1 local lastUpdate = nil local lastParent = parent local lastSavedOverscanPX = START_EDGE_SIZE.X.Scale local lastSavedOverscanPY = START_EDGE_SIZE.Y.Scale local function StorePreviousOverscanValues() local success, errormsg = pcall(function() lastSavedOverscanPX = GameOptionsSettings.OverscanPX lastSavedOverscanPY = GameOptionsSettings.OverscanPY end) if not success then print("Error, StorePreviousOverscanValues: OverscanPX and OverscanPY" , errormsg) end end StorePreviousOverscanValues() local MainContainer = Utility.Create'Frame' { Name = 'OverscanAdjustmentScreen'; Size = UDim2.new(1, 0, 1, 0); BorderSizePixel = 1; BackgroundTransparency = 0; BackgroundColor3 = BACKGROUND_COLOR; Visible = false; } local BackgroundImage = Utility.Create'ImageLabel' { Name = 'BackgroundImage'; Image = 'rbxasset://textures/ui/Shell/ScreenAdjustment/Background.png'; Size = UDim2.new(1,0,1,0); BackgroundTransparency = 1; Parent = MainContainer; }; local Title = Utility.Create'TextLabel' { Name = 'Title'; Text = Strings:LocalizedString('ScreenSizeWord'); Position = UDim2.new(0, 230, 0, 175); TextXAlignment = 'Left'; TextColor3 = GlobalSettings.WhiteTextColor; Font = GlobalSettings.LightFont; FontSize = GlobalSettings.HeaderSize; BackgroundTransparency = 1; Parent = MainContainer; }; do local titleTextSize = TextService:GetTextSize(Title.Text, Utility.ConvertFontSizeEnumToInt(Title.FontSize), Title.Font, Vector2.new()) Title.Size = UDim2.new(0, titleTextSize.X, 0, titleTextSize.Y) end local Prompt = Utility.Create'TextLabel' { Name = 'Prompt'; Text = Strings:LocalizedString('ResizeScreenPrompt'); Position = UDim2.new(0,230,0,225); TextXAlignment = 'Left'; TextColor3 = GlobalSettings.WhiteTextColor; Font = GlobalSettings.RegularFont; FontSize = GlobalSettings.ButtonSize; BackgroundTransparency = 1; Parent = MainContainer; }; do local ControllerImage = Utility.Create'ImageLabel' { Name = 'ControllerImage'; BackgroundTransparency = 1; Image = 'rbxasset://textures/ui/Shell/ScreenAdjustment/Controller@1080.png'; Size = UDim2.new(0,599,0,404); Parent = MainContainer; }; Utility.CalculateAnchor(ControllerImage, UDim2.new(0.5,0,0.5,0), Utility.Enum.Anchor.Center) local Line = Utility.Create'Frame' { Name = 'Line'; Size = UDim2.new(0.4,0,0,1); Position = UDim2.new(0.73, 0, 0.545, 0); BackgroundColor3 = Color3.new(1,1,1); BorderSizePixel = 0; BackgroundTransparency = 0; Parent = ControllerImage; }; local InputHint = Utility.Create'TextLabel' { Name = 'InputHint'; Text = Strings:LocalizedString('ResizeScreenInputHint'); Size = UDim2.new(0,0,0,0); Position = UDim2.new(1, 3, 0, -1); TextXAlignment = 'Left'; TextColor3 = GlobalSettings.WhiteTextColor; Font = GlobalSettings.RegularFont; FontSize = GlobalSettings.ButtonSize; BackgroundTransparency = 1; Parent = Line; }; end local AcceptButtonImage = Utility.Create'ImageLabel' { Name = 'AcceptButtonImage'; Size = UDim2.new(0,65,0,65); Position = UDim2.new(0.5, 25, 0.75, 0); Image = 'rbxasset://textures/ui/Shell/ButtonIcons/AButton.png'; BackgroundTransparency = 1; Parent = MainContainer; }; local AcceptHint = Utility.Create'TextLabel' { Name = 'AcceptHint'; Text = Strings:LocalizedString('AcceptWord'):upper(); Size = UDim2.new(0,0,1,0); Position = UDim2.new(1, 5, 0, -3); TextXAlignment = 'Left'; TextColor3 = GlobalSettings.WhiteTextColor; Font = GlobalSettings.RegularFont; FontSize = GlobalSettings.ButtonSize; BackgroundTransparency = 1; Parent = AcceptButtonImage; }; local ResetButtonImage = Utility.Create'ImageLabel' { Name = 'ResetButtonImage'; Size = UDim2.new(0,65,0,65); Image = 'rbxasset://textures/ui/Shell/ButtonIcons/XButton.png'; BackgroundTransparency = 1; Parent = MainContainer; }; local ResetHint = Utility.Create'TextLabel' { Name = 'ResetHint'; Text = Strings:LocalizedString('ResetWord'):upper(); Size = UDim2.new(0,0,1,0); Position = UDim2.new(1, 5, 0, -3); TextXAlignment = 'Left'; TextColor3 = GlobalSettings.WhiteTextColor; Font = GlobalSettings.RegularFont; FontSize = GlobalSettings.ButtonSize; BackgroundTransparency = 1; Parent = ResetButtonImage; }; local Edges = Utility.Create'Frame' { Name = 'Edges'; Size = START_EDGE_SIZE; BackgroundTransparency = 1; Parent = MainContainer; }; do local edgesSelectionImage = Utility.Create'ImageLabel' { Name = 'EdgesSelectionImage'; Size = UDim2.new(1,2,1,2); Position = UDim2.new(0,-1,0,-1); Image = 'rbxasset://textures/ui/Shell/ScreenAdjustment/ScreenRangeOverlay.png'; ScaleType = Enum.ScaleType.Slice; SliceCenter = Rect.new(21,21,41,41); BackgroundTransparency = 1; }; edgesSelectionImage.Parent = Edges end for i = 0, 3 do local CornerImage = Utility.Create'ImageLabel' { Name = 'CornerImage'; Size = UDim2.new(0, 95, 0, 95); BackgroundTransparency = 1; Rotation = 90 * i; Image = "rbxasset://textures/ui/Shell/ScreenAdjustment/ScreenAdjustmentArrow.png"; Parent = Edges; }; if i == 0 then Utility.CalculateAnchor(CornerImage, UDim2.new(0,0,0,0), Utility.Enum.Anchor.TopLeft) elseif i == 1 then Utility.CalculateAnchor(CornerImage, UDim2.new(1, 0, 0, 0), Utility.Enum.Anchor.TopRight) elseif i == 2 then Utility.CalculateAnchor(CornerImage, UDim2.new(1, 0, 1, 0), Utility.Enum.Anchor.BottomRight) elseif i == 3 then Utility.CalculateAnchor(CornerImage, UDim2.new(0, 0, 1, 0), Utility.Enum.Anchor.BottomLeft) end end local function RefreshLayout() ResetButtonImage.Position = UDim2.new(0.5 - ResetButtonImage.Size.X.Scale, -ResetHint.TextBounds.X - 25 - ResetButtonImage.Size.X.Offset, 0.75, 0); Prompt.Size = UDim2.new(0, Prompt.TextBounds.X, 0, Prompt.TextBounds.Y) end local closedEvent = Instance.new("BindableEvent") closedEvent.Name = "ClosedEvent" this.Closed = closedEvent.Event local EdgePercent = Vector2.new(START_EDGE_SIZE.X, START_EDGE_SIZE.Y) function this:GetAdjustmentEdgesPercent() return EdgePercent end function this:SetAdjustmentEdges(newEdgeSize) EdgePercent = Utility.ClampVector2(Vector2.new(MIN_EDGE_SIZE.X.Scale, MIN_EDGE_SIZE.Y.Scale), Vector2.new(1,1), newEdgeSize) local guiSize = GuiRoot.AbsoluteSize local absoluteEdgeSize = EdgePercent * guiSize -- Round to nearest 2 so that we can evenly space above and below local roundedAbsoluteEdgeSize = Vector2.new(Utility.Round(absoluteEdgeSize.X/2), Utility.Round(absoluteEdgeSize.Y/2)) * 2 roundedAbsoluteEdgeSize = Utility.ClampVector2(Vector2.new(), guiSize, roundedAbsoluteEdgeSize) Edges.Size = UDim2.new(0, roundedAbsoluteEdgeSize.X, 0, roundedAbsoluteEdgeSize.Y) Edges.Position = UDim2.new(0, (guiSize.X - roundedAbsoluteEdgeSize.X) / 2, 0, (guiSize.Y - roundedAbsoluteEdgeSize.Y) / 2) end function this:PushAdjustmentEdges(pushAmount) self:SetAdjustmentEdges(self:GetAdjustmentEdgesPercent() + pushAmount) end function this:PushAdjustmentEdgesByPixels(pixelPushAmount) local guiSize = GuiRoot.AbsoluteSize local pushAmount = pixelPushAmount / guiSize if Utility.IsFinite(pushAmount.X) and Utility.IsFinite(pushAmount.Y) then self:SetAdjustmentEdges(self:GetAdjustmentEdgesPercent() + pushAmount) end end function this:Update() local now = tick() if lastUpdate and self.StickPosition ~= ZERO_VEC2 then local delta = now - lastUpdate local transformedStick = Utility.GamepadLinearToCurve(self.StickPosition, 0.2) self:PushAdjustmentEdges(Vector2.new(transformedStick.X, -transformedStick.Y) * self.StickAcceleration * delta * THUMBSTICK_BORDER_SENSITIVITY) if transformedStick ~= ZERO_VEC2 then self.StickAcceleration = math.min(self.StickAcceleration + delta * ACCELERATION_RATE, MAX_STICK_ACCELERATION) else self.StickAcceleration = 1 end end lastUpdate = now end function this:ResetEdges() self:SetAdjustmentEdges(Vector2.new(START_EDGE_SIZE.X, START_EDGE_SIZE.Y)) end local GuiRootChangedConn = nil function this:Show() MainContainer.Visible = true MainContainer.Parent = lastParent RefreshLayout() self.StickAcceleration = 1 if not UserSettings().GameSettings:InStudioMode() and GameOptionsSettings.OverscanPX > 0 and GameOptionsSettings.OverscanPY > 0 then this:SetAdjustmentEdges(Vector2.new(GameOptionsSettings.OverscanPX, GameOptionsSettings.OverscanPY)) else self:ResetEdges() end local success, errormsg = pcall(function() StorePreviousOverscanValues() -- Need to do this step so that the player -- can accurately estimate their TV's overscan GameOptionsSettings.OverscanPX = 1 GameOptionsSettings.OverscanPY = 1 end) if not success then print("Error resetting Overscan Screen Resolution:" , errormsg) end Utility.DisconnectEvent(GuiRootChangedConn) GuiRootChangedConn = GuiRoot.Changed:connect(function(prop) if prop == 'AbsoluteSize' then self:PushAdjustmentEdgesByPixels(ZERO_VEC2) end end) self:PushAdjustmentEdgesByPixels(ZERO_VEC2) end function this:Hide() MainContainer.Visible = false MainContainer.Parent = nil GuiRootChangedConn = Utility.DisconnectEvent(GuiRootChangedConn) end function this:Focus() GuiService.SelectedCoreObject = nil RefreshLayout() self.StickPosition = ZERO_VEC2 self.StickAcceleration = 1 local beginSeen = false ContextActionService:BindCoreAction("ResetAdjustmentScreen", function(actionName, inputState, inputObject) if inputState == Enum.UserInputState.End then self:ResetEdges() end end, false, Enum.KeyCode.ButtonX) ContextActionService:BindCoreAction("AcceptAdjustmentScreen", function(actionName, inputState, inputObject) if inputState == Enum.UserInputState.Begin then beginSeen = true elseif inputState == Enum.UserInputState.End and beginSeen then local success, errormsg = pcall(function() GameOptionsSettings.OverscanPX = math.min(1, self:GetAdjustmentEdgesPercent().X) GameOptionsSettings.OverscanPY = math.min(1, self:GetAdjustmentEdgesPercent().Y) StorePreviousOverscanValues() end) if not success then print("Error setting Overscan Screen Resolution:" , errormsg) end if self == ScreenManager:GetTopScreen() then ScreenManager:CloseCurrent() closedEvent:Fire() end end end, false, Enum.KeyCode.ButtonA) ContextActionService:BindCoreAction("ThumbstickAdjustmentScreen", function(actionName, inputState, inputObject) self.StickPosition = Vector2.new(inputObject.Position.X, inputObject.Position.Y) end, false, Enum.KeyCode.Thumbstick2) ContextActionService:BindCoreAction("DPadAdjustmentScreen", function(actionName, inputState, inputObject) if inputState == Enum.UserInputState.Begin then local pushAmount = DPAD_CODE_TO_EDGE_PUSH[inputObject.KeyCode] if pushAmount then self:PushAdjustmentEdgesByPixels(pushAmount) end end end, false, Enum.KeyCode.DPadDown, Enum.KeyCode.DPadUp, Enum.KeyCode.DPadLeft, Enum.KeyCode.DPadRight) RunService:BindToRenderStep("UpdateAdjustmentScreen", Enum.RenderPriority.Input.Value, function() self:Update() end) end function this:RemoveFocus() self.StickPosition = ZERO_VEC2 lastUpdate = nil ContextActionService:UnbindCoreAction("ResetAdjustmentScreen") ContextActionService:UnbindCoreAction("AcceptAdjustmentScreen") ContextActionService:UnbindCoreAction("ThumbstickAdjustmentScreen") ContextActionService:UnbindCoreAction("DPadAdjustmentScreen") RunService:UnbindFromRenderStep("UpdateAdjustmentScreen") end function this:SetPosition(newPosition) MainContainer.Position = newPosition end function this:SetParent(newParent) MainContainer.Parent = newParent lastParent = newParent end function this:SetStyleForInGame() BackgroundImage.Visible = false MainContainer.BackgroundTransparency = 0.2 local function setZIndex(guiObject, newZIndex) if not guiObject:IsA("GuiObject") then return end guiObject.ZIndex = newZIndex local children = guiObject:GetChildren() for i = 1, #children do setZIndex(children[i], newZIndex) end end setZIndex(MainContainer, 9) end if not UserSettings().GameSettings:InStudioMode() then pcall(function() PlatformService.Suspended:connect(function() GameOptionsSettings.OverscanPX = lastSavedOverscanPX GameOptionsSettings.OverscanPY = lastSavedOverscanPY end) end) end return this end return CreateOverscanAdjustmentScreen