BOOMBOX SKID
Key RequiredUniversal

BOOMBOX SKID

Universal Scriptts_pmots_pmo 292 views 14.8K executesUpdated Aug 22, 2026
Unrated
00
Lua Script•513 lines (15564 chars)
-- Services
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local ContentProvider = game:GetService("ContentProvider")
local player = Players.LocalPlayer

-- Configuration
local MESH_ID = "rbxassetid://1066606415"
local TEXTURE_ID = "" -- optional: rbxassetid://... if the mesh has a matching texture
local MESH_SCALE = Vector3.new(0.016, 0.016, 0.016) -- adjust if the mesh looks too big/small
local BASE_OFFSET = CFrame.new(0, 0, 1.0) -- forward/back + left/right placement, Y height is adjusted separately below

local SPEED_STEP = 0.1
local SPEED_MIN, SPEED_MAX = 0.5, 2
local PITCH_STEP = 0.1
local PITCH_MIN, PITCH_MAX = 0.5, 2

local COLOR_SWATCHES = {
	Color3.fromRGB(40, 40, 40),
	Color3.fromRGB(200, 200, 200),
	Color3.fromRGB(180, 40, 40),
	Color3.fromRGB(40, 90, 180),
	Color3.fromRGB(60, 150, 70),
	Color3.fromRGB(230, 200, 40),
}

-- State (shared between GUI and boombox)
local boomboxPart = nil
local boomboxWeld = nil
local currentUpperTorso = nil
local sound = nil
local pitchEffect = nil
local currentSoundId = ""
local soundLoadedConn = nil
local statusLabelRef = nil -- set once the GUI exists, used for error feedback
local yOffsetLabelRef = nil
local speedLabelRef = nil
local pitchLabelRef = nil
local yOffset = 0 -- studs, adjustable from the GUI, persists across respawns/recreation
local currentSpeed = 1
local currentPitch = 1
local currentColor = COLOR_SWATCHES[1]

-- Re-welds the boombox at its current upperTorso + yOffset. WeldConstraint
-- locks in the relative CFrame at creation time, so to move it we have to
-- set the CFrame first, then rebuild the weld against the new position.
local function repositionBoombox()
	if not boomboxPart or not currentUpperTorso then return end

	if boomboxWeld then
		boomboxWeld:Destroy()
		boomboxWeld = nil
	end

	boomboxPart.CFrame = currentUpperTorso.CFrame * BASE_OFFSET * CFrame.new(0, yOffset, 0)

	local weld = Instance.new("WeldConstraint")
	weld.Part0 = currentUpperTorso
	weld.Part1 = boomboxPart
	weld.Parent = boomboxPart
	boomboxWeld = weld
end

local function applySpeed()
	if sound then
		sound.PlaybackSpeed = currentSpeed
	end
end

local function applyPitch()
	if pitchEffect then
		pitchEffect.Octave = currentPitch
	end
end

local function applyColor()
	if boomboxPart then
		boomboxPart.Color = currentColor
	end
end

-- Removes the boombox and stops the sound. It comes back next respawn
-- (createBoombox runs again on CharacterAdded).
local function deleteBoombox()
	if boomboxPart then
		boomboxPart:Destroy()
	end
	boomboxPart = nil
	boomboxWeld = nil
	sound = nil
	pitchEffect = nil
end

-- Pull a numeric asset id out of either a bare number or a pasted
-- roblox.com/library/123456/Song-Name style link
local function parseSoundId(text)
	local digits = text:match("(%d+)")
	return digits and tonumber(digits) or nil
end

