Tutorial: Wear and take off uniform


  • Hi, this is my first tutorial, in which I'm gonna teach you how to change somebodies uniform AND change it back.

    We will for simplicity limit ourselves to shirts, pants and T-Shirts, because the point is to extend this method to any clothing, or any other modifications you make of any character. Because let's face it, most games that can 'remove hats' will never give you your own hair back. I leave this challenge up to you!

    The general idea is that, each time you modify a player, you save all changes in a list or a dictionary. For any objects that you add or remove, you save the reference in your dictionary (the variable that you store them in, so that you can find them uniquely later). Then the objects themselves that you temporarily remove can be put in a holding folder in serverStorage.

    I'll summarize the process. When player touches this block, his current pants, shirts and T-shirts will be taken away, and stored. At the same time, a new uniform is cloned from our Uniforms storage and put on the player.

    A second button then on touch deletes the uniform and gives the player his old clothes back. Let's look at some code:

    myList = {}
    

    This bit of code above initiates a new "list" or "dictionary". Lists have keys and values as such:

    myList[key1] = value1 
    

    (this supposes we have two existing variables key1 and value1 with something stored in them)

    So now that we know how to fill things into a list, we make a block in workspace to which we attach a script. Make sure it a normal 'Part' for now. In the script, we write the following code:

    savedOutfits={}
    serverStorage = game:GetService("ServerStorage")
    clothesStorage = serverStorage:WaitForChild("PlayersClothesStorage")
    
    newPants = serverStorage:WaitForChild("Pants")
    newShirt = serverStorage:WaitForChild("Shirt")
    

    This doesn't do anything yet, but we start by making everything we will need later. For this code to work we need 3 things:
    -A pants called 'Pants' in the ServerStorage folder (in studio)
    -A shirt called 'Shirt' in the ServerStorage folder
    -An empty folder called 'PlayersClothesStorage' in the ServerStorage.

    These constitute your uniform and the storage location for player's clothes, so go ahead and find and insert them into ServerStorage.

    So after the above code we have variables with references to our list, to our storage folder, and to the new uniform. Now we can start listening to anybody touching our block.

    
    script.Parent.Touched:connect(function (otherPart)
        if otherPart and otherPart.Parent and otherPart.Parent:FindFirstChild('Humanoid') then
            local player = game:GetService("Players"):GetPlayerFromCharacter(otherPart.Parent)
            if player then
                wearUniform(player.Character)            
            end
        end
    end)
    
    

    This code makes really really sure that whatever touched our brick is really a player, and then calls the function wearUniform(player.Character). This function doesn't exist yet, so we are gonna write it:

    local function wearUniform(char)
        if not savedOutfits[char.Name] then
            saveClothes(char)
            removeClothes(char)
            local uniform = {newPants:Clone(), newShirt:Clone()}
            wearOutfit(char, uniform)
        end
    end
    

    So in this new function wearUniform we call three new unknown functions. Will our work ever be done? But I do this to organise everything into simple and reusable steps. The names of the functions make it clear what happens where and make it easier to debug. This function is to make a player wear a uniform. First, we save his current clothes, then we remove his current clothes, then we make a list of new clothes for him to wear, and finally put the new outfit onto the player's character. So after we have these three functions, we have completed giving somebody an outfit.

    Let's first look at the most important one, saving the current outfit.

    local function saveClothes(char)
        savedOutfits[char.Name] = {}
        local storageFolder = Instance.new("Folder", clothesStorage)
        storageFolder.Name = char.Name
        for index, item in pairs(char:GetChildren()) do
            if item:IsA("Pants") or item:IsA("ShirtGraphic") or item:IsA("Shirt") then
                local clone = item:Clone()
                savedOutfits[char.Name][#savedOutfits[char.Name]+1] = clone
                clone.Parent= storageFolder
            end
        end
    end
    

    You might ask, why not just parent the item to storageFolder, instead of cloning it and then later removing it? And yes that is better, but my code is more reusable, and the overhead has virtually no impact. Always be careful when doing unneccesairy for loops though.

    So in the code above, the saveClothes function makes a new list inside our savedOutfits list: so a list within a list, we call this a two-dimensional array. In our two-dimensional array, we store for each player a list of clothes. (so the first list is a list of players, then each player has his own list of clothing). We can access this list as savedOutfits[player][pieceOfClothing].

    The code above then makes a folder in our ClothesStorage folder in ServerStorage, where we can put the saved copies of our players' gear. We name the folder after the character name. We name the keys to our list after the character as well.

    The second keys in our list (for our clothing items) are just numbers, and we always pick a number 1 higher than the current highest number in the list:

    savedOutfits[char.Name][#savedOutfits[char.Name]+1] = clone
    

    So the function saveClothes looks at all the clothing items that we are interested in that are in the character, and saves them in both the dictionary and in serverstorage. To save in two places may be unneccesairy but I wanna make it really clear where everything is while we shuffle around with references.

    Two functions to go! The next one is easy now that we've seen saveOutfit:

    local function removeClothes(char, saveBool)
        for index, item in pairs(char:GetChildren()) do
            if item:IsA("Pants") or item:IsA("ShirtGraphic") or item:IsA("Shirt") then
                item:Destroy()
            end					
        end
    end
    

    Bam. Just destroy it. But now comes the hardest part. We have prepared a list of items, that are ready to be worn by the player's character, by the line:

    local uniform = {newPants:Clone(), newShirt:Clone()}
    

    We now have to write a function wearOutfit so that the character actually wears them. Well, since we have been so well-organised this far, this is our code:

    local function wearOutfit(char, outfit)
        for index, item in ipairs(outfit) do
           item.Parent = char
        end
    end
    

    Awesome! So we now have a block that in a really overly complex way gives a player a shirt and pants of our choice. Let's put all code that we have now together in the right order:

    savedOutfits={}
    serverStorage = game:GetService("ServerStorage")
    clothesStorage = serverStorage:WaitForChild("PlayersClothesStorage")
    
    newPants = serverStorage:WaitForChild("Pants")
    newShirt = serverStorage:WaitForChild("Shirt")
    
    local function saveClothes(char)
        savedOutfits[char.Name] = {}
        local storageFolder = Instance.new("Folder", clothesStorage)
        storageFolder.Name = char.Name
        for index, item in pairs(char:GetChildren()) do
            if item:IsA("Pants") or item:IsA("ShirtGraphic") or item:IsA("Shirt") then
                local clone = item:Clone()
                savedOutfits[char.Name][#savedOutfits[char.Name]+1] = clone
                clone.Parent= storageFolder
            end
        end
    end
    
    local function removeClothes(char, saveBool)
        for index, item in pairs(char:GetChildren()) do
            if item:IsA("Pants") or item:IsA("ShirtGraphic") or item:IsA("Shirt") then
                item:Destroy()
            end					
        end
    end
    
    local function wearOutfit(char, outfit)
        for index, item in ipairs(outfit) do
           item.Parent = char
        end
    end
    
    local function wearUniform(char)
        if not savedOutfits[char.Name] then
            saveClothes(char)
            removeClothes(char)
            local uniform = {newPants:Clone(), newShirt:Clone()}
            wearOutfit(char, uniform)
        end
    end
    
    script.Parent.Touched:connect(function (otherPart)
        if otherPart and otherPart.Parent and otherPart.Parent:FindFirstChild('Humanoid') then
            local player = game:GetService("Players"):GetPlayerFromCharacter(otherPart.Parent)
            if player then
                wearUniform(player.Character)            
            end
        end
    end)
    
    

    Great! But what do we do with it? Well off course the reason we wanted to save all that stuff is to give it back again later. We will make a second block, with the same parent as our first block, named "Block2". And if you touch it while wearing the uniform, you get your clothes back.

    And we will try to do the same thing and reuse our code as much as possible:

    script.Parent.Parent:WaitForChild("Block2").Touched:connect(function (otherPart)
        if otherPart and otherPart.Parent and otherPart.Parent:FindFirstChild('Humanoid') then
            local player = game:GetService("Players"):GetPlayerFromCharacter(otherPart.Parent)
            if player then
                returnOldClothes(player.Character)            
            end
        end
    end)
    

    We listen for when a player touches our second block, and then call the function returnOldClothes. Now comes the moment we are glad that we wrote all that code in low-level-concept functions before:

    local function returnOldClothes(char)
        if savedOutfits[char.Name] then
            removeClothes(char)
            wearOutfit(char, savedOutfits[char.Name])
            savedOutfits[char.Name] = nil	
            storeFolder = clothesStorage:FindFirstChild(char.Name)
            if storeFolder and storeFolder:IsA("Folder") then storeFolder:Destroy() end
        end
    end
    
    

    We can reuse all our previous functions removeClothes() and wearOutfit(), because we don't need to save the uniform and have a list ready of the player's old clothes. Then we just need to clean up and remove the folder and list that held the player's clothes while he didn't wear them.

    We may not have saved ourselves much time in this example, but in big applications you will be a happier programmer when you are accustomed to this "code-reuse" kind of thinking. What I made here not an elegant or particularly efficient implementation but it's robust due to it's clarity.

    Speaking of which, now is the time to look carefully for any eventualities. What if a player dies or resets character? He will get his clothes back on respawn, then get double clothes and all kinds of bugs! So we write a fix:

    
    game:GetService("Players")..PlayerAdded:connect(function(player)
        player.CharacterAdded:connect(function(character)
            savedOutfits[character.Name] = nil
            storeFolder = clothesStorage:FindFirstChild(character.Name)
            if storeFolder and storeFolder:IsA("Folder") then storeFolder:Destroy() end
        end)
    end)
    
    

    There, now every time a player receives a new character model (after dying or resetting), we also reset our save files for that player. That way nothing messes up.

    Great, so I think we are now all done with this tutorial! Can you think of anything else we should add?

    Here is the summary of the whole code:

    savedOutfits={}
    serverStorage = game:GetService("ServerStorage")
    clothesStorage = serverStorage:WaitForChild("PlayersClothesStorage")
    
    newPants = serverStorage:WaitForChild("Pants")
    newShirt = serverStorage:WaitForChild("Shirt")
    
    local function saveClothes(char)
        savedOutfits[char.Name] = {}
        local storageFolder = Instance.new("Folder", clothesStorage)
        storageFolder.Name = char.Name
        for index, item in pairs(char:GetChildren()) do
            if item:IsA("Pants") or item:IsA("ShirtGraphic") or item:IsA("Shirt") then
                local clone = item:Clone()
                savedOutfits[char.Name][#savedOutfits[char.Name]+1] = clone
                clone.Parent= storageFolder
            end
        end
    end
    
    local function removeClothes(char, saveBool)
        for index, item in pairs(char:GetChildren()) do
            if item:IsA("Pants") or item:IsA("ShirtGraphic") or item:IsA("Shirt") then
                item:Destroy()
            end					
        end
    end
    
    local function wearOutfit(char, outfit)
        for index, item in ipairs(outfit) do
           item.Parent = char
        end
    end
    
    local function wearUniform(char)
        if not savedOutfits[char.Name] then
            saveClothes(char)
            removeClothes(char)
            local uniform = {newPants:Clone(), newShirt:Clone()}
            wearOutfit(char, uniform)
        end
    end
    
    local function returnOldClothes(char)
        if savedOutfits[char.Name] then
            removeClothes(char)
            wearOutfit(char, savedOutfits[char.Name])
            savedOutfits[char.Name] = nil	
            storeFolder = clothesStorage:FindFirstChild(char.Name)
            if storeFolder and storeFolder:IsA("Folder") then storeFolder:Destroy() end
        end
    end
    
    script.Parent.Touched:connect(function (otherPart)
        if otherPart and otherPart.Parent and otherPart.Parent:FindFirstChild('Humanoid') then
            local player = game:GetService("Players"):GetPlayerFromCharacter(otherPart.Parent)
            if player then
                wearUniform(player.Character)            
            end
        end
    end)
    
    script.Parent.Parent:WaitForChild("Block2").Touched:connect(function (otherPart)
        if otherPart and otherPart.Parent and otherPart.Parent:FindFirstChild('Humanoid') then
            local player = game:GetService("Players"):GetPlayerFromCharacter(otherPart.Parent)
            if player then
                returnOldClothes(player.Character)            
            end
        end
    end)
    
    
    
    game:GetService("Players").PlayerAdded:connect(function(player)
        player.CharacterAdded:connect(function(character)
            savedOutfits[character.Name] = nil
            storeFolder = clothesStorage:FindFirstChild(character.Name)
            if storeFolder and storeFolder:IsA("Folder") then storeFolder:Destroy() end
        end)
    end)
    
    

    Awesome! Now I leave it up to you to temporarily take a players inventories, hats, scores etc. This approach works to store something individually for each player, without them getting eachothers clothes. It would work even if we stored all items directly in serverStorage, or even parented them to nil, because we keep the reference to it in our list. I did that just to show clearly where the items would stay, which is in any case in general often not a bad idea.

    Well, I hope you learned something and enjoyed it. It's more about the concepts and points to take from this than about the actual implementation or best way to award/save clothes. It doesn't save anything between visits or even between deaths. Maybe that is something for next time, if you think my tutorial is good enough to make more.

    Find the example place with all this code in action, free to copy at:
    https://www.roblox.com/games/1614355578/Uniform-Demo?rbxp=264859050

    Find the Rat Chat café (opening soon) where I implemented this approach for employees in an actual place:
    https://www.roblox.com/games/1477244537/Cafe-Rat-Chat?rbxp=264859050

    Find my current main project, a work-in-progress farming game, for many more ideas on what you can do with scripting to learn from:
    https://www.roblox.com/games/913673244/Goose-Haven-Teaser?rbxp=264859050

    If you like what I do please upvote it, to follow join my group at:
    https://www.roblox.com/groups/group.aspx?gid=3976671&rbxp=264859050

    Have a great day!
    Nonaz_jr


  • @Nonaz_jr

    Didn't read the whole tutorial, but must say you must took long to write that!

Log in to reply
 

Looks like your connection to Scripting Helpers was lost, please wait while we try to reconnect.