Fnf recreation
Key RequiredUniversal

Fnf recreation

Universal ScriptYzn_lolYzn_lol 1.7K views 1.8K executesUpdated Jan 22, 2026
Unrated
00
Lua Script•623 lines (15979 chars)
-- FNF Recreation
--made by yourstraight_idk

local Players = game:GetService("Players")
local UIS = game:GetService("UserInputService")
local RunService = game:GetService("RunService")

local player = Players.LocalPlayer

-- DEFAULT SETTINGS
local NOTE_SPEED = 380
local NOTE_COLOR = Color3.fromRGB(255,0,0)

local HIT_WINDOW = 55
local NOTE_SIZE = 48
local BUTTON_SIZE = 60

-- RANDOM DELAY SETTINGS
local MIN_DELAY = 0.4
local MAX_DELAY = 1.5
local MULTI_NOTE_DELAY = 0.18

-- GAME STATE
local score = 0
local combo = 0
local misses = 0
local notes = {}
local playing = false
local paused = false
local mode = "Easy"
local resumeTimer = 0
local isResuming = false

local spawnTimer = 0
local spawnDelay = math.random(MIN_DELAY * 100, MAX_DELAY * 100) / 100

local gameTime = 120
local timerRunning = false

local notesHit = 0
local notesSpawned = 0

-- HIT SOUND
local hitSoundId = ""
local hitSound = Instance.new("Sound")
hitSound.Parent = player:WaitForChild("PlayerGui")
hitSound.Volume = 1

-- GUI
local gui = Instance.new("ScreenGui")
gui.Parent = player.PlayerGui
gui.ResetOnSpawn = false

-- =========================
-- RESULTS UI
-- =========================
local resultFrame = Instance.new("Frame", gui)
resultFrame.Size = UDim2.fromScale(0.4,0.5)
resultFrame.Position = UDim2.fromScale(0.3,0.25)
resultFrame.BackgroundColor3 = Color3.fromRGB(20,20,20)
resultFrame.Visible = false

Instance.new("UICorner", resultFrame).CornerRadius = UDim.new(0,20)

local resultTitle = Instance.new("TextLabel", resultFrame)
resultTitle.Size = UDim2.fromScale(1,0.2)
resultTitle.Position = UDim2.fromScale(0,0)
resultTitle.BackgroundTransparency = 1
resultTitle.Text = "RESULTS"
resultTitle.TextScaled = true
resultTitle.TextColor3 = Color3.new(1,1,1)

local resultText = Instance.new("TextLabel", resultFrame)
resultText.Size = UDim2.fromScale(1,0.6)
resultText.Position = UDim2.fromScale(0,0.2)
resultText.BackgroundTransparency = 1
resultText.TextScaled = true
resultText.TextColor3 = Color3.new(1,1,1)
resultText.Text = ""

local replayBtn = Instance.new("TextButton", resultFrame)
replayBtn.Size = UDim2.fromScale(0.5,0.15)
replayBtn.Position = UDim2.fromScale(0.25,0.8)
replayBtn.Text = "REPLAY"
replayBtn.TextScaled = true
replayBtn.BackgroundColor3 = Color3.fromRGB(120,120,120)

-- =========================
-- MENU SCREEN
-- =========================
local menu = Instance.new("Frame", gui)
menu.Size = UDim2.fromScale(0.5,0.6)
menu.Position = UDim2.fromScale(0.25,0.2)
menu.BackgroundColor3 = Color3.fromRGB(20,20,20)
menu.Visible = false

Instance.new("UICorner", menu).CornerRadius = UDim.new(0,20)

local menuTitle = Instance.new("TextLabel", menu)
menuTitle.Size = UDim2.fromScale(1,0.25)
menuTitle.Position = UDim2.fromScale(0,0)
menuTitle.BackgroundTransparency = 1
menuTitle.Text = "RHYTHM GAME"
menuTitle.TextScaled = true
menuTitle.TextColor3 = Color3.new(1,1,1)

local playBtn = Instance.new("TextButton", menu)
playBtn.Size = UDim2.fromScale(0.6,0.15)
playBtn.Position = UDim2.fromScale(0.2,0.35)
playBtn.Text = "PLAY"
playBtn.TextScaled = true
playBtn.BackgroundColor3 = Color3.fromRGB(120,120,120)

