Multiple StarterCharacters

As a Roblox developer, it is impossible to have more than one StarterCharacter.
My use case is simple. I have a game that needs a different StarterCharacter depending on the in-game role of the character. For example, a knight would require a StarterCharacter that looks like a knight, and a wizard would require a StarterCharacter that looks like a wizard.

If Roblox is able to address your issue, how would it improve your game and/or your development experience.
With this feature, it would be very easy to create games with multiple StarterCharacters that have different appearances.

Here is a sample implementation:

Players.PlayerAdded:Connect(function(player)
	local playerRole = GetPlayerRole(player)
	if playerRole == "Knight" then
	    	player.StarterCharacter = KnightStarterCharacter
	else
	    	player.StarterCharacter = WizardStarterCharacter
	end
end)

Currently, I am forced to do one of two workarounds, neither of which is ideal.

1. Set player.Character to a custom model.
But this does not load the default character scripts (Animate, Health, Animate), nor does it load any scripts in the CharacterScripts folder. It also does not automatically spawn characters at SpawnLocations.

2. Use a single StarterCharacter, and morph it into another.
The morph basically swaps out all the body parts, and then uses BuildRigFromAttachments. But this method is prone to the occasional character death during the process.

33 Likes

I want to bring up this underrated feature request once again. As mentioned, a big problem is that there can only be one StarterCharacter at a time. But to make things worse, the Player:LoadCharacter() method is also a yielding method.

The problem with LoadCharacter() being a yielding method is that two players could now technically be loading at the same time. There is no guarantee that calling LoadCharacter() on player A before player B will also make player A spawn before player B. Maybe player B loads faster. So if you swap in the StarterCharacter for a player, load them, and then swap out the StarterCharacter, there is no guarantee that some other character loaded as that StarterCharacter in the meantime.

With multiple StarterCharacters, one assigned to each player, this would no longer be a problem.

To work around the loading problem, I have to implement a loading system where characters are being added to a queue so that I can load their character one by one and guarantee that they load in the right order, so that I can swap in and out StarterCharacter models at the right moment. However, I also need to be careful because a player could technically leave the game while they are loading because, again, LoadCharacter() is a yielding method and there is no guarantee that the player’s character is always loaded before PlayerRemoving is called.

The result looks something like this:

local SpawnQueue = {}

function spawnPlayer(Plr: Player)
	table.insert(SpawnQueue, Plr) -- SpawnQueue is an ordered array with Player instances
	task.spawn(
		function()
			-- wait until it's time to spawn the player or the player is no longer in the game
			while SpawnQueue[1] ~= Plr and Plr ~= nil and Plr:IsDescendantOf(game.Players) do -- edge-case in case players leave
				game:GetService("RunService").Heartbeat:Wait() -- ideally you would find a way to eliminate any additional waits, but this is 'simpler' to program
			end
			
			-- destroy old starter character and create a new one
			if game.StarterPlayer:FindFirstChild("StarterCharacter")~= nil then
				game.StarterPlayer.StarterCharacter:Destroy()
			end
			local NewCharacter = createStarterCharacter(Plr)
			NewCharacter.Parent = game.StarterPlayer
			
			-- if the player is still in the game, finally load them and remove them from the queue
			if Plr ~= nil and Plr:IsDescendantOf(game.Players) then
				Plr:LoadCharacter()
				-- check again if the player is in the game because LoadCharacter yields
				if SpawnQueue[1] == Plr then
					table.remove(SpawnQueue, 1)
				end
			end
		end
	)
	
end

game.Players.PlayerRemoving:Connect(
	function(Plr: Player)
		-- if a player leaves, remove them from the spawn queue to prevent a soft-lock
		for i = 1, #SpawnQueue do
			if SpawnQueue[i] == Plr then
				table.remove(SpawnQueue, i)
				break
			end
		end
	end
)

This is a ton of code for a task that should be super simple to do. With multiple StarterCharacters I can remove all of this code and simply use the code from the original post. I know which one I prefer.

3 Likes

This! Currently, Gem Galaxies uses a method of setting the Player.Character directly to a new character’s rig from a ServerStorage location. I have a solid enough workaround to mitigate this, however for other games in the future I’d like to avoid using workarounds and instead something more official that let’s me set a character per player.

Another unrelated/on another note feature I’d like to see too is not having a character loaded by default, I know there’s also work arounds for that, but being able to have the player not loaded in automatically so that I don’t have to shove the players into a random spawn cube (and any workaround advice would be appreciated, too).

2 Likes

This would be really useful, saving a lot of time and generally simplifying the process of using different StarterCharacters.

1 Like

I really don’t see how or why this should be implemented. We already have HumanoidDescriptions.

Yikes...

It can be done in 4 lines of code at the least.
I recommend:

1 Like

HumanoidDescriptions are not the solution.

As per the HumanoidDescription documentation:

HumanoidDescription is an object that stores a description a Humanoid for R6 and R15 rigs.

This does not help if you are using a custom character made out of skinned MeshParts. It also does not work if your rig is a custom rig for an animal for example. It also does not work if you want to add more fidelity to your character by splitting limbs into multiple parts. I think you get the idea.

1 Like

What about a SpawnParams object that was passed through Player/LoadCharacter? That way, the functionality is extendable (Model, CFrame, HumanoidDescription, etc.) on a case-by-case basis without sacrificing forwards-compatibility.

Yes, this has been on my Feature-Request TODO list. No, I cannot create a thread for it because I’m stuck as a member.

1 Like