local TweenService = game:GetService("TweenService") local RunService = game:GetService("RunService") local Players = game:GetService("Players") local UserInputService = game:GetService("UserInputService") local Settings = UserSettings() local GameSettings = Settings.GameSettings local LocalPlayer = Players.LocalPlayer local IMAGE_INTRO_MOVE = "rbxasset://textures/ui/Input/IntroMove.png" local IMAGE_INTRO_CAMERA = "rbxasset://textures/ui/Input/IntroCamera.png" local IMAGE_INTRO_CAMERA_PINCH = "rbxasset://textures/ui/Input/IntroCameraPinch.png" local IMAGE_DASHED_LINE = "rbxasset://textures/ui/Input/DashedLine.png" local IMAGE_DASHED_LINE_90 = "rbxasset://textures/ui/Input/DashedLine90.png" local MIDDLE_TRANSPARENCIES = { 1 - 0.69, 1 - 0.50, 1 - 0.40, 1 - 0.30, 1 - 0.20, 1 - 0.10, 1 - 0.05 } local MOVE_CAMERA_THRESHOLD = 50 local ParentFrame = nil local GestureArea = nil local ThumbstickFrame = nil local StartImage, EndImage, MiddleImages = nil, nil, {} local IntroMoveImage = nil local IntroJumpImage = nil local IntroJumpImageTap = nil local IntroCameraImage = nil local IntroCornerAnim = nil local IntroDashedLineTop, IntroDashedLineSide = nil local GenericFadeTweenInfo = TweenInfo.new(0.5, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut) local IntroMoveSpriteSize = 128 local IntroMoveImageSize = 128 local noThumbstickFrameFade = true local function create(className) return function(props) local instance = Instance.new(className) for key, val in pairs(props) do if typeof(key) == "string" then if key ~= "Parent" then instance[key] = val end else if typeof(val) == "Instance" then val.Parent = instance end end end instance.Parent = props.Parent return instance end end local Intro = { running = false, portraitMode = false, fadeThumbstick = nil, fadeThumbstickFrame = nil, didMoveThumbstick = false, didJump = false, didMoveCamera = false, currentState = nil, states = {}, } function Intro.setup(isBigScreen, parentFrame, gestureArea, thumbstickFrame, startImage, endImage, middleImages) ParentFrame = parentFrame GestureArea = gestureArea ThumbstickFrame = thumbstickFrame StartImage = startImage EndImage = endImage MiddleImages = middleImages if isBigScreen then IntroMoveImageSize = IntroMoveImageSize * 2 end IntroMoveImage = create("ImageLabel") { Name = "IntroMoveAnimation", BackgroundTransparency = 1, Image = IMAGE_INTRO_MOVE, AnchorPoint = Vector2.new(102/256, 55/256), Position = UDim2.new(0.5, 0, 0.5, 0), Size = UDim2.new(0, IntroMoveImageSize, 0, IntroMoveImageSize), ImageRectOffset = Vector2.new(IntroMoveSpriteSize, IntroMoveSpriteSize), ImageRectSize = Vector2.new(IntroMoveSpriteSize, IntroMoveSpriteSize), ZIndex = 10, Visible = false, Parent = EndImage } IntroJumpImage = create("ImageLabel") { Name = "IntroJumpAnimation", BackgroundTransparency = 1, AnchorPoint = Vector2.new(102/256, 55/256), Position = UDim2.new(0.75, 0, 0.25, 0), Image = IMAGE_INTRO_MOVE, ImageRectOffset = Vector2.new(0, 0), ImageRectSize = Vector2.new(IntroMoveSpriteSize, IntroMoveSpriteSize), ZIndex = 10, Visible = false, Parent = ThumbstickFrame } IntroJumpImageTap = create("ImageLabel") { Name = "IntroJumpTapAnimation", BackgroundTransparency = 1, Image = IMAGE_INTRO_MOVE, Position = UDim2.new(0, 0, 0, 0), AnchorPoint = Vector2.new(0, 0), Size = UDim2.new(1, 0, 1, 0), ImageTransparency = 1, ImageRectOffset = Vector2.new(IntroMoveSpriteSize, 0), ImageRectSize = Vector2.new(IntroMoveSpriteSize, IntroMoveSpriteSize), ZIndex = 10, Visible = false, Parent = IntroJumpImage } IntroCameraImage = create("ImageLabel") { Name = "IntroCameraDragAnimation", BackgroundTransparency = 1, Image = IMAGE_INTRO_MOVE, ImageRectOffset = Vector2.new(0, IntroMoveSpriteSize), ImageRectSize = Vector2.new(IntroMoveSpriteSize, IntroMoveSpriteSize), Position = UDim2.new(0.75, 0, -0.6, 0), Size = UDim2.new(0, IntroMoveImageSize, 0, IntroMoveImageSize), AnchorPoint = Vector2.new(0.5, 0), ZIndex = 10, Visible = false, Parent = GestureArea } IntroPinchImage = create("ImageLabel") { Name = "IntroCameraPinchAnimation", BackgroundTransparency = 1, Image = IMAGE_INTRO_CAMERA_PINCH, ImageRectOffset = Vector2.new(0, 0), ImageRectSize = Vector2.new(IntroMoveSpriteSize, IntroMoveSpriteSize), Position = UDim2.new(0.95, 0, 0.15, 0), Size = UDim2.new(0, IntroMoveImageSize, 0, IntroMoveImageSize), AnchorPoint = Vector2.new(1, 0), Rotation = 50, ZIndex = 10, Visible = false, Parent = GestureArea } IntroDashedLineTop = create("ImageLabel") { Name = "DashedLine", BackgroundTransparency = 1, Position = UDim2.new(0, 0, 0, 0), Size = UDim2.new(1, -5, 0, 10), AnchorPoint = Vector2.new(0, 0.5), Image = IMAGE_DASHED_LINE, ImageTransparency = 0.5, ScaleType = Enum.ScaleType.Tile, TileSize = UDim2.new(0, 64, 0, 10), Visible = false, Parent = ThumbstickFrame } IntroDashedLineSide = IntroDashedLineTop:Clone() IntroDashedLineSide.Image = IMAGE_DASHED_LINE_90 IntroDashedLineSide.Position = UDim2.new(1, 0, 0, -5) IntroDashedLineSide.AnchorPoint = Vector2.new(0.5, 0) IntroDashedLineSide.Size = UDim2.new(0, 10, 1, 0) IntroDashedLineSide.TileSize = UDim2.new(0, 10, 0, 64) IntroDashedLineSide.Parent = ThumbstickFrame end function Intro.setPortraitMode(portraitMode) if not IntroDashedLineSide or not Intro.running then return end Intro.portraitMode = portraitMode IntroDashedLineSide.Visible = not portraitMode end function Intro.addState(stateName) local state = {} state.counter = 0 Intro.states[stateName] = state return state end function Intro.onCompleted() --TODO: Remove this pcall when API is stable pcall(function() GameSettings:SetOnboardingCompleted("DynamicThumbstick") end) local fadeOutTop = TweenService:Create(IntroDashedLineTop, GenericFadeTweenInfo, { ImageTransparency = 1 }) local fadeOutSide = TweenService:Create(IntroDashedLineSide, GenericFadeTweenInfo, { ImageTransparency = 1}) fadeOutTop:Play() fadeOutSide:Play() fadeOutTop.Completed:wait() IntroDashedLineTop.Visible = false IntroDashedLineSide.Visible = false Intro.running = false end function Intro.setState(newState) if Intro.currentState == newState then return end if Intro.currentState then coroutine.wrap(function() Intro.currentState:stop() end)() end Intro.currentState = newState Intro.currentState.counter = Intro.currentState.counter + 1 Intro.currentState:start() end function Intro.repeatState() if not Intro.currentState then return end coroutine.wrap(function() Intro.currentState:stop() end)() Intro.currentState.counter = Intro.currentState.counter + 1 Intro.currentState:start() end function Intro.pause() if not Intro.currentState then return end Intro.currentState:stop() Intro.currentState = nil end function Intro.onThumbstickMoveBegin() if Intro.currentState == Intro.states.MoveThumbstick then Intro.states.MoveThumbstick.startedMoving = true local moveTween = Intro.states.MoveThumbstick.moveTween if moveTween then moveTween:Cancel() end end end function Intro.onThumbstickMoved(dist) if Intro.currentState == Intro.states.MoveThumbstick then if dist > 100 then Intro.pause() spawn(function() wait(0.5) while StartImage.ImageTransparency ~= 1 do wait() end if Intro.states.MoveThumbstick.counter == 2 then Intro.setState(Intro.states.MoveCamera) else Intro.setState(Intro.states.MoveThumbstick) end end) else Intro.states.MoveThumbstick.startedMoving = false end end end function Intro.onJumped() if Intro.currentState == Intro.states.Jump then Intro.pause() spawn(function() wait(1) if Intro.states.Jump.counter > 1 then Intro.setState(Intro.states.MoveCamera) else Intro.setState(Intro.states.Jump) end end) end end function Intro.onCameraMoved() if Intro.currentState == Intro.states.MoveCamera then wait(1) Intro.setState(Intro.states.ZoomCamera) end end function Intro.onCameraZoomed() if Intro.currentState == Intro.states.ZoomCamera then Intro.pause() end end function Intro.play() for _, state in pairs(Intro.states) do state.counter = 0 end Intro.running = true Intro.currentState = nil local portraitMode = false local currentCamera = workspace.CurrentCamera if currentCamera then portraitMode = currentCamera.ViewportSize.X < currentCamera.ViewportSize.Y end Intro.setPortraitMode(portraitMode) Intro.setState(Intro.states.MoveThumbstick) end Intro.addState("MoveThumbstick") do function Intro.states.MoveThumbstick:start() StartImage.Visible = true EndImage.Visible = true IntroMoveImage.Visible = true IntroDashedLineTop.Visible = true if not Intro.portraitMode then IntroDashedLineSide.Visible = true end self.startedMoving = false StartImage.Position = UDim2.new(0.5, 0, 0.6, 0) EndImage.Position = UDim2.new(0.5, 0, 0.6, 0) local moveTweenTarget = UDim2.new(0.3, 0, -0.25, 0) if Intro.states.MoveThumbstick.counter % 2 ~= 0 then moveTweenTarget = UDim2.new(0.7, 0, -0.25, 0) end local moveTweenInfo = TweenInfo.new(1.5, Enum.EasingStyle.Quart, Enum.EasingDirection.InOut) self.moveTween = TweenService:Create(EndImage, moveTweenInfo, { Position = moveTweenTarget }) local moveFadeInfo = TweenInfo.new(0.15, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut) local fadeOutTween = TweenService:Create(IntroMoveImage, moveFadeInfo, { ImageTransparency = 1 }) local fadeInTween = TweenService:Create(IntroMoveImage, moveFadeInfo, { ImageTransparency = 0 }) self.moveAnimActive = true coroutine.wrap(function() while Intro.currentState == self do if not self.startedMoving then if not noThumbstickFrameFade then Intro.fadeThumbstickFrame(moveTweenInfo.Time, 0.05) end StartImage.Position = UDim2.new(0.5, 0, 0.6, 0) EndImage.Position = UDim2.new(0.5, 0, 0.6, 0) self.moveTween:Play() Intro.fadeThumbstick(true) fadeInTween:Play() self.moveTween.Completed:wait() if Intro.currentState == self then Intro.fadeThumbstick(false) fadeOutTween:Play() fadeOutTween.Completed:wait() wait(0.5) end else wait() end end end)() end function Intro.states.MoveThumbstick:stop() self.moveTween:Cancel() IntroMoveImage.Visible = false if self.iconOptConn then self.iconOptConn:disconnect() self.iconOptConn = nil end end end Intro.addState("Jump") do function Intro.states.Jump:start() IntroJumpImage.Visible = true IntroJumpImageTap.Visible = true local jumpImageBaseSize = IntroMoveImageSize IntroJumpImage.Size = UDim2.new(0, jumpImageBaseSize * 1.5, 0, jumpImageBaseSize * 1.25) IntroJumpImageTap.ImageTransparency = 1 local fadeInInfo = TweenInfo.new(0.15, Enum.EasingStyle.Quint, Enum.EasingDirection.InOut) local fadeOutInfo = TweenInfo.new(0.5, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut) self.fadeHandIn = TweenService:Create(IntroJumpImage, fadeInInfo, { ImageTransparency = 0, Size = UDim2.new(0, jumpImageBaseSize, 0, jumpImageBaseSize * 0.95) }) self.fadeHandOut = TweenService:Create(IntroJumpImage, fadeOutInfo, { ImageTransparency = 0.25, Size = UDim2.new(0, jumpImageBaseSize * 1.25, 0, jumpImageBaseSize * 1.25) }) self.fadeTapIn = TweenService:Create(IntroJumpImageTap, fadeInInfo, { ImageTransparency = 0 }) self.fadeTapOut = TweenService:Create(IntroJumpImageTap, fadeOutInfo, { ImageTransparency = 1 }) coroutine.wrap(function() while Intro.currentState == self do self.fadeHandIn:Play() IntroJumpImageTap.ImageTransparency = 1 if self.iconOptConn then self.iconOptConn:disconnect() self.iconOptConn = nil end wait(self.fadeHandIn.Completed:wait()) if not noThumbstickFrameFade then Intro.fadeThumbstickFrame(fadeOutInfo.Time, 0) end IntroJumpImageTap.ImageTransparency = 0 self.fadeHandOut:Play() self.fadeTapOut:Play() self.fadeHandOut.Completed:wait() end end)() end function Intro.states.Jump:stop() IntroJumpImage.Visible = false IntroJumpImageTap.Visible = false end end Intro.addState("MoveCamera") do function Intro.states.MoveCamera:start() IntroCameraImage.Visible = true local swipeInfo = TweenInfo.new(1, Enum.EasingStyle.Quint, Enum.EasingDirection.InOut) local swipeTweenLeft = TweenService:Create(IntroCameraImage, swipeInfo, { Position = UDim2.new(0.25, 0, 0.25, 0) }) local swipeTweenRight = TweenService:Create(IntroCameraImage, swipeInfo, { Position = UDim2.new(0.75, 0, 0.25, 0) }) local camera = workspace.CurrentCamera local cameraLookStart = camera.CFrame.lookVector coroutine.wrap(function() while Intro.currentState == self do swipeTweenLeft:Play() swipeTweenLeft.Completed:wait() swipeTweenRight:Play() swipeTweenRight.Completed:wait() end end)() local function isInCameraArea(touchPos) return touchPos.X >= GestureArea.AbsolutePosition.X and touchPos.Y >= GestureArea.AbsolutePosition.Y and touchPos.X < GestureArea.AbsolutePosition.X + GestureArea.AbsoluteSize.X and touchPos.Y < GestureArea.AbsolutePosition.Y + GestureArea.AbsoluteSize.Y end local touchObj = nil local recordedMovement = Vector3.new() self.onInputBeganConn = UserInputService.InputBegan:Connect(function(inputObj, wasProcessed) if inputObj.UserInputType == Enum.UserInputType.Touch and isInCameraArea(inputObj.Position) and not wasProcessed then touchObj = inputObj end end) self.onInputChangedConn = UserInputService.InputChanged:Connect(function(inputObj, wasProcessed) if inputObj == touchObj then recordedMovement = recordedMovement + inputObj.Delta if recordedMovement.magnitude > MOVE_CAMERA_THRESHOLD then IntroCameraImage.Visible = false end end end) self.onInputEndedConn = UserInputService.InputEnded:Connect(function(inputObj, wasProcessed) if inputObj == touchObj then touchObj = nil if recordedMovement.magnitude > MOVE_CAMERA_THRESHOLD then --todo: make this number a constant Intro.onCameraMoved() end end end) end function Intro.states.MoveCamera:stop() if self.iconOptConn then self.iconOptConn:disconnect() self.iconOptConn = nil end IntroCameraImage.Visible = false end end Intro.addState("ZoomCamera") do function Intro.states.ZoomCamera:start() IntroPinchImage.Visible = true local camera = workspace.CurrentCamera local zoomStart = (camera.Focus.p - camera.CFrame.p).magnitude coroutine.wrap(function() while Intro.currentState == self do IntroPinchImage.ImageRectOffset = Vector2.new(0, 0) wait(0.75) IntroPinchImage.ImageRectOffset = Vector2.new(128, 0) wait(0.25) end end)() coroutine.wrap(function() while Intro.currentState == self do local zoom = (camera.Focus.p - camera.CFrame.p).magnitude if math.abs(zoom - zoomStart) > 3 then Intro.onCameraZoomed() end wait() end end)() end function Intro.states.ZoomCamera:stop() IntroPinchImage.Visible = false Intro.onCompleted() end end return Intro