local settingsBtn = Instance.new("TextButton", menu)
settingsBtn.Size = UDim2.fromScale(0.6,0.15)
settingsBtn.Position = UDim2.fromScale(0.2,0.55)
settingsBtn.Text = "SETTINGS"
settingsBtn.TextScaled = true
settingsBtn.BackgroundColor3 = Color3.fromRGB(120,120,120)

local menuBackBtn = Instance.new("TextButton", menu)
menuBackBtn.Size = UDim2.fromScale(0.6,0.15)
menuBackBtn.Position = UDim2.fromScale(0.2,0.75)
menuBackBtn.Text = "BACK TO GAME"
menuBackBtn.TextScaled = true
menuBackBtn.BackgroundColor3 = Color3.fromRGB(120,120,120)

-- =========================
-- SETTINGS SCREEN
-- =========================
local settings = Instance.new("Frame", gui)
settings.Size = UDim2.fromScale(0.5,0.6)
settings.Position = UDim2.fromScale(0.25,0.2)
settings.BackgroundColor3 = Color3.fromRGB(20,20,20)
settings.Visible = false

Instance.new("UICorner", settings).CornerRadius = UDim.new(0,20)

local settingsTitle = Instance.new("TextLabel", settings)
settingsTitle.Size = UDim2.fromScale(1,0.25)
settingsTitle.Position = UDim2.fromScale(0,0)
settingsTitle.BackgroundTransparency = 1
settingsTitle.Text = "SETTINGS"
settingsTitle.TextScaled = true
settingsTitle.TextColor3 = Color3.new(1,1,1)

-- Note Speed
local speedLabel = Instance.new("TextLabel", settings)
speedLabel.Size = UDim2.fromScale(0.9,0.12)
speedLabel.Position = UDim2.fromScale(0.05,0.3)
speedLabel.BackgroundTransparency = 1
speedLabel.Text = "Note Speed: "..NOTE_SPEED
speedLabel.TextScaled = true
speedLabel.TextColor3 = Color3.new(1,1,1)

local speedUp = Instance.new("TextButton", settings)
speedUp.Size = UDim2.fromScale(0.2,0.1)
speedUp.Position = UDim2.fromScale(0.7,0.42)
speedUp.Text = "+"
speedUp.TextScaled = true
speedUp.BackgroundColor3 = Color3.fromRGB(120,120,120)

local speedDown = Instance.new("TextButton", settings)
speedDown.Size = UDim2.fromScale(0.2,0.1)
speedDown.Position = UDim2.fromScale(0.1,0.42)
speedDown.Text = "-"
speedDown.TextScaled = true
speedDown.BackgroundColor3 = Color3.fromRGB(120,120,120)

speedUp.MouseButton1Click:Connect(function()
	NOTE_SPEED += 20
	speedLabel.Text = "Note Speed: "..NOTE_SPEED
end)

speedDown.MouseButton1Click:Connect(function()
	if NOTE_SPEED > 100 then
		NOTE_SPEED -= 20
		speedLabel.Text = "Note Speed: "..NOTE_SPEED
	end
end)

-- Note Color Buttons
local colorLabel = Instance.new("TextLabel", settings)
colorLabel.Size = UDim2.fromScale(0.9,0.12)
colorLabel.Position = UDim2.fromScale(0.05,0.55)
colorLabel.BackgroundTransparency = 1
colorLabel.Text = "Note Color"
colorLabel.TextScaled = true
colorLabel.TextColor3 = Color3.new(1,1,1)

local colors = {
	{Color3.fromRGB(255,0,0), "RED"},
	{Color3.fromRGB(0,255,0), "GREEN"},
	{Color3.fromRGB(0,0,255), "BLUE"},
	{Color3.fromRGB(255,255,0), "YELLOW"},
	{Color3.fromRGB(255,0,255), "PINK"},
	{Color3.fromRGB(0,255,255), "CYAN"},
	{Color3.fromRGB(255,128,0), "ORANGE"},
	{Color3.fromRGB(128,0,255), "PURPLE"}
}

for i, v in ipairs(colors) do
	local btn = Instance.new("TextButton", settings)
	btn.Size = UDim2.fromScale(0.2,0.1)
	btn.Position = UDim2.fromScale(0.1 + ((i-1)%4)*0.3, 0.67 + math.floor((i-1)/4)*0.12)
	btn.Text = v[2]
	btn.TextScaled = true
	btn.BackgroundColor3 = v[1]

	btn.MouseButton1Click:Connect(function()
		NOTE_COLOR = v[1]
	end)
