Most efficient way to get character model using user id?

I’ve been working on a custom character controller with it’s own replication system, each client has his own copy of all player’s avatars and moves them according to state data comming from the server.

I need an efficient and low-overhead way to get the character model

here is my current implementation:

local InsertService = game:GetService("InsertService")
local PlayersService = game:GetService("Players")
local RepStorage = game:GetService("ReplicatedStorage")
local GetPlayerModelRequest = RepStorage.GetPlayerModelRequest
local DefaultRig = script.DefaultRig

local function onGetPlayerModelRequest(_, playerId: number): Model
	assert(typeof(playerId) == "number", "Player Id must be a number")
	local avatarData = PlayersService:GetCharacterAppearanceInfoAsync(playerId)
	local character = DefaultRig:Clone()
	character.Name = PlayersService:GetNameFromUserIdAsync(playerId)
	
	-- Apply Body Colors
	local bodyColors = Instance.new("BodyColors")
	bodyColors.HeadColor = BrickColor.new(avatarData.bodyColors.headColorId)
	bodyColors.TorsoColor = BrickColor.new(avatarData.bodyColors.torsoColorId)
	bodyColors.LeftArmColor = BrickColor.new(avatarData.bodyColors.leftArmColorId)
	bodyColors.RightArmColor = BrickColor.new(avatarData.bodyColors.rightArmColorId)
	bodyColors.LeftLegColor = BrickColor.new(avatarData.bodyColors.leftLegColorId)
	bodyColors.RightLegColor = BrickColor.new(avatarData.bodyColors.rightLegColorId)
	bodyColors.Parent = character

	-- Load accessories, clothing, and body parts from assets
	for _, asset in ipairs(avatarData.assets) do
		local assetModel = InsertService:LoadAsset(asset.id)
		for _, loadedAsset in ipairs(assetModel:GetChildren()) do
			for _, part: Part in loadedAsset:GetDescendants() do
				if part:IsA("Part") then
					part.CastShadow = false
					part.CanCollide = false
					part.CanQuery = false
					part.CanTouch = false
				end
			end
			loadedAsset.Parent = character -- Assuming `character` is your player's character model
		end
	end

	-- Return the constructed character model
	character.Parent = workspace -- issue here !
	return character	
end

GetPlayerModelRequest.OnServerInvoke = onGetPlayerModelRequest 

The main issue with this approach is that I have to set the parent of this model as you can see at the end of the function, if I didn’t, it will be dropped by the time it’s sent to the client.

In my replication model, the players are represented as data on the server, so having creating a model on the server causes issues since it’s automatically replicated to all players, so now each client would have two copies of each player’s avatar model.

this function doesn’t work on the client, apparently you can’t use :LoadAsset method of asset service on the client.

1 Like

This may work, but there is a much easier way:

local function onGetPlayerModelRequest(_, playerId: number): Model
	assert(typeof(playerId) == "number", "Player Id must be a number")
	local avatarData = game.Players:GetHumanoidDescriptionFromUserId(playerId)
	local character = DefaultRig:Clone()
    character.Humanoid:ApplyDescription(avatarData)
	character.Parent = workspace
	return character	
end
2 Likes
Humanoid::ApplyDescription() DataModel was not available

That’s the error message I got with it.
Also, I need the character to be R6 and not apply any scaling, so I’m not sure about this approach.

Server-side

  1. Data Preparation: Prepare and send only the necessary avatar data to the client.
local RepStorage = game:GetService("ReplicatedStorage")
local GetPlayerModelDataRequest = RepStorage.GetPlayerModelDataRequest

local function onGetPlayerModelDataRequest(_, playerId: number)
    assert(typeof(playerId) == "number", "Player Id must be a number")
    local avatarData = PlayersService:GetCharacterAppearanceInfoAsync(playerId)
    return avatarData
end

GetPlayerModelDataRequest.OnServerInvoke = onGetPlayerModelDataRequest

Client-side

  1. Model Construction: Construct the model locally on each client.
local RepStorage = game:GetService("ReplicatedStorage")
local DefaultRig = script.DefaultRig
local GetPlayerModelDataRequest = RepStorage.GetPlayerModelDataRequest

