Why aren't some parts of my character turning invisible?

how would the reliability of using an arbitrary wait value compare to this method? like are there any drawbacks to using it? as far as I’m concerned it works alright and is way simpler anyway

1 Like

I won’t be able to explain the technical details behind it since I’m not as knowledgeable about the specific inner-workings, but I can offer a general overview:


  • Arbitrary wait time

    • No guarantee that it will wait for a long enough amount of time.

    • Not flexible to different situations. It might wait the perfect amount of time for one user, but not another.

    • The longer the wait, the more likely it is to guarantee it has waited for enough time. However, this comes at the expense of the user experience. As players wait longer before having visual feedback that something has worked / reached its intended state, the game may feel less polished from the perspective of the player.

And going back to something you mentioned in the original post:

Although I don’t know many details about the intended gameplay for your game, if that few seconds of the Character being visible upon respawning has the potential of providing a gameplay advantage for someone in the game, then waiting for an arbitrary amount of time will always leave the door open for the average player to take advantage of that.


  • Events (in this case, CharacterAppearanceLoaded)
    • On the server-side, guaranteed to fire once the Character’s appearance has loaded.

    • Flexible to different situations / loading times. Whether a Character’s appearance loads almost instantly, or if it takes a few seconds, the event will fire at the right time.

    • Because the relevant code for updating the Character’s visibility would run as soon as the event fires, this would provide nearly instantaneous visual feedback for the player while simultaneously lessening the likelihood that players would be able to take advantage of the timeframe where the Character model would be visible.


Aside from the odd, inconsistent bug that prevents it from working specifically on the client-side for some users (which is a glitch which doesn’t happen with practically all other client-side-compatible events, and is something that should have been resolved by now) I can’t think of any common drawbacks for using the event over a wait.

The only situation I can think of where there could be an issue would be if Roblox’s Avatar services go offline while games are still playable (which very rarely happens) causing Character appearances to never load. However, for something like that, it would be best to have additional code to fall back to rather than having the entirety of the code revolve around a wait.

  • An example implementation of that could be to store a true or false value for each player that would indicate if their Character model is invisible. It would be set to true after the CharacterAppearanceLoaded event fires.

    Before that event fires, though, a separate CharacterAdded event would fire, setting the value to false and then starting a timer with the RunService. If the value is set to true, the timer can be stopped because the event fired. However, if it’s not set to true after a given amount of time, that would indicate that CharacterAppearanceLoaded never fired. As a result, it can be instructed to run the same code that would loop through the Character model and make it invisible.

    If you want it to be even more reliable, you could connect the DescendantAdded event to the Character model so that it will be able to detect the addition of any new Accessories, BaseParts, Decals, etc. in order to destroy it or update its Transparency right away (even if the CharacterAppearanceLoaded event had already fired).

    All of this helps ensure that the Character model would become invisible, even in unexpected situations.


Since it appears to be reliable when used on the server-side, will immediately fire the event and run the code as soon as it’s ready, alongside the benefit of automatically replicating the visibility changes to every player in the game (because it’s being run on the server side), utilizing CharacterAppearanceLoaded for this use case provides much more reliable and timely visual feedback for the players in the game, in comparison to adding a wait before looping through the Character model.


Although adding a wait works in theory and it could work in the vast majority of situations, it is generally advised to make use of events where possible, as events are much more versatile and are designed in a way to make it faster and easier for the code to respond / react to what’s happening in the game.

Ultimately, for a gameplay feature as simple as this, it’s unlikely for there to be many long-term consequences that could negatively affect the game as a whole by using wait over an event. However, it does have an impact on future gameplay design decisions you might make and how one’s coding practices and relevant skills develop.

If relying on wait becomes a common solution in your scripting toolbox, it could inadvertently impact other parts of the game that are created in the future. What could start with a few seconds of waiting after the Character respawns could turn into cumulative minutes of unnecessary waiting for a variety of things to happen in the game if other features are also designed around waits where events could be used.

On a similar note, waits are often considered to be a “code smell” by other developers; that thread provides some insight into some alternatives of wait with adjacent examples.


There are still valid use cases for waits and waiting through the RunService, especially in circumstances where there’s code that needs to be run indefinitely, but for situations like these where there’s an event that is really suitable for the use case, events usually end up making it possible to create a more robust implementation of specific gameplay features.


I can’t force you to use one option over the other, but I hope I was able to provide some valuable insight about this subject matter.


TL;DR / Key Takeaways

  • Use event-based programming when possible.

  • waits are not bad on its own; it depends on how and when it’s being used.

    • *(side note, wait() has been deprecated in favor of the newer task.wait(). It’s recommended to replace existing wait() calls with task.wait(), if not swapping to other methods of yielding).
1 Like

alright i might try the serverscriptservice method later, and i don’t mean to be a nuisance, but what about using repeat wait() until character? Is it basically the same as using that arbitrary wait value?

actl nvm i just started reading the article you linked :skull:

Okay, so the localscript is giving me hell. Say that I only wanted to make the player invisible on the client, but not to anyone else. should i modify the script above to instead fire a remote event and insert a local script in starterPlayerScripts to listen for it? (p.s., why wouldn’t you put in in starterCharacterScripts)

1 Like

