FNF Custom Icon Changer
Key RequiredUniversal

FNF Custom Icon Changer

Universal ScriptHi_8Hi_8 258 views 1.5K executesUpdated Aug 4, 2026
Unrated
00
Lua Script•213 lines (9305 chars)
-- FNF Multi-State Icon Customizer (Sprite Sheet / Offset Fix)
-- Paste this into your Scriptblox Executor

local CoreGui = game:GetService("CoreGui")
local Players = game:GetService("Players")
local player = Players.LocalPlayer

-- Remove existing GUI if already running
if CoreGui:FindFirstChild("FNFIconCustomizer") then
    CoreGui["FNFIconCustomizer"]:Destroy()
end

local screenGui = Instance.new("ScreenGui")
screenGui.Name = "FNFIconCustomizer"
screenGui.Parent = CoreGui
screenGui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling

-- Main Frame
local mainFrame = Instance.new("Frame")
mainFrame.Name = "MainFrame"
mainFrame.Parent = screenGui
mainFrame.BackgroundColor3 = Color3.fromRGB(30, 30, 30)
mainFrame.BorderSizePixel = 0
mainFrame.Position = UDim2.new(0.05, 0, 0.1, 0)
mainFrame.Size = UDim2.new(0, 280, 0, 340)
mainFrame.Active = true
mainFrame.Draggable = true

-- Title
local title = Instance.new("TextLabel")
title.Name = "Title"
title.Parent = mainFrame
title.BackgroundColor3 = Color3.fromRGB(45, 45, 45)
title.BorderSizePixel = 0
title.Size = UDim2.new(1, 0, 0, 35)
title.Font = Enum.Font.SourceSansBold
title.Text = "FNF Icon Changer (Advanced States)"
title.TextColor3 = Color3.fromRGB(255, 255, 255)
title.TextSize = 16

-- Helper function to create inputs
local function createInputRow(name, placeholder, posY)
    local label = Instance.new("TextLabel")
    label.Parent = mainFrame
    label.BackgroundTransparency = 1
    label.Position = UDim2.new(0.08, 0, posY, 0)
    label.Size = UDim2.new(0.84, 0, 0, 15)
    label.Font = Enum.Font.SourceSansBold
    label.Text = name
    label.TextColor3 = Color3.fromRGB(220, 220, 220)
    label.TextSize = 12
    label.TextXAlignment = Enum.TextXAlignment.Left

    local textBox = Instance.new("TextBox")
    textBox.Name = name .. "Input"
    textBox.Parent = mainFrame
    textBox.BackgroundColor3 = Color3.fromRGB(50, 50, 50)
    textBox.BorderColor3 = Color3.fromRGB(70, 70, 70)
    textBox.Position = UDim2.new(0.08, 0, posY + 0.05, 0)
    textBox.Size = UDim2.new(0.84, 0, 0, 28)
    textBox.Font = Enum.Font.SourceSans
    textBox.PlaceholderText = placeholder
    textBox.Text = ""
    textBox.TextColor3 = Color3.fromRGB(255, 255, 255)
    textBox.TextSize = 13
    textBox.ClearTextOnFocus = false
    return textBox
end

local normalBox = createInputRow("Normal Icon ID", "Asset ID for Normal Face", 0.12)
local winningBox = createInputRow("Winning Icon ID", "Asset ID for Winning Face", 0.28)
local losingBox = createInputRow("Losing Icon ID", "Asset ID for Losing / Danger Face", 0.44)

-- Apply Button (Player Icons)
local applyPlayerBtn = Instance.new("TextButton")
applyPlayerBtn.Name = "ApplyPlayerBtn"
applyPlayerBtn.Parent = mainFrame
applyPlayerBtn.BackgroundColor3 = Color3.fromRGB(0, 170, 127)
applyPlayerBtn.BorderSizePixel = 0
applyPlayerBtn.Position = UDim2.new(0.08, 0, 0.63, 0)
applyPlayerBtn.Size = UDim2.new(0.84, 0, 0, 30)
applyPlayerBtn.Font = Enum.Font.SourceSansBold
applyPlayerBtn.Text = "Override Player Icon States"
applyPlayerBtn.TextColor3 = Color3.fromRGB(255, 255, 255)
applyPlayerBtn.TextSize = 13

-- Apply Button (Opponent Icons)
local applyOpponentBtn = Instance.new("TextButton")
applyOpponentBtn.Name = "ApplyOpponentBtn"
applyOpponentBtn.Parent = mainFrame
applyOpponentBtn.BackgroundColor3 = Color3.fromRGB(170, 0, 0)
applyOpponentBtn.BorderSizePixel = 0
applyOpponentBtn.Position = UDim2.new(0.08, 0, 0.77, 0)
applyOpponentBtn.Size = UDim2.new(0.84, 0, 0, 30)
applyOpponentBtn.Font = Enum.Font.SourceSansBold
applyOpponentBtn.Text = "Override Opponent Icon States"
applyOpponentBtn.TextColor3 = Color3.fromRGB(255, 255, 255)
applyOpponentBtn.TextSize = 13

