sonic 2 movement mobile
Key RequiredUniversal

sonic 2 movement mobile

Universal Scriptgrilled_cheesegrilled_cheese 323 views 8.1K executesUpdated Jul 17, 2026
Unrated
00
Lua Script•226 lines (9460 chars)
-- Delta Fixed: 100% Mathematically Accurate Classic Sonic Engine
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local player = Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local humanoid = character:WaitForChild("Humanoid")
local rootPart = character:WaitForChild("HumanoidRootPart")

-- 1:1 SONIC RETRO PHYSICS GUIDE SETTINGS
local ACCELERATION_STEP = 0.45 -- Precise, fixed buildup increment per frame
local NATURAL_FRICTION = 0.965 -- Slippery classic ground drift deceleration
local BALL_ROLL_DECEL = 0.992  -- Slippery sliding friction multiplier
local RETRO_RUN_CAP = 34.0    -- Precise 6 pixels per frame flat-ground ceiling
local SPINDASH_LAUNCH = 90.0   -- Explosive release velocity
local RETRO_JUMP = 72.0        -- Classic pixel-height jump arc

-- Internal Physics States
local currentMomentum = 16.0
local isSpindashing = false
local isRollingBall = false
local spinDegrees = 0

-- Configure baseline properties securely
humanoid.UseJumpPower = true
humanoid.JumpPower = RETRO_JUMP
humanoid.WalkSpeed = 0.1 -- Disable native ground handling to stop instant speed overrides

-- BUILD MOBILE HUD OVERLAY
local ScreenGui = Instance.new("ScreenGui", player:WaitForChild("PlayerGui"))
ScreenGui.ResetOnSpawn = false

local function buildTouchButton(text, pos, color)
    local btn = Instance.new("TextButton", ScreenGui)
    btn.Text = text
    btn.Position = pos
    btn.Size = UDim2.new(0, 120, 0, 45)
    btn.BackgroundColor3 = color
    btn.TextColor3 = Color3.fromRGB(255, 255, 255)
    btn.Font = Enum.Font.SourceSansBold
    btn.TextSize = 15
    Instance.new("UICorner", btn).CornerRadius = UDim.new(0, 10)
    return btn
end

local SpindashBtn = buildTouchButton("SPINDASH", UDim2.new(0.70, 0, 0.50, 0), Color3.fromRGB(0, 110, 220))

-- EXECUTOR-SAFE PHYSICS DRIVER (Bypasses Humanoid velocity clamping loops)
local physicsMotor = rootPart:FindFirstChild("SonicBypassForce") or Instance.new("BodyVelocity", rootPart)
physicsMotor.Name = "SonicBypassForce"
physicsMotor.MaxForce = Vector3.new(45000, 0, 45000) -- Leaves Y free so jumping handles natively
physicsMotor.Velocity = Vector3.new(0,0,0)

-- Container for the visual model sphere
local visualSpinSphere = nil

-- RETRO VISUAL GENERATOR
local function deleteSpinBall()
    if visualSpinSphere then
        visualSpinSphere:Destroy()
        visualSpinSphere = nil
    end
    for _, part in pairs(character:GetChildren()) do
        if part:IsA("BasePart") and part.Name ~= "HumanoidRootPart" then
            part.Transparency = 0
        end
    end
    if character:FindFirstChild("Animate") and not character.Animate.Enabled then 
        character.Animate.Enabled = true 
    end
end

local function spawnSpinBall()
    deleteSpinBall()
    
    local targetColor = Color3.fromRGB(0, 100, 255) 
    local headPart = character:FindFirstChild("Head")
    if headPart and headPart:IsA("BasePart") then targetColor = headPart.Color end
    
    for _, part in pairs(character:GetChildren()) do
        if part:IsA("BasePart") and part.Name ~= "HumanoidRootPart" then part.Transparency = 1 end
    end
    if character:FindFirstChild("Animate") and character.Animate.Enabled then character.Animate.Enabled = false end
    
    local sphere = Instance.new("Part")
    sphere.Name = "SonicSpinSphere"
    sphere.Size = Vector3.new(3.8, 3.8, 3.8)
    sphere.Shape = Enum.PartType.Ball
    sphere.Color = targetColor 
    sphere.Material = Enum.Material.Neon
    sphere.CanCollide = false 
    sphere.Parent = character
    
    local weld = Instance.new("Weld")
    weld.Name = "SpinWeldObject"
    weld.Part0 = rootPart
    weld.Part1 = sphere
    weld.C0 = CFrame.new(0, -0.4, 0) 
    weld.Parent = sphere
    
    visualSpinSphere = sphere
end

-- TRACK AIRBORNE AND LANDED STATUS CHANGES
humanoid.StateChanged:Connect(function(_, newState)
    if newState == Enum.HumanoidStateType.Freefall or newState == Enum.HumanoidStateType.Jumping then
        isRollingBall = true 
        spawnSpinBall() 
    elseif newState == Enum.HumanoidStateType.Landed then
        if isRollingBall then
            isRollingBall = false
            deleteSpinBall()
        end
    end
end)