end

-- HIT SOUND SETTING
local soundLabel = Instance.new("TextLabel", settings)
soundLabel.Size = UDim2.fromScale(0.9,0.12)
soundLabel.Position = UDim2.fromScale(0.05,0.82)
soundLabel.BackgroundTransparency = 1
soundLabel.Text = "Hit Sound ID:"
soundLabel.TextScaled = true
soundLabel.TextColor3 = Color3.new(1,1,1)

local soundBox = Instance.new("TextBox", settings)
soundBox.Size = UDim2.fromScale(0.6,0.12)
soundBox.Position = UDim2.fromScale(0.05,0.92)
soundBox.PlaceholderText = "Paste Sound ID here"
soundBox.TextScaled = true
soundBox.TextColor3 = Color3.new(1,1,1)
soundBox.BackgroundColor3 = Color3.fromRGB(60,60,60)

local soundApply = Instance.new("TextButton", settings)
soundApply.Size = UDim2.fromScale(0.3,0.12)
soundApply.Position = UDim2.fromScale(0.68,0.92)
soundApply.Text = "Apply"
soundApply.TextScaled = true
soundApply.BackgroundColor3 = Color3.fromRGB(120,120,120)

soundApply.MouseButton1Click:Connect(function()
	local id = soundBox.Text
	if id ~= "" then
		hitSoundId = id
		hitSound.SoundId = "rbxassetid://" .. hitSoundId
	end
end)

local backToMenu = Instance.new("TextButton", settings)
backToMenu.Size = UDim2.fromScale(0.6,0.15)
backToMenu.Position = UDim2.fromScale(0.2,0.85)
backToMenu.Text = "BACK TO MENU"
backToMenu.TextScaled = true
backToMenu.BackgroundColor3 = Color3.fromRGB(120,120,120)

backToMenu.MouseButton1Click:Connect(function()
	settings.Visible = false
	menu.Visible = true
end)

-- =========================
-- GAME SCREEN
-- =========================
local main = Instance.new("Frame", gui)
main.Size = UDim2.fromScale(0.45, 0.7)
main.Position = UDim2.fromScale(0.275, 0.15)
main.BackgroundColor3 = Color3.fromRGB(20,20,20)
main.Visible = false

Instance.new("UICorner", main).CornerRadius = UDim.new(0,20)

-- DRAGGABLE TOGGLE BUTTON
local toggle = Instance.new("TextButton", gui)
toggle.Size = UDim2.fromOffset(64,64)
toggle.Position = UDim2.fromScale(0.02,0.45)
toggle.Text = "🎵"
toggle.TextScaled = true
toggle.BackgroundColor3 = Color3.fromRGB(255,90,90)
toggle.Active = true
toggle.Draggable = true

Instance.new("UICorner", toggle).CornerRadius = UDim.new(1,0)
Instance.new("UIAspectRatioConstraint", toggle).AspectRatio = 1

-- TOGGLE BUTTON (ONLY HIDES/SHOWS GAME UI)
toggle.MouseButton1Click:Connect(function()
	main.Visible = not main.Visible
	menu.Visible = false
	settings.Visible = false
	resultFrame.Visible = false

	-- START GAME IF SHOW BUTTON PRESS
	if main.Visible and not playing then
		playing = true
		timerRunning = true
	end
end)

-- INFO
local info = Instance.new("TextLabel", main)
info.Size = UDim2.fromScale(1,0.08)
info.BackgroundTransparency = 1
info.TextColor3 = Color3.new(1,1,1)
info.TextScaled = true

-- MODE LABEL
local modeLabel = Instance.new("TextLabel", main)
modeLabel.Size = UDim2.fromScale(1,0.08)
modeLabel.Position = UDim2.fromScale(0,0.08)
modeLabel.BackgroundTransparency = 1
modeLabel.TextColor3 = Color3.new(1,1,1)
modeLabel.TextScaled = true
modeLabel.Text = "Mode: Easy"

-- RESET FUNCTION (CALLED WHEN MODE CHANGES)
local function resetGame()
	score = 0
	combo = 0
	misses = 0
	notesHit = 0
	notesSpawned = 0
	gameTime = 120
	timerRunning = true
	paused = false
	isResuming = false

	for _, v in ipairs(notes) do
		v.obj:Destroy()
	end
	notes = {}