-- Plays soundId on the given Sound instance, handling both the "already
-- cached, fires before we can connect" case and genuine load failures
-- (e.g. the asset isn't permitted for use in this experience).
local function playSoundId(soundInstance, soundId, onResult)
	if soundLoadedConn then
		soundLoadedConn:Disconnect()
		soundLoadedConn = nil
	end

	soundInstance:Stop()
	soundInstance.SoundId = soundId

	local function attemptPlay()
		local ok, err = pcall(function()
			soundInstance:Play()
		end)
		if onResult then
			onResult(ok, err)
		end
	end

	if soundInstance.IsLoaded then
		-- Already loaded (common with cached/short sounds) - the Loaded
		-- event will never fire, so play immediately instead of waiting.
		attemptPlay()
		return
	end

	local loaded = false
	soundLoadedConn = soundInstance.Loaded:Connect(function()
		loaded = true
		attemptPlay()
		if soundLoadedConn then
			soundLoadedConn:Disconnect()
			soundLoadedConn = nil
		end
	end)

	-- Fallback in case Loaded never fires (bad id, moderation block, etc.)
	task.delay(2, function()
		if not loaded and soundInstance and soundInstance.Parent then
			attemptPlay()
		end
	end)
end

-- Small helper to build a plain grey button, matching the rough flat style
local function makeGreyButton(name, size, position, text, parent)
	local btn = Instance.new("TextButton")
	btn.Name = name
	btn.Size = size
	btn.Position = position
	btn.BackgroundColor3 = Color3.fromRGB(200, 200, 200)
	btn.BorderSizePixel = 1
	btn.BorderColor3 = Color3.fromRGB(0, 0, 0)
	btn.Text = text
	btn.TextColor3 = Color3.fromRGB(0, 0, 0)
	btn.Font = Enum.Font.SourceSans
	btn.TextSize = 14
	btn.Parent = parent
	return btn
end

-- Small helper to build a +/- adjust row: label on the left, minus/plus on the right
local function makeAdjustRow(y, labelText, parent)
	local label = Instance.new("TextLabel")
	label.Size = UDim2.new(0, 80, 0, 26)
	label.Position = UDim2.new(0, 0, 0, y)
	label.BackgroundColor3 = Color3.fromRGB(200, 200, 200)
	label.BorderSizePixel = 1
	label.BorderColor3 = Color3.fromRGB(0, 0, 0)
	label.Text = labelText
	label.TextColor3 = Color3.fromRGB(0, 0, 0)
	label.Font = Enum.Font.SourceSans
	label.TextSize = 14
	label.Parent = parent

	local minusBtn = makeGreyButton("Minus", UDim2.new(0, 50, 0, 26), UDim2.new(0, 85, 0, y), "-", parent)
	local plusBtn = makeGreyButton("Plus", UDim2.new(0, 50, 0, 26), UDim2.new(0, 140, 0, y), "+", parent)

	return label, minusBtn, plusBtn
end

-- Create the draggable GUI
local function createGUI()
	local playerGui = player:FindFirstChild("PlayerGui")
	if not playerGui then return end

	local existing = playerGui:FindFirstChild("BoomboxGui")
	if existing then
		existing:Destroy()
	end

	local screenGui = Instance.new("ScreenGui")
	screenGui.Name = "BoomboxGui"
	screenGui.ResetOnSpawn = false
	screenGui.Parent = playerGui

	local frame = Instance.new("Frame")
	frame.Name = "MainFrame"
	frame.Size = UDim2.new(0, 200, 0, 250)
	frame.Position = UDim2.new(0, 20, 0.5, -125)
	frame.BackgroundColor3 = Color3.fromRGB(150, 150, 150)
	frame.BorderSizePixel = 1
	frame.BorderColor3 = Color3.fromRGB(0, 0, 0)
	frame.Parent = screenGui

	local title = Instance.new("TextLabel")
	title.Name = "Title"
	title.Size = UDim2.new(1, 0, 0, 20)
	title.BackgroundColor3 = Color3.fromRGB(150, 150, 150)
	title.BorderSizePixel = 1
	title.BorderColor3 = Color3.fromRGB(0, 0, 0)
	title.Text = "Boombox"
	title.TextColor3 = Color3.fromRGB(0, 0, 0)
	title.Font = Enum.Font.SourceSans
	title.TextSize = 14
	title.Active = true
	title.Parent = frame

	-- Tabs
	local mainTabButton = makeGreyButton("MainTabButton", UDim2.new(0, 95, 0, 22), UDim2.new(0, 5, 0, 22), "Main", frame)
	local colorTabButton = makeGreyButton("ColorTabButton", UDim2.new(0, 90, 0, 22), UDim2.new(0, 100, 0, 22), "Color", frame)

	local mainPage = Instance.new("Frame")
	mainPage.Name = "MainPage"
	mainPage.Size = UDim2.new(1, -10, 0, 172)
	mainPage.Position = UDim2.new(0, 5, 0, 46)
	mainPage.BackgroundTransparency = 1
	mainPage.Visible = true
	mainPage.Parent = frame

	local colorPage = Instance.new("Frame")
	colorPage.Name = "ColorPage"
	colorPage.Size = UDim2.new(1, -10, 0, 172)
	colorPage.Position = UDim2.new(0, 5, 0, 46)
	colorPage.BackgroundTransparency = 1
	colorPage.Visible = false
	colorPage.Parent = frame

	local function showTab(tab)
		mainPage.Visible = (tab == "main")
		colorPage.Visible = (tab == "color")
	end

	mainTabButton.MouseButton1Click:Connect(function()
		showTab("main")
	end)
	colorTabButton.MouseButton1Click:Connect(function()
		showTab("color")
	end)

	-- ===== Main page =====
	local textBox = Instance.new("TextBox")
	textBox.Name = "SongIdInput"
	textBox.Size = UDim2.new(1, 0, 0, 26)
	textBox.Position = UDim2.new(0, 0, 0, 0)
	textBox.BackgroundColor3 = Color3.fromRGB(200, 200, 200)
	textBox.BorderSizePixel = 1
	textBox.BorderColor3 = Color3.fromRGB(0, 0, 0)
	textBox.PlaceholderText = "Enter Sound ID or link..."
	textBox.Text = ""
	textBox.TextColor3 = Color3.fromRGB(0, 0, 0)
	textBox.Font = Enum.Font.SourceSans
	textBox.TextSize = 14
	textBox.ClearTextOnFocus = true
	textBox.Parent = mainPage

	local button = makeGreyButton("EnterButton", UDim2.new(1, 0, 0, 26), UDim2.new(0, 0, 0, 28), "Enter", mainPage)

	local yLabel, yDownButton, yUpButton = makeAdjustRow(58, "Y: 0.0", mainPage)
	yOffsetLabelRef = yLabel

	local speedLabel, speedDownButton, speedUpButton = makeAdjustRow(88, "Speed: 1.0", mainPage)
	speedLabelRef = speedLabel

	local pitchLabel, pitchDownButton, pitchUpButton = makeAdjustRow(118, "Pitch: 1.0", mainPage)
	pitchLabelRef = pitchLabel

	local deleteButton = makeGreyButton("DeleteButton", UDim2.new(1, 0, 0, 26), UDim2.new(0, 0, 0, 148), "Delete Boombox", mainPage)

	-- Small status line so load/permission errors are visible, not silent
	local status = Instance.new("TextLabel")
	status.Name = "Status"
	status.Size = UDim2.new(1, 0, 0, 16)
	status.Position = UDim2.new(0, 0, 1, -16)
	status.BackgroundTransparency = 1
	status.Text = ""
	status.TextColor3 = Color3.fromRGB(30, 30, 30)
	status.Font = Enum.Font.SourceSans
	status.TextSize = 12
	status.TextXAlignment = Enum.TextXAlignment.Left
	status.Parent = frame
	statusLabelRef = status

	local Y_STEP = 0.1

	local function nudgeY(delta)
		yOffset = yOffset + delta
		yLabel.Text = "Y: " .. string.format("%.1f", yOffset)
		repositionBoombox()
	end

	yDownButton.MouseButton1Click:Connect(function() nudgeY(-Y_STEP) end)
	yUpButton.MouseButton1Click:Connect(function() nudgeY(Y_STEP) end)

	local function nudgeSpeed(delta)
		currentSpeed = math.clamp(currentSpeed + delta, SPEED_MIN, SPEED_MAX)
		speedLabel.Text = "Speed: " .. string.format("%.1f", currentSpeed)
		applySpeed()
	end

	speedDownButton.MouseButton1Click:Connect(function() nudgeSpeed(-SPEED_STEP) end)
	speedUpButton.MouseButton1Click:Connect(function() nudgeSpeed(SPEED_STEP) end)

	local function nudgePitch(delta)
		currentPitch = math.clamp(currentPitch + delta, PITCH_MIN, PITCH_MAX)
		pitchLabel.Text = "Pitch: " .. string.format("%.1f", currentPitch)
		applyPitch()
	end

	pitchDownButton.MouseButton1Click:Connect(function() nudgePitch(-PITCH_STEP) end)
	pitchUpButton.MouseButton1Click:Connect(function() nudgePitch(PITCH_STEP) end)

	deleteButton.MouseButton1Click:Connect(function()
		deleteBoombox()
		status.Text = "Boombox deleted"
		status.TextColor3 = Color3.fromRGB(30, 30, 30)
	end)

	-- ===== Color page (very simple - just swatch buttons) =====
	local swatchSize = 40
	local perRow = 4
	local gap = 5
	for i, color in ipairs(COLOR_SWATCHES) do
		local row = math.floor((i - 1) / perRow)
		local col = (i - 1) % perRow
		local swatch = Instance.new("TextButton")
		swatch.Name = "Swatch" .. i
		swatch.Size = UDim2.new(0, swatchSize, 0, swatchSize)
		swatch.Position = UDim2.new(0, col * (swatchSize + gap), 0, row * (swatchSize + gap))
		swatch.BackgroundColor3 = color
		swatch.BorderSizePixel = 1
		swatch.BorderColor3 = Color3.fromRGB(0, 0, 0)
		swatch.Text = ""
		swatch.Parent = colorPage

		swatch.MouseButton1Click:Connect(function()
			currentColor = color
			applyColor()
		end)
	end

	-- Custom dragging via the title bar
	local dragging = false
	local dragStart = nil
	local startPos = nil

	title.InputBegan:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
			dragging = true
			dragStart = input.Position
			startPos = frame.Position
		end
	end)

	title.InputEnded:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
			dragging = false
		end
	end)

	UserInputService.InputChanged:Connect(function(input)
		if dragging and (input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch) then
			local delta = input.Position - dragStart
			frame.Position = UDim2.new(startPos.X.Scale, startPos.X.Offset + delta.X, startPos.Y.Scale, startPos.Y.Offset + delta.Y)
		end
	end)

	local submitting = false

	local function submitSong()
		if submitting then return end
		submitting = true

		local soundIdNum = parseSoundId(textBox.Text)
		if soundIdNum then
			currentSoundId = "rbxassetid://" .. tostring(soundIdNum)

			-- If the boombox exists, update+play immediately. If it doesn't
			-- exist yet (character not spawned, or deleted), currentSoundId
			-- is still saved and will be picked up next time it's created.
			if sound then
				playSoundId(sound, currentSoundId, function(ok, err)
					if ok then
						status.Text = "Playing " .. tostring(soundIdNum)
						status.TextColor3 = Color3.fromRGB(20, 120, 40)
					else
						status.Text = "Can't play this id (check it's a valid, permitted audio asset)"
						status.TextColor3 = Color3.fromRGB(150, 30, 30)
						warn("[Boombox] Play failed for", currentSoundId, err)
					end
				end)
			end

			button.Text = "Enter"
		else
			status.Text = "Enter a numeric Sound ID"
			status.TextColor3 = Color3.fromRGB(150, 30, 30)
			button.Text = "Invalid ID!"
		end

		task.delay(1.5, function()
			button.Text = "Enter"
			submitting = false
		end)
	end

	button.MouseButton1Click:Connect(submitSong)

	textBox.FocusLost:Connect(function(enterPressed)
		if enterPressed then
			submitSong()
		end
	end)
