Custom Character Loading Issue

So i was working on an old roblox revival which i made. I used this script (from How do I make a datastore save a player's custom character?):

(local DataStore = game:GetService("DataStoreService"):GetDataStore("ClothingSaveReset")

function Load(Chr, Plr)
    task.wait(2)
    local good, info = pcall(function()
        return(DataStore:GetAsync(Plr.UserId))
    end)
    print(Plr.Name, "DataStoreRetrivalInformation", good, info)
    Chr.Shirt.ShirtTemplate = info.Shirt
    Chr.Pants.PantsTemplate = info.Pants
    Chr.Head.face.Texture = info.Face
    Chr.Accesory.Handle.Mesh.MeshId = info.Accesory
    Chr.Accesory.Handle.Mesh.TextureId = info.TextureAccesory
end

local function LoadCharacter(Plr)
    task.wait(5)
    local Char =  Plr.Character
    Load(Char,Plr)
end

game.Players.PlayerAdded:Connect(function(plr)
    LoadCharacter(plr)
    plr.Changed:Connect(function()
        task.wait(2)
        LoadCharacter(plr)
    end)
end))

It worked fine for my roblox friend who has edit permissions, meanwhile it doesn’t work for me:


(When i screenshotted this i had pants and a shirt by the way)

1 Like

Seems like Pants and Shirt instances are missing.

The issue could be due to ‘Chr’ being nil or not having the expected structure.

You could try something like this:

local DataStore = game:GetService("DataStoreService"):GetDataStore("ClothingSaveReset")

function Load(Chr, Plr)
    task.wait(2)
    local good, info = pcall(function()
        return(DataStore:GetAsync(Plr.UserId))
    end)
    print(Plr.Name, "DataStoreRetrivalInformation", good, info)

    if Chr and info then
        if Chr:FindFirstChild("Shirt") and info.Shirt then
            Chr.Shirt.ShirtTemplate = info.Shirt
        end

        if Chr:FindFirstChild("Pants") and info.Pants then
            Chr.Pants.PantsTemplate = info.Pants
        end

        if Chr:FindFirstChild("Head") and Chr.Head:FindFirstChild("face") and info.Face then
            Chr.Head.face.Texture = info.Face
        end

        if Chr:FindFirstChild("Accesory") and Chr.Accesory:FindFirstChild("Handle") and Chr.Accesory.Handle:FindFirstChild("Mesh") and info.Accesory then
            Chr.Accesory.Handle.Mesh.MeshId = info.Accesory
            Chr.Accesory.Handle.Mesh.TextureId = info.TextureAccesory
        end
    end
end

local function LoadCharacter(Plr)
    task.wait(5)
    local Char = Plr.Character
    Load(Char, Plr)
end

game.Players.PlayerAdded:Connect(function(plr)
    LoadCharacter(plr)
    plr.Changed:Connect(function()
        task.wait(2)
        LoadCharacter(plr)
    end)
end)

This script checks if ‘Chr’ and ‘info’ are not nil, then checks if each property exists before trying to access it.

1 Like

You have a couple of code smells.

  • You should never need to wait an arbitrary amount of time (task.wait) before running code. If you do, you’re not writing defensive code. Don’t rely on timing.

  • CharacterAdded is the canonical way to check when a new character is set for the player. You should be using this instead of Changed.

  • You should not be performing DataStore operations every time the character spawns as you will very easily hit request limits. Start caching.

  • In preparation for deferred mode, you should be making a named function that you connect to handle all event fires as well as any existing instances.

With Immediate signals, the character subtree is not guaranteed when CharacterAdded fires (usually, more specifically, any appearance items). It is important to design around this - you should use CharacterAppearanceLoaded when writing code that customises a character’s appearance.

You might end up with a code sample that may look something like this:

local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")

local ClothingStore = DataStoreService:GetDataStore("ClothingSaveReset")

local appearanceInfo = {}

local function onPlayerAdded(player)
    local success, ret = pcall(function()
        return ClothingStore:GetAsync(player.UserId)
    end)

    if success then
        appearanceInfo[player.UserId] = ret
    end

    local function onCharacterAppearanceLoaded(character)
        local info = appearanceInfo[player.UserId]
        -- Write ShirtTemplate, PantsTemplate, etc.
    end

    player.CharacterAppearanceLoaded:Connect(onCharacterAppearanceLoaded)
    if player.Character then
        if not player:HasAppearanceLoaded() then
            player.CharacterAppearanceLoaded:Wait()
        end
        onCharacterAppearanceLoaded(player.Character)
    end
end

Players.PlayerAdded:Connect(onPlayerAdded)
for _, player in Players:GetPlayers() do
    onPlayerAdded(player)
end

This is not a full code sample, it’s only meant to show what it might look like and point out some issues with the original code sample. It’s intentionally incomplete and doesn’t account for a vast majority of practice problems (e.g. player connections leak memory since the Player instance isn’t destroyed on leave, no cache cleaning, no DataStore saving, etc).

2 Likes