end

-- START GAME FUNCTION
local function startGame()
	playing = true
	timerRunning = true
	paused = false
	isResuming = false
end

-- PAUSE BUTTON
local pauseBtn = Instance.new("TextButton", main)
pauseBtn.Size = UDim2.fromScale(0.2,0.08)
pauseBtn.Position = UDim2.fromScale(0.4,0.18)
pauseBtn.Text = "Pause"
pauseBtn.TextScaled = true
pauseBtn.BackgroundColor3 = Color3.fromRGB(120,120,120)

pauseBtn.MouseButton1Click:Connect(function()
	if isResuming then return end

	if paused then
		isResuming = true
		resumeTimer = 4
		pauseBtn.Text = "Resuming..."
	else
		paused = true
		pauseBtn.Text = "Resume"
		for _, v in ipairs(notes) do
			v.obj:Destroy()
		end
		notes = {}
	end
end)

-- MODE BUTTONS
local function createModeButton(text, pos)
	local btn = Instance.new("TextButton", main)
	btn.Size = UDim2.fromScale(0.25,0.08)
	btn.Position = pos
	btn.Text = text
	btn.TextScaled = true
	btn.BackgroundColor3 = Color3.fromRGB(80,80,80)

	btn.MouseButton1Click:Connect(function()
		mode = text
		modeLabel.Text = "Mode: "..text
		resetGame()
	end)
	return btn
end

createModeButton("Easy", UDim2.fromScale(0.05,0.26))
createModeButton("Medium", UDim2.fromScale(0.375,0.26))
createModeButton("Hard", UDim2.fromScale(0.7,0.26))
createModeButton("Extreme", UDim2.fromScale(0.375,0.34))

-- MENU BUTTON
local menuBtn = Instance.new("TextButton", main)
menuBtn.Size = UDim2.fromScale(0.25,0.08)
menuBtn.Position = UDim2.fromScale(0.7,0.34)
menuBtn.Text = "MENU"
menuBtn.TextScaled = true
menuBtn.BackgroundColor3 = Color3.fromRGB(120,120,120)

menuBtn.MouseButton1Click:Connect(function()
	menu.Visible = true
	main.Visible = false
	settings.Visible = false
	resultFrame.Visible = false
end)

-- HIT LINE
local hitLine = Instance.new("Frame", main)
hitLine.Size = UDim2.fromScale(1,0.02)
hitLine.Position = UDim2.fromScale(0,0.75)
hitLine.BackgroundColor3 = Color3.new(1,1,1)

-- LANES + WASD BUTTONS
local lanes = {}
local keys = {"A","S","W","D"}

for i = 1,4 do
	local lane = Instance.new("Frame", main)
	lane.Size = UDim2.fromScale(0.25,1)
	lane.Position = UDim2.fromScale((i-1)*0.25,0)
	lane.BackgroundTransparency = 1
	lanes[i] = lane

	local btn = Instance.new("TextButton", main)
	btn.Size = UDim2.fromOffset(BUTTON_SIZE,BUTTON_SIZE)
	btn.Position = UDim2.fromScale((i-1)*0.25 + 0.125,0.9)
	btn.AnchorPoint = Vector2.new(0.5,0.5)
	btn.Text = keys[i]
	btn.TextScaled = true
	btn.TextColor3 = Color3.new(1,1,1)
	btn.BackgroundColor3 = Color3.fromRGB(60,60,60)

	Instance.new("UICorner", btn).CornerRadius = UDim.new(1,0)
	Instance.new("UIAspectRatioConstraint", btn).AspectRatio = 1

	btn.MouseButton1Click:Connect(function()
		pressLane(i)
	end)
end

-- SPAWN NOTE AT A HEIGHT
function spawnNote(laneIndex)
	local note = Instance.new("Frame")
	note.Size = UDim2.fromOffset(NOTE_SIZE,NOTE_SIZE)

	-- SPAWN height
	note.Position = UDim2.fromScale(0.5,-1)
	note.AnchorPoint = Vector2.new(0.5,0)

	note.BackgroundColor3 = NOTE_COLOR
	note.Parent = lanes[laneIndex]

	Instance.new("UICorner", note).CornerRadius = UDim.new(1,0)
	Instance.new("UIAspectRatioConstraint", note).AspectRatio = 1

	table.insert(notes,{
		obj = note,
		lane = laneIndex
	})

	notesSpawned += 1