-- CORE RETRO STEP PHYSICS RUNNER LOOP
RunService.Heartbeat:Connect(function(deltaTime)
    if not character or not rootPart or not humanoid or not physicsMotor then return end
    
    local flatVelocity = Vector3.new(rootPart.AssemblyLinearVelocity.X, 0, rootPart.AssemblyLinearVelocity.Z).Magnitude
    
    -- Clean rolling state exit upon dropping below floor limits
    if isRollingBall and flatVelocity < 4 and not isSpindashing and humanoid.FloorMaterial ~= Enum.Material.Air then
        isRollingBall = false
        deleteSpinBall()
        physicsMotor.Velocity = Vector3.new(0, 0, 0)
    end
    
    -- ROTATE GEOMETRIC SPHERE
    if (isRollingBall or isSpindashing) and visualSpinSphere then
        local weld = visualSpinSphere:FindFirstChild("SpinWeldObject")
        if weld then
            local speedMultiplier = (flatVelocity < 5) and 180 or (flatVelocity + 45) * 35
            spinDegrees = (spinDegrees + (deltaTime * speedMultiplier)) % 360
            weld.C0 = CFrame.new(0, -0.4, 0) * CFrame.Angles(math.rad(-spinDegrees), 0, 0)
        end
    end
    
    -- ABSOLUTE FREEZE CONSTRAINT DURING SPINDASH CHARGING (Blocks joystick movement completely)
    if isSpindashing then
        rootPart.AssemblyLinearVelocity = Vector3.new(0, rootPart.AssemblyLinearVelocity.Y, 0)
        physicsMotor.Velocity = Vector3.new(0, 0, 0)
        return
    end
    
    -- READ JOYSTICK VECTOR FOR CUSTOM MOMENTUM CALCULATION
    local thumbstickInput = humanoid.MoveDirection
    
    if thumbstickInput.Magnitude > 0.05 then
        if isRollingBall then
            -- Low Friction Slidings: Keeps rolling momentum drifting smoothly
            currentMomentum = math.max(16, currentMomentum * (BALL_ROLL_DECEL ^ (deltaTime * 60)))
            physicsMotor.Velocity = rootPart.CFrame.LookVector * currentMomentum
        else
            -- FIXED ACCELERATION STEP: Gradually builds up velocity the longer you hold forward
            if currentMomentum < RETRO_RUN_CAP then
                currentMomentum = math.min(RETRO_RUN_CAP, currentMomentum + (ACCELERATION_STEP * deltaTime * 60))
            else
                -- Slow down gradually if launched over the running limit via a Spindash
                currentMomentum = math.max(RETRO_RUN_CAP, currentMomentum - (0.25 * deltaTime * 60))
            end
            physicsMotor.Velocity = thumbstickInput * currentMomentum
        end
        
        -- Smooth turning vector orientation mapping
        rootPart.CFrame = CFrame.new(rootPart.Position, rootPart.Position + Vector3.new(thumbstickInput.X, 0, thumbstickInput.Z))
    else
        -- Slippery friction decay coefficient when letting go of the stick
        currentMomentum = math.max(0, currentMomentum * (NATURAL_FRICTION ^ (deltaTime * 60)))
        if currentMomentum <= 1 then
            physicsMotor.Velocity = Vector3.new(0, 0, 0)
        else
            local driftHeading = rootPart.CFrame.LookVector
            physicsMotor.Velocity = Vector3.new(driftHeading.X * currentMomentum, 0, driftHeading.Z * currentMomentum)
        end
    end
end)

-- BUTTON CONNECTIONS USING EXECUTOR-SAFE MOBILE TOUCH TRIGGERS
SpindashBtn.InputBegan:Connect(function(input)
    if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1 then
        local flatVelocity = Vector3.new(rootPart.AssemblyLinearVelocity.X, 0, rootPart.AssemblyLinearVelocity.Z).Magnitude
        
        if flatVelocity > 10 and humanoid.FloorMaterial ~= Enum.Material.Air then
            -- MID-RUN BALL ROLL
            isRollingBall = not isRollingBall
            if isRollingBall then
                currentMomentum = flatVelocity
                spawnSpinBall()
                SpindashBtn.BackgroundColor3 = Color3.fromRGB(0, 220, 220)
            else
                deleteSpinBall()
                SpindashBtn.BackgroundColor3 = Color3.fromRGB(0, 110, 220)
            end
        elseif flatVelocity <= 10 then
            -- STATIC CHARGE SPINDASH: Locks position immediately
            isSpindashing = true
            spawnSpinBall() 
            SpindashBtn.BackgroundColor3 = Color3.fromRGB(230, 40, 40)
        end
    end
end)

SpindashBtn.InputEnded:Connect(function(input)
    if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1 then
        if not isSpindashing then return end
        isSpindashing = false
        SpindashBtn.BackgroundColor3 = Color3.fromRGB(0, 110, 220)
        
        -- EXPLOSIVE PROPULSION BLAST: Forced high-velocity forward vector release
        currentMomentum = SPINDASH_LAUNCH
        local launchHeading = rootPart.CFrame.LookVector
        rootPart.AssemblyLinearVelocity = Vector3.new(launchHeading.X * currentMomentum, rootPart.AssemblyLinearVelocity.Y, launchHeading.Z * currentMomentum)
        physicsMotor.Velocity = Vector3.new(launchHeading.X * currentMomentum, 0, launchHeading.Z * currentMomentum)
        
        isRollingBall = true 
    end
end)

-- Cleanup UI sequences upon character death
humanoid.Died:Connect(function()
    deleteSpinBall()
    if physicsMotor then physicsMotor:Destroy() end
    ScreenGui:Destroy()
end)

Features & Information

this is a cool sonic script i made featchures:sonic movement and spindash
#sonic#movement#cl*ic#game#cool

Community Comments (1)

HypersoniclikerHypersoniclikerSep 7, 2026

I am mad slow can you make it faster

More Scripts for Universal Scripts