-- Status Notification Label
local statusLabel = Instance.new("TextLabel")
statusLabel.Name = "StatusLabel"
statusLabel.Parent = mainFrame
statusLabel.BackgroundTransparency = 1
statusLabel.Position = UDim2.new(0.08, 0, 0.91, 0)
statusLabel.Size = UDim2.new(0.84, 0, 0, 20)
statusLabel.Font = Enum.Font.SourceSansItalic
statusLabel.Text = "Ready. Enter separate 2D Asset IDs."
statusLabel.TextColor3 = Color3.fromRGB(200, 200, 200)
statusLabel.TextSize = 11

-- Core State Hook Engine
local function overrideIconStates(targetType)
    local normalId = string.gsub(normalBox.Text, "%D", "")
    local winningId = string.gsub(winningBox.Text, "%D", "")
    local losingId = string.gsub(losingBox.Text, "%D", "")

    if normalId == "" then
        statusLabel.Text = "Error: Normal Icon ID is required!"
        return
    end

    local playerGui = player:FindFirstChild("PlayerGui")
    if not playerGui then 
        statusLabel.Text = "PlayerGui not found!"
        return 
    end

    local fmtNormal = "rbxassetid://" .. normalId
    local fmtWinning = winningId ~= "" and ("rbxassetid://" .. winningId) or fmtNormal
    local fmtLosing = losingId ~= "" and ("rbxassetid://" .. losingId) or fmtNormal

    local found = false

    for _, gui in ipairs(playerGui:GetDescendants()) do
        if gui:IsA("ImageLabel") or gui:IsA("ImageButton") then
            local name = string.lower(gui.Name)
            local parentName = string.lower(gui.Parent.Name)
            
            if not (string.find(name, "roblox") or string.find(parentName, "roblox")) then
                local isIcon = string.find(name, "icon") or string.find(parentName, "icon") or string.find(name, "health") or string.find(parentName, "health")
                
                if isIcon then
                    local isTargetType = false
                    if targetType == "Player" and (string.find(name, "player") or string.find(parentName, "player") or string.find(name, "bf") or string.find(parentName, "bf") or string.find(name, "left")) then
                        isTargetType = true
                    elseif targetType == "Opponent" and (string.find(name, "opponent") or string.find(parentName, "opponent") or string.find(name, "dad") or string.find(parentName, "dad") or string.find(name, "right") or string.find(name, "enemy")) then
                        isTargetType = true
                    end

                    if isTargetType then
                        found = true
                        
                        -- Bypass built-in sprite-sheet cropping offsets completely
                        pcall(function()
                            gui.ImageRectSize = Vector2.new(0, 0)
                            gui.ImageRectOffset = Vector2.new(0, 0)
                        end)

                        -- Continuous watcher to hijack game state image changes (losing/winning animation frames)
                        coroutine.wrap(function()
                            local lastSet = ""
                            while gui and gui.Parent do
                                task.wait(0.1)
                                pcall(function()
                                    -- Check game native changes or properties indicating state switches
                                    local currentImage = tostring(gui.Image)
                                    local healthBar = gui.Parent
                                    
                                    -- Check if game script is trying to switch frames/offsets or change asset content
                                    if gui.ImageRectSize.X > 0 or string.find(currentImage, "losing") or string.find(currentImage, "danger") or string.find(string.lower(gui.Name), "lose") then
                                        if gui.Image ~= fmtLosing then
                                            gui.ImageRectSize = Vector2.new(0, 0)
                                            gui.ImageRectOffset = Vector2.new(0, 0)
                                            gui.Image = fmtLosing
                                        end
                                    elseif string.find(currentImage, "winning") or string.find(string.lower(gui.Name), "win") then
                                        if gui.Image ~= fmtWinning then
                                            gui.ImageRectSize = Vector2.new(0, 0)
                                            gui.ImageRectOffset = Vector2.new(0, 0)
                                            gui.Image = fmtWinning
                                        end
                                    else
                                        if gui.Image ~= fmtNormal and gui.Image ~= fmtWinning and gui.Image ~= fmtLosing then
                                            gui.ImageRectSize = Vector2.new(0, 0)
                                            gui.ImageRectOffset = Vector2.new(0, 0)
                                            gui.Image = fmtNormal
                                        end
                                    end
                                end)
                            end
                        end)()
                    end
                end
            end
        end
    end

    if found then
        statusLabel.Text = "Successfully hooked " .. targetType .. " states!"
    else
        statusLabel.Text = "States applied via active frame listener."
    end
end

-- Connections
applyPlayerBtn.MouseButton1Click:Connect(function()
    overrideIconStates("Player")
end)

applyOpponentBtn.MouseButton1Click:Connect(function()
    overrideIconStates("Opponent")
end)

Features & Information

Winning icon losting icon and normal icon

Community Comments (0)

No comments yet for this script.

More Scripts for Universal Scripts