end

-- Create the boombox on the character
local function createBoombox(character)
	if boomboxPart then
		boomboxPart:Destroy()
		boomboxPart = nil
	end

	local upperTorso = character:FindFirstChild("UpperTorso") or character:FindFirstChild("Torso")
	if not upperTorso then
		warn("[Boombox] Could not find Torso/UpperTorso on the player's character.")
		return
	end

	-- Plain Part + SpecialMesh (FileMesh) - this is the combo you confirmed
	-- actually renders for this mesh id. MeshPart.MeshId is pickier about
	-- what it'll accept and can silently show nothing for the same asset.
	local part = Instance.new("Part")
	part.Name = "ClientBoombox"
	part.Size = Vector3.new(1, 1, 1)
	part.Transparency = 0
	part.CanCollide = false
	part.CanQuery = false
	part.Massless = true
	part.Color = currentColor

	local mesh = Instance.new("SpecialMesh")
	mesh.MeshType = Enum.MeshType.FileMesh
	mesh.MeshId = MESH_ID
	if TEXTURE_ID ~= "" then
		mesh.TextureId = TEXTURE_ID
	end
	mesh.Scale = MESH_SCALE
	mesh.Parent = part

	part.Parent = character

	-- Kick off loading the mesh/texture as soon as it's placed
	pcall(function()
		ContentProvider:PreloadAsync({ part })
	end)

	boomboxPart = part
	currentUpperTorso = upperTorso
	repositionBoombox() -- sets CFrame + weld using the current yOffset

	sound = Instance.new("Sound")
	sound.Volume = 1
	sound.Looped = true
	sound.RollOffMaxDistance = 60
	sound.PlaybackSpeed = currentSpeed
	sound.Parent = part

	pitchEffect = Instance.new("PitchShiftSoundEffect")
	pitchEffect.Octave = currentPitch
	pitchEffect.Parent = sound

	if currentSoundId ~= "" then
		playSoundId(sound, currentSoundId, function(ok, err)
			if statusLabelRef then
				if ok then
					statusLabelRef.Text = "Playing"
					statusLabelRef.TextColor3 = Color3.fromRGB(20, 120, 40)
				else
					statusLabelRef.Text = "Can't play this id (check it's a valid, permitted audio asset)"
					statusLabelRef.TextColor3 = Color3.fromRGB(150, 30, 30)
					warn("[Boombox] Play failed for", currentSoundId, err)
				end
			end
		end)
	end
end

-- Handle character spawns
local function onCharacterAdded(character)
	character:WaitForChild("HumanoidRootPart", 5)
	createBoombox(character)
end

-- Create the GUI (persists across respawns)
createGUI()

-- Handle current character and future respawns
if player.Character then
	onCharacterAdded(player.Character)
end
player.CharacterAdded:Connect(onCharacterAdded)

Features & Information

works basically in any game

Community Comments (0)

No comments yet for this script.

More Scripts for Universal Scripts