Yup! That’s even what a Roblox Staff Member mentioned as a workaround for the use case of reliably letting the client know that CharacterAppearanceLoaded has fired:


Here’s an example of how that could be achieved:

Codeblock #1 (Server Script)

-- Code for a Server Script placed into the ServerScriptService
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")


local RemoteEvent = ReplicatedStorage.RemoteEvent
local function onPlayerJoin(player)
	player.CharacterAppearanceLoaded:Connect(function(Character)
		RemoteEvent:FireClient(player, Character)
	end)
end

for _, player in Players:GetPlayers() do
	task.spawn(onPlayerJoin, player)
end
Players.PlayerAdded:Connect(onPlayerJoin)

Codeblock #2 (LocalScript)

-- Code for a LocalScript placed into the StarterPlayerScripts
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local RemoteEvent = ReplicatedStorage.RemoteEvent
RemoteEvent.OnClientEvent:Connect(function(Character)
	
	for _, v in Character:GetDescendants() do
		
		if v:IsA("Accessory") then
			v:Destroy()
			
		elseif v:IsA("BasePart") or v:IsA("Decal") then
			v.Transparency = 1
		end
		
	end
	
end)

If a script can be created just once and achieve the same thing rather than having it be cloned into the Character model every time they spawn, then that helps simplify things so there’s only 1 LocalScript created per player throughout the duration they remain in the server, instead of it being re-created dozens or even hundreds of times.


As of writing this post, Character models do not have :Destroy() called on them by default when a new one needs to spawn (it’s only set to nil), so as far as I understand, the server memory could build up faster than it would otherwise since there are extra scripts and event connections in existence within the despawned Character models.

1 Like

The LocalScript is throwing me errors. I’ve tried before and it seems that I can’t get the player’s character from starterPlayerScripts. Apologies if this is becoming a drag

1 Like

Hmmm, although I’m not super sure why that would be happening, a possible solution could be to change how the Character is referenced in the LocalScript. It could be modified a bit to access it through the LocalPlayer rather than trying to reference the one sent through via the RemoteEvent.

This means that the server script would only be firing an empty RemoteEvent to the player for the sole purpose of indicating that the CharacterAppearanceLoaded event has fired, without sending through the Character model it returns. Once the client receives that, it’ll reference the Character on its own and continue.


In the Server Script, the only thing that would need to be changed is when it calls :FireClient():

-- Before
RemoteEvent:FireClient(player, Character)

-- After
RemoteEvent:FireClient(player)

In the LocalScript, how the Character is referenced in the function would be changed to access the Character through the Player object:

-- Revised LocalScript code for the StarterPlayerScripts
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local player = Players.LocalPlayer
local RemoteEvent = ReplicatedStorage.RemoteEvent

RemoteEvent.OnClientEvent:Connect(function()
	local Character = player.Character or player.CharacterAdded:Wait()

	for _, v in Character:GetDescendants() do
		
		if v:IsA("Accessory") then
			v:Destroy()
			
		elseif v:IsA("BasePart") or v:IsA("Decal") then
			v.Transparency = 1
		end
		
	end
	
end)

No worries! This has been a fun experience actually; I like the questions that you’ve been asking to learn more about the reasoning behind my suggestions. Helps to refine all of our understanding about the subject and also gives me some practice for explaining ideas that I might not have thoroughly explained before or explained that often.

Still not working. However, it partially works after you reset, only that it prints the body parts but doesn’t make them invisible. Curiously, it’s not picking up any accessories. I’ve disabled every script except for the server script and this one and nothing changed.

1 Like

i’m starting to think roblox studio has something against me

1 Like

Honestly, I am super stumped now lol. With all of these different setups, whether it’s just a LocalScript, just a server Script, or a mixture of the two, I haven’t been able to replicate the broken behavior (it’s worked every time during my own testing).

What happens if you try testing it in a live game? Maybe it’s different in Roblox Studio for some reason but works as intended in the published version of the game.


I’ve exhausted what seems like all the options without adding an arbitrary delay back into the code, so hopefully it just happens to work properly in a live server?

Same behaviour??

1 Like

maybe you could try publishing a place of your own and see if the script works

1 Like

Oh yeah have a go at it and see if you turn invisible

1 Like

The game is set to private so it isn’t letting me join.


If I join afterwards and my Character goes invisible and your Character model doesn’t, that will be even more unbelievably confusing haha.

I had just gone into Roblox Studio and added your Character model into the Workspace as both R6 and R15 RigTypes and tested a slightly modified version of the code to see if it could make your Character model fully invisible right away or if it encountered any issues and it made both invisible so I’m still super confused lol.

1 Like

my bad gonna make it public real quick

1 Like

alright just made it public :japanese_ogre: :japanese_ogre: :japanese_ogre: :japanese_ogre: :japanese_ogre: :japanese_ogre:

1 Like

I am so confused right now; the same thing happened as in the screenshot you posted earlier. The same things are printing out in the Developer Console (excluding all Accessories), and my Character wasn’t turned invisible.

Not sure what’s working differently there when I wasn’t able to replicate that in any of my own places / experiences that I tested the code in.

you mean you played my game and the same thing that happened to me happened to you?

1 Like

btw do you want to just check the actual place out on studio? i could post it here

1 Like