In-game face accessory vanishes after player receives it

In my game, the player is given a basic face accessory. The player can then upgrade and equip new face accessories.

Problem:
When testing in studio and testing alone in the game, I can equip the face accessory without an issue. When I enter the game, I get the basic one and then go to the shop to equip a new one. But when there are multiple players, the face accessory vanishes after a few seconds. When I equip a new one, I can see it for a moment and then it disappears as well.

  1. I’ve tried removing any face accessory from the players first (that they wear when entering the game) and then giving the new in-game accessory. I have also tried to just give the accessory. I’ve also removed the module being called when character is added or player appearance loaded (it’s just in PlayerAdded and the shop function now), but I always get the same result.
--// ServerScript
local function onPlayerAdded(Player)
	-- different code here --
	local character = Player.Character or Player.CharacterAdded:wait()
	WeldStorage2.AttachToBack(Player, StorageFolder:GetChildren()[1])
end
	
game.ReplicatedStorage.Remotes.ShopDone.OnServerEvent:Connect(function(Player)
	local PlayerDataFolder = PlayerData[Player.Name]
	local ToolHolder = Player.HoldingFolder
	if ToolHolder:GetChildren()[1] then
		ToolHolder:GetChildren()[1].Parent = Player.Backpack
	end
	
	Player.Character.HumanoidRootPart.Anchored = false
	WeldStorage2.AttachToBack(Player, PlayerDataFolder.ItemFolder.StorageFolder:GetChildren()[1]) 
end)

--//Module
local WeldStorage2 = {}

local OldObject = nil -- set previous storage to false for when player joins game

function WeldStorage2.AttachToBack(player, object)
	if OldObject then OldObject:Destroy() end
	
	local char = player.Character or player.CharacterAdded:wait()
	
	local humanoid = char:WaitForChild("Humanoid")
	local objectClone = object:Clone()

	humanoid:AddAccessory(objectClone)
	print(object.Name.." was added")

	OldObject = objectClone
	
end
return WeldStorage2

This is expected because when a module is required after the first time it will return the same thing it did the first time. That being said, it means that OldObject will be shared among all players the method is called on. Simply change OldObject to a table where the key is the player and the value is the object.

Although this will cause another issue! We need to clear the index whenever the player leaves to avoid a memory leak. You can do this by adding a method to clear the index and removing it when the player leaves the game via PlayerRemoving.

Thank you for the quick response!
Could I create a function within the script then instead of calling a module? And would I have to remove existing face accessories first or would they be removed because the player can only have one at a time?

Like this:
–// ServerScript
local OldObject = nil -- set previous storage to false for when player joins game

local function faceMask(player, object)
	if OldObject then OldObject:Destroy() end
	
	local char = player.Character or player.CharacterAdded:wait()
	
	local humanoid = char:WaitForChild("Humanoid")
	local objectClone = object:Clone()

	humanoid:AddAccessory(objectClone)
	print(object.Name.." was added")

	OldObject = objectClone
	
end
faceMask(Player, PlayerDataFolder.ItemFolder.StorageFolder:GetChildren()[1])

The issue isn’t associated with the module. It doesn’t make much of a difference if it’s within the script or the module (it really just depends on if the functionality needs to be used in more than just that script). OldObject has to be a table in order to separate the face masks of each individual player.

Here’s how I would do it:

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

local playerMasks = {}

function setMask(player, object)
	if playerMasks[player] then
		playerMasks[player]:Destroy()
		playerMasks[player] = nil
	end

	local character = player.Character or player.CharacterAdded:Wait()
	local humanoid = character:WaitForChild("Humanoid")
	local clonedObj = object:Clone()

	humanoid:AddAccessory(clonedObj)

	playerMasks[player] = clonedObj
end

Players.PlayerRemoving:Connect(function(player)
	if playerMasks[player] then
		playerMasks[player] = nil
	end
end)

Note: this pattern can be very repetitive so I’d suggest tracking it all in one place since you have to clear the indexes to prevent the memory from being leaked.

2 Likes

Thank you so much!! I have learned something new now and this fixed the issue completely.