end

-- SPAWN MULTIPLE NOTES WITH DELAY BETWEEN EACH NOTE
function spawnMultipleNotes()
	local amount = 1
	if mode == "Easy" then
		amount = 1
	elseif mode == "Medium" then
		amount = 2
	elseif mode == "Hard" then
		amount = 3
	elseif mode == "Extreme" then
		amount = 4
	end

	local usedLanes = {}

	for i = 1, amount do
		local lane = math.random(1,4)
		while usedLanes[lane] do
			lane = math.random(1,4)
		end
		usedLanes[lane] = true

		spawnNote(lane)

		if i < amount then
			wait(MULTI_NOTE_DELAY)
		end
	end
end

-- HIT CHECK (closest note)
function pressLane(lane)
	if paused then return end

	local closestNote = nil
	local closestDist = math.huge
	local closestIndex = nil

	for i, data in ipairs(notes) do
		if data.lane == lane then
			local dy = math.abs(data.obj.AbsolutePosition.Y - hitLine.AbsolutePosition.Y)

			if dy < closestDist then
				closestDist = dy
				closestNote = data.obj
				closestIndex = i
			end
		end
	end

	if closestNote and closestDist <= HIT_WINDOW then
		closestNote:Destroy()
		table.remove(notes, closestIndex)
		score += 100
		combo += 1
		notesHit += 1

		-- PLAY HIT SOUND
		if hitSoundId ~= "" then
			hitSound:Play()
		end
	end
end

-- KEYBOARD INPUT
local keyMap = {
	A = 1,
	S = 2,
	W = 3,
	D = 4
}

UIS.InputBegan:Connect(function(input,gp)
	if gp or not playing then return end
	local lane = keyMap[input.KeyCode.Name]
	if lane then
		pressLane(lane)
	end
end)

-- REPLAY BUTTON
replayBtn.MouseButton1Click:Connect(function()
	resetGame()
	resultFrame.Visible = false
	main.Visible = true
	menu.Visible = false
	settings.Visible = false
	playing = true
	timerRunning = true
end)

-- MENU BACK BUTTON
menuBackBtn.MouseButton1Click:Connect(function()
	menu.Visible = false
	main.Visible = true
end)

-- PLAY BUTTON
playBtn.MouseButton1Click:Connect(function()
	menu.Visible = false
	main.Visible = true
	startGame()
end)

settingsBtn.MouseButton1Click:Connect(function()
	menu.Visible = false
	settings.Visible = true
end)

-- GAME LOOP
RunService.RenderStepped:Connect(function(dt)
	info.Text = "Score: "..score.." | Combo: "..combo.." | Misses: "..misses .. " | Time: "..math.floor(gameTime)

	if not playing then return end

	-- TIMER
	if timerRunning and not paused and not isResuming then
		gameTime -= dt
		if gameTime <= 0 then
			gameTime = 0
			timerRunning = false
			paused = true

			resultText.Text = "Score: "..score..
				"\nNotes Hit: "..notesHit..
				"\nNotes Spawned: "..notesSpawned

			resultFrame.Visible = true
			main.Visible = false
			return
		end
	end

	-- RESUME TIMER
	if isResuming then
		resumeTimer -= dt
		if resumeTimer <= 0 then
			isResuming = false
			paused = false
			pauseBtn.Text = "Pause"
		end
		return
	end

	-- SPAWN NOTES
	if not paused then
		spawnTimer += dt
		if spawnTimer >= spawnDelay then
			spawnTimer = 0
			spawnDelay = math.random(MIN_DELAY * 100, MAX_DELAY * 100) / 100
			spawnMultipleNotes()
		end

		for i = #notes,1,-1 do
			local n = notes[i]
			n.obj.Position += UDim2.fromOffset(0,NOTE_SPEED*dt)

			if n.obj.AbsolutePosition.Y > hitLine.AbsolutePosition.Y + HIT_WINDOW then
				n.obj:Destroy()
				table.remove(notes,i)
				combo = 0
				misses += 1
			end
		end
	end
end)

Features & Information

Fun to play whenever you're bored Free note hitsound id:7144958286

Community Comments (1)

OsosDarkHubOsosDarkHubJan 22, 2026

w

More Scripts for Universal Scripts