I’m back here again because something doesn’t work
Here’s the issue:
A game I’m working on has a shop, and when the player enters the shop I want a random shopkeeper to appear which is an avatar model, but only locally. The avatars are stored in a folder in workspace called ShopNPCs.
I have an invisible and cancollide = false hitbox in the shop, and I’m trying to make the random NPC appear when the player touches the box, and disappear when they leave.
Here’s the code (localscript):
local hitBox = script.Parent
function disappear()
for _, v in NPC:GetChildren() do
if v:IsA("Part") then
if v.Name ~= "HumanoidRootPart" then
v.Transparency = 1
end
end
if v:IsA("Accessory") then
for _, v in v:GetChildren() do
if v.Name == "Handle" then
v.Transparency = 1
end
end
end
NPC.Head.face.Transparency = 1
end
end
function reappear()
for _, v in NPC:GetChildren() do
if v:IsA("Part") then
if v.Name ~= "HumanoidRootPart" then
v.Transparency = 0
end
end
if v:IsA("Accessory") then
for _, v in v:GetChildren() do
if v.Name == "Handle" then
v.Transparency = 0
end
end
end
NPC.Head.face.Transparency = 0
end
end
hitBox.Touched:Connect(function(part)
local player = game.Players.LocalPlayer
local randomnum = math.random(0, 1)
if randomnum == 0 then
NPC = game.Workspace.ShopNPCs.NPC1
end
if randomnum == 1 then
NPC = game.Workspace.ShopNPCs.NPC2
end
if player and player.Character and player.Character.PrimaryPart == part then
reappear()
end
end)
hitBox.TouchEnded:Connect(function(part)
local player = game.Players.LocalPlayer
if player and player.Character and player.Character.PrimaryPart == part then
disappear()
end
end)
Unfortunately, it doesn’t work.
Can anyone help me out here? I’d greatly appreciate it!
(P.S. The players enter and leave the shop via a character.HumanoidRootPart.CFrame = CFrame.new(partToTeleportTo.Position) command after a proximityprompt is activated. Just in case that’s relevant.)
I’m pretty sure you need to store the NPC in ServerStorage then you can just clone it from there.
Saves the for _,v loops
Then you can just destroy the clone.
Also you can clean up your code with
if randomnum == 0 then
NPC = game.Workspace.ShopNPCs.NPC1 --and reset this path link to the ServerStorage file.
else
NPC = game.Workspace.ShopNPCs.NPC2 -- see above.
end
I don’t think this script is running at all. Local scripts have limited places to run. You can use “Script” (not a local script) and set “RunContext” to “Client”.
You can try this code but I didn’t test it in studio:
local Players = game:GetService("Players")
local npcs = {game.Workspace:WaitForChild("ShopNPCs"):WaitForChild("NPC1"), game.Workspace:WaitForChild("ShopNPCs"):WaitForChild("NPC2")}
local hitBox = script.Parent
local localPlayer = Players.LocalPlayer
local visible = false
local function hideNpc()
for _, npc in npcs do
for _, part in npc:GetDescendants() do
if not part:IsA("BasePart") or part.Name == "HumanoidRootPart" then continue end
part.Transparency = 1
end
NPC.Head.face.Transparency = 1
end
end
local function showNpc()
local npc = npcs[math.random(1, #npcs)]
for _, part in npc:GetDescendants() do
if not part:IsA("BasePart") or part.Name == "HumanoidRootPart" then continue end
part.Transparency = 0
end
NPC.Head.face.Transparency = 0
end
hitBox.Touched:Connect(function(part)
local player = Players:GetPlayerFromCharacter(part:FindFirstAncestorOfClass("Model"))
if not player or not player == localPlayer or visible then return end
showNpc()
visible = true
end)
hitBox.TouchEnded:Connect(function(part)
local player = Players:GetPlayerFromCharacter(part:FindFirstAncestorOfClass("Model"))
if not player or not player == localPlayer or not visible then return end
hideNpc()
visible = false
end)
So I got it to work; partially. I used the clone() idea along with the serverscript runcontext being client. Here’s my code:
local hitBox = script.Parent
local isvis = false
hitBox.Touched:Connect(function(part)
local player = game.Players.LocalPlayer
if player and player.Character and player.Character.PrimaryPart == part then
local randomnum = math.random(0, 1)
if isvis == false then
if randomnum == 0 then
NPC = game.ReplicatedStorage.ShopNPCs.npc1:Clone()
NPC.Parent = workspace
isvis = true
end
if randomnum == 1 then
NPC = game.ReplicatedStorage.ShopNPCs.npc2:Clone()
NPC.Parent = workspace
isvis = true
end
end
end
end)
hitBox.TouchEnded:Connect(function(part)
if isvis == true then
NPC:Destroy()
isvis = false
end
end)
The only issue is that when the player jumps, the shop NPC changes. How can I prevent this?
Debounces = {}
part.Touched:connect(function(player)
if not Debounces[player] then
Debounces[player] = true
print("Touched")
task.wait(2)
Debounces[player] = nil
end
end)
Or you can do this, this will only fire once. When player touches the Part.
And will only fire again, once the player is no longer touching the Part
local AlreadyTouched = {}
part.Touched:connect(function(player)
if not AlreadyTouched[player] then
AlreadyTouched[player] = true
end
end)
part.TouchEnded:connect(function(player)
AlreadyTouched[player] = nil
end)
Adding onto this, since the Touched and TouchEnded connections are temperamental, and dont fire sometimes. You could do this
local AlreadyTouched = {}
local RunService = game:GetService("RunService")
part.Touched:connect(function(player)
if not AlreadyTouched[player] then
AlreadyTouched[player] = true
local RunServiceConnection
local overlap = OverlapParams.new()
overlap.FilterDescendantsInstances = {player}
overlap.FilterType = Enum.RaycastFilterType.Include
local function checkStatus()
local Parts = game.Workspace:GetPartsInPart(part,overlap)
if #Parts <= 0 then
AlreadyTouched[player] = nil
RunServiceConnection:Disconnect()
end
end
RunServiceConnection = RunService.Heartbeat:Connect(checkStatus)
end
end)
I was actually able to fix this problem using a random post I found on the devforum.
Here’s my code (in a serverscript where runcontext is client):
local hitBox = script.Parent
local isvis = false
hitBox.Touched:Connect(function(h)
if h and h.Parent:FindFirstChild("Humanoid") then
if isvis == false then
local randomnum = math.random(0, 1)
if randomnum == 0 then
NPC = game.ReplicatedStorage.ShopNPCs.npc1:Clone()
NPC.Parent = workspace
isvis = true
print("npc1")
end
if randomnum == 1 then
isvis = true
NPC = game.ReplicatedStorage.ShopNPCs.npc2:Clone()
NPC.Parent = workspace
print("npc2 is shopkeeper")
end
end
repeat wait() until game.Players:GetPlayerFromCharacter(h.Parent):DistanceFromCharacter(hitBox.Position) > 70
NPC:Destroy()
isvis = false
wait(1)
print("NPC destroyed!")
end
end)
The only thing I will say is that it prints the NPC destroyed! message like, 7 times whenever I leave the shop using the teleporter, but I don’t think it will cause the issue (maybe clogging up the output possibly).
Any feedback on this is welcome (just in case this is secretly gonna cause the worst performance issues or something )
I wouldn’t use this code This will cause lag. As it creates a loop every time the Touched event is fired. (Which will happen alot if the player is moving ever so slighty while in the Hit Box of the Part)
local AlreadyTouched = {}
local RunService = game:GetService("RunService")
local isvis = false
hitBox.Touched:connect(function(part)
local Humanoid = part.Parent:FindFirstChild("Humanoid")
if Humanoid then
if not AlreadyTouched[Humanoid] then
local player = Humanoid.Parent
AlreadyTouched[Humanoid] = true
if isvis == false then
local randomnum = math.random(0, 1)
if randomnum == 0 then
NPC = game.ReplicatedStorage.ShopNPCs.npc1:Clone()
NPC.Parent = workspace
isvis = true
print("npc1")
end
if randomnum == 1 then
isvis = true
NPC = game.ReplicatedStorage.ShopNPCs.npc2:Clone()
NPC.Parent = workspace
print("npc2 is shopkeeper")
end
end
local function PlayerUnTouched()
NPC:Destroy()
isvis = false
end
local RunServiceConnection
local overlap = OverlapParams.new()
overlap.FilterDescendantsInstances = {player}
overlap.FilterType = Enum.RaycastFilterType.Include
local function checkStatus()
local Parts = game.Workspace:GetPartsInPart(hitBox,overlap)
if #Parts <= 0 then
PlayerUnTouched()
AlreadyTouched[Humanoid] = nil
RunServiceConnection:Disconnect()
end
end
RunServiceConnection = RunService.Heartbeat:Connect(checkStatus)
end
end
end)```
I’m not sure about more efficient. My code doesn’t need to calculate distances which might help performance, idrk? But it is more precise as it would destroy as soon as player leaves the part
Instantaneous destruction doesn’t matter because the door to the shop takes like 4 seconds to exit and then renter, so there is a bit of leeway with the destruction.
I won’t change my code but I will mark yours as the solution for being the “best practice”