PSA: (Player.Character or Player.CharacterAdded:Wait()) to get a Character model from a tool is no bueno

I recently learned that using Player.Character or Player.CharacterAdded:Wait() which is a common idiom which is supposedly the most reliable way, is actually no good. This only works when you first spawn, and Player.Character is already nil. But the problem is when you respawn, and Player.Character is not nil, but is the destroyed previous dead character. Using just Player.CharacterAdded:Wait() is no good either, because if your script runs after your character is loaded, this will yield forever.

A simple LocalScript in StarterPack demonstrates the disparity:

local Player = game:GetService("Players").LocalPlayer
print(if Player.Character then Player.Character.Parent else nil)
print(Player.CharacterAdded:Wait().Parent)

Notice that only the second one does not print nil even after respawning.

The only reliable way is to check the Player.Character and also make sure that the Parent exists:

local Player = game:GetService("Players").LocalPlayer
local function GetCharacter()
    local Char = Player.Character
    return if Char and Char.Parent then Char else Player.CharacterAdded:Wait()
end
4 Likes

I’d rather use the same method but use the Humanoid.Died event.

1 Like

CharacterAdded waits until character is fully loaded then.

2 Likes

Well yeah, thats how it works and its how its supposed to work

1 Like

Good observation! Apparently the behaviour was hiding in the Player docs:

LocalScripts that are cloned from StarterGui or StarterPack into a player’s Backpack or PlayerGui are often run before the old Character model is deleted.

The code sample there is not correct so I made a PR (your topic is linked) which also includes this:

local character = player.Character
if not character or character.Parent == nil then
	character = player.CharacterAdded:Wait()
end

In immediate signal mode player.CharacterAdded:Wait() alone works fine most of the time, but often yields indefinitely in deferred mode, hence the if-statement.

2 Likes

I recently found out this wasn’t good either and spent hours trying to fix my gun breaking until I got to a solution similar to this. Wish I found this earlier.

Sorry for necroing this, but to clarify; this is the intended behavior of CharacterAdded.
The CharacterAdded event fires when, as the name implies, the character is added. This means that if you connect anything to the event after the player spawns in, the event won’t fire until next time the player respawns. This is why you never use CharacterAdded by itself for this usecase. You will almost always see people use some variation of

local Character = Player.Character or Player.CharacterAdded:Wait()

This only makes use of the event, if the player is not already spawned in, and thus circumvents the issue you described.

Checking the Parent of Player.Character as shown in the topic does not achieve anything.