local function constructCharacterModel(playerId)
    local avatarData = GetPlayerModelDataRequest:InvokeServer(playerId)
    local character = DefaultRig:Clone()
    
    -- Set character properties based on data
    character.Name = PlayersService:GetNameFromUserIdAsync(playerId)
    
    -- Apply Body Colors
    local bodyColors = Instance.new("BodyColors")
    bodyColors.HeadColor = BrickColor.new(avatarData.bodyColors.headColorId)
    bodyColors.TorsoColor = BrickColor.new(avatarData.bodyColors.torsoColorId)
    bodyColors.LeftArmColor = BrickColor.new(avatarData.bodyColors.leftArmColorId)
    bodyColors.RightArmColor = BrickColor.new(avatarData.bodyColors.rightArmColorId)
    bodyColors.LeftLegColor = BrickColor.new(avatarData.bodyColors.leftLegColorId)
    bodyColors.RightLegColor = BrickColor.new(avatarData.bodyColors.rightLegColorId)
    bodyColors.Parent = character

    -- Load accessories, clothing, and body parts from assets
    for _, asset in ipairs(avatarData.assets) do
        local assetModel = InsertService:LoadAsset(asset.id) -- This will need a workaround as InsertService isn't available on the client
        for _, loadedAsset in ipairs(assetModel:GetChildren()) do
            for _, part: Part in loadedAsset:GetDescendants() do
                if part:IsA("Part") then
                    part.CastShadow = false
                    part.CanCollide = false
                    part.CanQuery = false
                    part.CanTouch = false
                end
            end
            loadedAsset.Parent = character
        end
    end

    -- Parent to the client-specific location
    character.Parent = workspace -- Adjust this to wherever you need the model on the client
end
1 Like

the following script worked for me:

local InsertService = game:GetService("InsertService")
local PlayersService = game:GetService("Players")
local RepStorage = game:GetService("ReplicatedStorage")
local GetPlayerModelRequest = RepStorage.GetPlayerModelRequest
local DefaultRig = script.DefaultRig

local function onGetPlayerModelRequest(_, playerId: number): Model
	assert(typeof(playerId) == "number", "Player Id must be a number")
	local avatarData = PlayersService:GetCharacterAppearanceInfoAsync(playerId)
	local character = DefaultRig:Clone()
	local scale = character:GetScale()
	character:ScaleTo(1)
	character.Parent = game.ReplicatedStorage
	character.Name = PlayersService:GetNameFromUserIdAsync(playerId)
	local humanoid = character.Humanoid or Instance.new("Humanoid")
    local alreadyThere = character:FindFirstChild("Humanoid")
	humanoid.Parent = character
	local descr = game.Players:GetHumanoidDescriptionFromUserId(playerId)
	humanoid:ApplyDescription(descr)
	character:ScaleTo(scale)
        if not alreadyThere then
          	humanoid:Destroy()
        end
	character.Parent = workspace
	return character	
end

GetPlayerModelRequest.OnServerInvoke = onGetPlayerModelRequest 

hopefully it works for you too, this one parents the character to replicatedStorage for it to have a valid DataModel

2 Likes

chatgpt ahh code,


why would you use InsertService on the client if you know it would need a workaround?

2 Likes

There is no a workaround for it, apparently it has to do with security reasons, the api only allows servers to request such data.

This is a different way of doing the same thing, I tried it and it works but it doesn’t solve the problem.

I guess If there is no way around not creating the model on the server, I might aswell just allow the server to create it for me and clone it on the client.

You are still using default Roblox character models, right? Not your own build?

You’ll likely need to do this on the server for a neater implementation. Creating a rig from a player’s UserId can be done simply like this:

--do this on the server
local players = game:GetService("Players")

local function onPlayerAdded(player: Player)
    local char = players:CreateHumanoidModelFromUserId(player.UserId)
    (player.Character or player.CharacterAdded:Wait()):Destroy()
    char.Parent = workspace
end

players.PlayerAdded:Connect(onPlayerAdded)

The client can then wait for it in the workspace and do what it will.

Or, if you literally just don’t want the player to have default Roblox control over the model, you can set the player.Character property to something else.

1 Like

Finaly have figured it out, took so long.

Players:GetHumanoidDescriptionFromUserId()
to get a humanoid description object, it has all the Id’s of all the assets, body colors, etc of the player’s avatar.

Then I can apply it use Humanoid:ApplyDescription()

the humanoid has to be created locally though, so an already exisiting humanoid when the game was loaded won’t allow this method to work on it.