local Players = game:GetService("Players") local UserInputService = game:GetService("UserInputService") local RunService = game:GetService("RunService") local ContextActionService = game:GetService("ContextActionService") local TweenService = game:GetService("TweenService") local player = Players.LocalPlayer local camera = workspace.CurrentCamera -- --- CONFIGURATION --- local CONFIG = { -- Movement DoubleJumpPower = 60, RollDuration = 0.5, RollDistance = 50, BaseWalkSpeed = 16, MaxWalkSpeed = 150, Acceleration = 30, BrakingDuration = 0.7, StopThreshold = 1, BaseFOV = 70, MaxFOV = 120, -- Tool DashSpeed = 200, DashMaxTime = 1, -- Assets RollAnimID = "rbxassetid://18537367238", RunAnimID = "rbxassetid://85752365414182", GreenCursorID = "rbxassetid://1460596350" } local DECELERATION = CONFIG.MaxWalkSpeed / CONFIG.BrakingDuration -- --- STATE VARIABLES --- local isMobileMode = false local selectionMade = false local currentCharacter = nil -- --- GUI CREATION --- local function createIntroGUI() local existing = player:WaitForChild("PlayerGui"):FindFirstChild("MaldixIntroGUI") if existing then existing:Destroy() end local screenGui = Instance.new("ScreenGui") screenGui.Name = "MaldixIntroGUI" screenGui.Parent = player:WaitForChild("PlayerGui") screenGui.ResetOnSpawn = false local frame = Instance.new("Frame") frame.Name = "MainFrame" frame.Size = UDim2.new(0, 400, 0, 350) frame.Position = UDim2.new(0.5, 0, 0.5, 0) frame.AnchorPoint = Vector2.new(0.5, 0.5) frame.BackgroundColor3 = Color3.new(0.14, 0.14, 0.14) frame.BorderSizePixel = 0 frame.Parent = screenGui local uiCorner = Instance.new("UICorner") uiCorner.CornerRadius = UDim.new(0, 12) uiCorner.Parent = frame local header = Instance.new("TextLabel") header.Text = "Maldix's Sonic Script" header.Size = UDim2.new(1, 0, 0, 50) header.BackgroundTransparency = 1 header.TextColor3 = Color3.new(0.33, 1, 0.5) header.Font = Enum.Font.FredokaOne header.TextSize = 28 header.Parent = frame local desc = Instance.new("TextLabel") desc.Text = [[ Controls: • WASD / Joystick: Move (Accelerate) • Space: Jump (Double tap to Double Jump) • R / Button: Roll (Spam to go fast) • Dash Tool: Click in air to Homing Dash Please select your device type: ]] desc.Size = UDim2.new(1, -40, 0, 200) desc.Position = UDim2.new(0, 20, 0, 50) desc.BackgroundTransparency = 1 desc.TextColor3 = Color3.new(0.86, 0.86, 0.86) desc.Font = Enum.Font.GothamMedium desc.TextSize = 16 desc.TextXAlignment = Enum.TextXAlignment.Left desc.TextYAlignment = Enum.TextYAlignment.Top desc.TextWrapped = true desc.Parent = frame local btnPC = Instance.new("TextButton") btnPC.Name = "PCButton" btnPC.Text = "PC" btnPC.Size = UDim2.new(0.4, 0, 0, 50) btnPC.Position = UDim2.new(0.05, 0, 1, -60) btnPC.BackgroundColor3 = Color3.new(0.23, 0.23, 0.23) btnPC.TextColor3 = Color3.new(1, 1, 1) btnPC.Font = Enum.Font.GothamBold btnPC.TextSize = 18 btnPC.Parent = frame local btnCorner1 = Instance.new("UICorner") btnCorner1.CornerRadius = UDim.new(0, 8) btnCorner1.Parent = btnPC local btnMobile = Instance.new("TextButton") btnMobile.Name = "MobileButton" btnMobile.Text = "Mobile" btnMobile.Size = UDim2.new(0.4, 0, 0, 50) btnMobile.Position = UDim2.new(0.55, 0, 1, -60) btnMobile.BackgroundColor3 = Color3.new(0.23, 0.23, 0.23) btnMobile.TextColor3 = Color3.new(1, 1, 1) btnMobile.Font = Enum.Font.GothamBold btnMobile.TextSize = 18 btnMobile.Parent = frame local btnCorner2 = Instance.new("UICorner") btnCorner2.CornerRadius = UDim.new(0, 8) btnCorner2.Parent = btnMobile local function closeGui() screenGui:Destroy() end btnPC.MouseButton1Click:Connect(function() isMobileMode = false selectionMade = true closeGui() end) btnMobile.MouseButton1Click:Connect(function() isMobileMode = true selectionMade = true closeGui() end) end -- --- MAIN LOGIC --- local function onCharacterAdded(character) currentCharacter = character local humanoid = character:WaitForChild("Humanoid") local rootPart = character:WaitForChild("HumanoidRootPart") local animator = humanoid:WaitForChild("Animator") humanoid.WalkSpeed = CONFIG.BaseWalkSpeed local currentSpeed = CONFIG.BaseWalkSpeed local lastMoveDirection = Vector3.new(0, 0, -1) -- --- ANIMATIONS --- local runAnim = Instance.new("Animation") runAnim.AnimationId = CONFIG.RunAnimID local runTrack = animator:LoadAnimation(runAnim) runTrack.Priority = Enum.AnimationPriority.Movement runTrack.Looped = true local rollAnim = Instance.new("Animation") rollAnim.AnimationId = CONFIG.RollAnimID local rollTrack = animator:LoadAnimation(rollAnim) rollTrack.Priority = Enum.AnimationPriority.Action rollTrack.Looped = true local jumpAnim = Instance.new("Animation") jumpAnim.AnimationId = CONFIG.RollAnimID local jumpTrack = animator:LoadAnimation(jumpAnim) jumpTrack.Priority = Enum.AnimationPriority.Action jumpTrack.Looped = false -- --- TOOL SETUP --- local tool = Instance.new("Tool") tool.Name = "Dash Cursor" tool.RequiresHandle = false tool.CanBeDropped = false tool.Parent = player.Backpack local mouse = player:GetMouse() tool.Equipped:Connect(function() mouse.Icon = CONFIG.GreenCursorID end) tool.Unequipped:Connect(function() mouse.Icon = "" end) tool.Activated:Connect(function() local state = humanoid:GetState() local isInAir = (state == Enum.HumanoidStateType.Freefall or state == Enum.HumanoidStateType.Jumping) if isInAir then local targetPos = mouse.Hit.Position local startPos = rootPart.Position local direction = (targetPos - startPos).Unit local distance = (targetPos - startPos).Magnitude local duration = math.min(distance / CONFIG.DashSpeed, CONFIG.DashMaxTime) rollTrack:Play() local bv = Instance.new("BodyVelocity") bv.Velocity = direction * CONFIG.DashSpeed bv.MaxForce = Vector3.new(1e5, 1e5, 1e5) bv.P = 10000 bv.Parent = rootPart local bg = Instance.new("BodyGyro") bg.CFrame = CFrame.lookAt(startPos, targetPos) bg.MaxTorque = Vector3.new(1e5, 1e5, 1e5) bg.P = 20000 bg.Parent = rootPart task.delay(duration, function() if bv then bv:Destroy() end if bg then bg:Destroy() end rollTrack:Stop() rootPart.AssemblyLinearVelocity = Vector3.zero end) end end) -- --- DOUBLE JUMP --- local hasDoubleJumped = false local canTriggerDoubleJump = false humanoid.StateChanged:Connect(function(oldState, newState) if newState == Enum.HumanoidStateType.Landed then hasDoubleJumped = false canTriggerDoubleJump = false elseif newState == Enum.HumanoidStateType.Jumping then canTriggerDoubleJump = false task.wait(0.2) canTriggerDoubleJump = true elseif newState == Enum.HumanoidStateType.Freefall then if oldState == Enum.HumanoidStateType.Landed then canTriggerDoubleJump = true end end end) UserInputService.JumpRequest:Connect(function() local state = humanoid:GetState() local isInAir = (state == Enum.HumanoidStateType.Freefall or state == Enum.HumanoidStateType.Jumping) if canTriggerDoubleJump and not hasDoubleJumped and isInAir then hasDoubleJumped = true rootPart.AssemblyLinearVelocity = Vector3.new(rootPart.AssemblyLinearVelocity.X, CONFIG.DoubleJumpPower, rootPart.AssemblyLinearVelocity.Z) jumpTrack:Stop() jumpTrack:Play() humanoid:ChangeState(Enum.HumanoidStateType.Jumping) end end) -- --- ROLL LOGIC --- local rolling = false local holdingRollKey = false local function stopRolling() rolling = false rollTrack:Stop() end local function performRoll() local direction = rootPart.CFrame.LookVector local startPos = rootPart.Position local targetPos = startPos + (direction * CONFIG.RollDistance) local startTime = tick() local connection connection = RunService.RenderStepped:Connect(function() local elapsed = tick() - startTime if elapsed > CONFIG.RollDuration then connection:Disconnect() if holdingRollKey then performRoll() else stopRolling() end return end local rayParams = RaycastParams.new() rayParams.FilterDescendantsInstances = {character} rayParams.FilterType = Enum.RaycastFilterType.Exclude local rayResult = workspace:Raycast(rootPart.Position, direction * 3, rayParams) if rayResult and rayResult.Instance.CanCollide then connection:Disconnect() stopRolling() return end local alpha = elapsed / CONFIG.RollDuration local nextPos = startPos:Lerp(targetPos, alpha) rootPart.CFrame = CFrame.new(nextPos, nextPos + direction) end) end local function startRoll() if rolling then return end rolling = true rollTrack:Play() performRoll() end -- Define Input Handler local function handleRollInput(actionName, inputState) if inputState == Enum.UserInputState.Begin then holdingRollKey = true startRoll() elseif inputState == Enum.UserInputState.End then holdingRollKey = false end end -- Function to bind controls based on current selection local function bindControls() ContextActionService:UnbindAction("RollAction") -- Bind R key always, add TouchButton only if mobile ContextActionService:BindAction("RollAction", handleRollInput, isMobileMode, Enum.KeyCode.R) if isMobileMode then ContextActionService:SetTitle("RollAction", "Roll") -- Moved to Left Side (0.15 = 15% from left edge) ContextActionService:SetPosition("RollAction", UDim2.new(0.15, 0, 0.6, 0)) end end -- Watch for selection change if not yet made task.spawn(function() while not selectionMade do task.wait(0.5) end bindControls() end) if selectionMade then bindControls() end -- --- MOVEMENT LOOP --- local function getUserInputVector() local moveVector = Vector3.new(0, 0, 0) -- Explicit assignment used to prevent compiler errors if UserInputService:IsKeyDown(Enum.KeyCode.W) then moveVector = moveVector + Vector3.new(0,0,-1) end if UserInputService:IsKeyDown(Enum.KeyCode.S) then moveVector = moveVector + Vector3.new(0,0,1) end if UserInputService:IsKeyDown(Enum.KeyCode.A) then moveVector = moveVector + Vector3.new(-1,0,0) end if UserInputService:IsKeyDown(Enum.KeyCode.D) then moveVector = moveVector + Vector3.new(1,0,0) end local success, module = pcall(function() return require(player.PlayerScripts.PlayerModule) end) if success and module then local controls = module:GetControls() if controls then local touchMove = controls:GetMoveVector() if touchMove.Magnitude > 0 then moveVector = touchMove end end end return moveVector end RunService.RenderStepped:Connect(function(dt) local inputVector = getUserInputVector() local isInputting = inputVector.Magnitude > 0.1 if isInputting then if rootPart.AssemblyLinearVelocity.Magnitude > 1 then lastMoveDirection = Vector3.new(rootPart.AssemblyLinearVelocity.X, 0, rootPart.AssemblyLinearVelocity.Z).Unit else lastMoveDirection = rootPart.CFrame.LookVector end currentSpeed = math.min(currentSpeed + (CONFIG.Acceleration * dt), CONFIG.MaxWalkSpeed) humanoid.WalkSpeed = currentSpeed else if currentSpeed > CONFIG.BaseWalkSpeed then currentSpeed = math.max(currentSpeed - (DECELERATION * dt), CONFIG.BaseWalkSpeed) humanoid.WalkSpeed = currentSpeed humanoid:Move(lastMoveDirection, false) else currentSpeed = CONFIG.BaseWalkSpeed humanoid.WalkSpeed = CONFIG.BaseWalkSpeed end end local velocityMag = Vector3.new(rootPart.AssemblyLinearVelocity.X, 0, rootPart.AssemblyLinearVelocity.Z).Magnitude local intensity = math.clamp((velocityMag - CONFIG.BaseWalkSpeed) / (CONFIG.MaxWalkSpeed - CONFIG.BaseWalkSpeed), 0, 1) local targetFOV = CONFIG.BaseFOV + (intensity * (CONFIG.MaxFOV - CONFIG.BaseFOV)) camera.FieldOfView = camera.FieldOfView + (targetFOV - camera.FieldOfView) * math.clamp(dt * 5, 0, 1) if velocityMag > 1 then if not runTrack.IsPlaying then runTrack:Play() end runTrack:AdjustSpeed(0.5 + (intensity * 2)) else if runTrack.IsPlaying then runTrack:Stop() end end end) end -- Init createIntroGUI() if player.Character then onCharacterAdded(player.Character) end player.CharacterAdded:Connect(onCharacterAdded)