Using RemoteFunctions to let a Player type their "nametag"

Hi there! I’m very new to scripting (in general, not just Lua), and I am trying to accomplish something for a project I have in mind. However, I just cannot grasp how LocalScripts, RFs and server Scripts interact with eachother, and would appreciate some more direct help.

So, I would like to create a “RP Name” feature, often seen in roleplay, or simulator games (RHS and Meepcity are two well known examples). I chose to code it myself so I could design the UI, add the features I want and actually understand what I am doing.

I have a collapsible GUI that looks like this;

And a Overhead GUI code, that currently runs on the Player’s username, that looks like this;

--Load the usertag
local usertag = game:GetService("ServerStorage"):WaitForChild("Usertag")

--Add the usertag when join
game.Players.PlayerAdded:Connect(function(player)
	
	--Add the usertag when respawn
	player.CharacterAdded:Connect(function(character)
		
		if player.Name == "ninjisy" then
			--Add the usertag to the determined player + change it
			local clonedtag = usertag:Clone()
			clonedtag.TextLabel.Text = "Ninjisy"
			clonedtag.TextLabel.TextColor3 = Color3.fromRGB(119,83,208)
			clonedtag.TextLabel.Font = "GothamBold"
			clonedtag.Parent = game.Workspace:WaitForChild(player.Name).Head			
		end
		
	end)
	
end)

I would like to connect these two objects with a RemoteFunction, that would allow the Overhead GUI to display what the user types in the box. This is what I cannot figure out at all.

The furthest I got is the LocalScript attached to the TextBox;


nameBox.Changed("Text"):Connect(function()
	game.ReplicatedStorage.NameChangeRE:FireServer()
end)

Could somebody give me any hint on where I should go next? How do I write a server Script that catches this fired event, and is able to get the text inputted into the box? Thank you for any help.

1 Like

Here’s some tasks you can follow to accomplish your goal:

  1. Create a RemoteEvent somewhere in ReplicatedStorage named SetRpName
  2. On the server, when the player joins, create a value to hold the player’s name. This can be a StringValue object in their Player or just a variable in a table somewhere. Using a StringValue will probably be easiest for you right now while learning.
  3. On the client, listen for when the user presses enter in the RP Name box. You can look up TextBox on the devhub to find out how to do this. Alternatively, use a click button.
  4. On the client, when the user presses enter, call SetRpName:FireServer(name) where name is the name the player put in and SetRpName is a reference to the SetRpName RemoteEvent (e.g. game.ReplicatedStorage.SetRpName)
  5. On the server, connect to SetRpName's OnServerEvent: SetRpName.OnServerEvent:Connect(function(player, name) ... end)
  6. On the server, when SetRpName is fired, change the player’s RpName value you made in step 2 to the given name.
  7. On the server, when the name is updated, also update their nametag with the new RpName.
  8. On the server, when the character spawns, use the RpName value from step 2 to set the nametag text.

This then follows the flow in-game:

  1. Player types in rp name
  2. Player presses enter, and FireServer is called with the name
  3. The server receives the name in OnServerEvent
  4. The server sets the RpName value
  5. The server updates the nametag
  6. The server creates the correct nametag on spawn

There is a lot missing here; this is just an example for you to learn from! If you use this in a live game then you will need to add text filtering and you will need to limit players from setting extremely long names. Both of those things should be done on the server.

Don’t forget that if things aren’t working as you would expect then you can add print statements to key location (e.g. event connections) to investigate what’s going on!

9 Likes

It’s implied in the reply above, but worth drawing attention to the fact that you need to both capture and send the data from the namebox. I don’t suggest doing this on the Changed event as you’ll be spamming the server with half-finished names, but for the sake of demonstration I’ve kept it as close to your original as possible:

nameBox.Changed("Text"):Connect(function( enteredText )
    game.ReplicatedStorage.NameChangeRE:FireServer( enteredText )
end)

Then, as Corecii mentioned above, on the server you’d want something like:

game.ReplicatedStorage.NameChangeRE.OnServerEvent:Connect(function( player, enteredText )
    -- Validate the entered text here, including passing it through the filtering service.

    print( enteredText ) -- for debug purposes, output to the server log.
end)

Hopefully that demonstrates the basic principle. As Corecii suggested, perhaps only send when the player presses Enter/Return (or focus is lost from the TextBox - see FocusLost).

1 Like

Thank you tons for your replies, I’ve been able to get a lot further than I was without your help!

I’ve gotten the RE to work (confirmed with print debugging), but I am now a bit caught up with actually getting the entered text, and then assigning it to the string.

Here is my TextBox’s new script;

local nameBox = script.Parent

nameBox.FocusLost:Connect(function(enterPressed)
	game.ReplicatedStorage.SetRpName:FireServer(enteredText)
	print("Event Fired")
end)

And here is my work in progress Server Script;

game.Players.PlayerAdded:Connect(function(player)
	local rpName = Instance.new("StringValue", player)
end)

game.ReplicatedStorage.SetRpName.OnServerEvent:Connect(function(player, enteredText) 
	print( enteredText )
	--value and GUI code to be added here(?)
end)

The code runs, but all it prints is nil. So it’s not getting the actual text inputted - what am I missing here?

In your textbox script, because the event that you’re using doesn’t return the text, you’ll need to define the variable enteredText, or replace it with nameBox.Text

Thank you, that fixed that problem!!

I’ve gotten the Server to read the text inputted and put it on a nametag! Hooray!
But now, any time I input a new name, it creates another nametag rather than changing the old one. This makes sense considering the code, but I can’t find a method to have it check if there’s already a nametag, and update it if so. I’ve tried quite a few different ways, but none of them work. Do you have any insight?

Here is the script now;

game.Players.PlayerAdded:Connect(function(player)
	local rpName = Instance.new("StringValue", player)
end)


game.ReplicatedStorage.SetRpName.OnServerEvent:Connect(function(player, name) 
	print(name)
	rpName = name
	print(rpName)
		local usertag = game:GetService("ServerStorage"):WaitForChild("UserTag")
		local clonedtag = usertag:Clone()
		clonedtag.TextLabel.Text = rpName
		clonedtag.Parent = game.Workspace:WaitForChild(player.Name).Head
end)

Here is what happens when I put in another name;
Screenshot_316

(P.S. I hope I’m not asking too many questions, or coming off as though I want someone to code this for me! I have definitely been trying lots of different methods (even if I think they’re silly) before asking for help. I greatly appreciate the advice, I find it hard to take a lot of the general scripting tutorials out there and use them for my own purposes, I tend to learn things in a very frustrating manner haha.)

You can use FindFirstChild to check if something already exists

local usertag = game:GetService("ServerStorage"):WaitForChild("UserTag")

game.Players.PlayerAdded:Connect(function(player)
	local rpName = Instance.new("StringValue", player)
end)


game.ReplicatedStorage.SetRpName.OnServerEvent:Connect(function(player, name) 
	print(name)
	rpName = name
	print(rpName)
	local head = player.Character.Head

	local clonedtag = head:FindFirstChild("UserTag") or usertag:Clone()
	clonedtag.TextLabel.Text = rpName
	clonedtag.Parent = head
end)

The or works nicely because clonedtag is either set to “UserTag” found inside of the player’s head or it is cloned from ServerStorage. Also, instead of looking in workspace for player.Name you can just use player.Character.

Looks like you aren’t destroying the previous nametag. Simply finding the tag and destroying it should do the trick. Also, if you don’t want to get your game deleted, you’re going to need to filter the text string using TextService:FilterStringAsync(). Here’s what I would recommend using:

game.ReplicatedStorage.SetRpName.OnServerEvent:Connect(function(player, name) 
    print(name)
    if game:GetService('TextService'):FilterStringAsync(name,player.userId,Enum.TextFilterContext.Public) == name then --determining if filtered string is same as normal string
	    rpName = name
	    print(rpName)
		local usertag = game:GetService("ServerStorage"):WaitForChild("UserTag")
		local clonedtag = player.Character.Head:FindFirstChild('UserTag') or usertag:Clone() --selects current tag or makes new one
		clonedtag.TextLabel.Text = rpName
		clonedtag.Parent = game.Workspace:WaitForChild(player.Name).Head
    end
end)

note: I haven’t tested this, but it should work.

1 Like

Thank you Vulkarin and Computer2467, that’s exactly what I was looking for in regards to the previous nametag!

@Computer2467 - I can’t seem to get the filter to work? I checked the documentation, and tried changing ‘Public’ to ‘PublicChat’ to see if that helped, but for whatever reason, it just brings the script to a halt - I don’t get the rpName print anymore. I would definitely like it filtered, so I can use it in my project.

Also, when it comes to selecting the answer for your topic, is there a general rule on which I should choose if several people have helped solved my issues? Should I select Corecii’s answer, as it’s the most concise response in terms of the whole process?

If several people answered your question, I would usually just include quotes of them in your own post and mark that as your answer. As for how its stopped working, that’s because the script (as of right now) doesn’t do anything if the string does get filtered. You should just to be able to add an else to it, like so:

    print(name)
    if game:GetService('TextService'):FilterStringAsync(name,player.userId,Enum.TextFilterContext.Public) == name then --determining if filtered string is same as normal string
	    rpName = name
	    print(rpName)
		local usertag = game:GetService("ServerStorage"):WaitForChild("UserTag")
		local clonedtag = player.Character.Head:FindFirstChild('UserTag') or usertag:Clone() --selects current tag or makes new one
		clonedtag.TextLabel.Text = rpName
		clonedtag.Parent = game.Workspace:WaitForChild(player.Name).Head
   else
       error('Text filtered! Original string: '..name..' filtered string: '..game:GetService('TextService'):FilterStringAsync(name,player.userId,Enum.TextFilterContext.Public)) --replace this line with whatever you want to do when the string gets filtered
   end
end)

I wasn’t able to concatenate the filtered string (as its a userdata value, it says), so I changed it to a generic error just to see - it appears that every single time I attempt to send the name value, it runs to the else error. Everything works stellar without the filtering, so I think I must be overlooking something?

Out of curiosity, I tried using the FilterStringToBroadcast function instead (though I understand its for localscripts), the result of this was that the name value went through fine and assigned the nametag correctly, but did not filter the text.

I’m pretty much at a loss here, I even tried running a server and publishing the place to see if being offline was causing it, but no dice.

Here is my code as of now;

game.Players.PlayerAdded:Connect(function(player)
	local rpName = Instance.new("StringValue", player)
end)


game.ReplicatedStorage.SetRpName.OnServerEvent:Connect(function(player, name) 
	print(name)
	if game:GetService('TextService'):FilterStringAsync(name,player.userId,Enum.TextFilterContext.PublicChat) == name then
		local rpName = name
		print(rpName)
		
		local head = player.Character.Head
		local usertag = game:GetService("ServerStorage"):WaitForChild("UserTag")
		local clonedtag = head:FindFirstChild("UserTag") or usertag:Clone()
		clonedtag.TextLabel.Text = rpName
		clonedtag.Parent = head
	 else 
		error('Text filtered!')
	end
end)

Ah, I think I figured it out. After doing a bit of reading on the wiki, it actually returns a TextFilterResult instance which you can’t simply index as a string, silly me. Calling the function :GetNonChatStringForBroadcastAsync() should turn it into a string, so try this instead:

	local rpName = Instance.new("StringValue", player)
end)


game.ReplicatedStorage.SetRpName.OnServerEvent:Connect(function(player, name) 
	print(name)
	if game:GetService('TextService'):FilterStringAsync(name,player.userId,Enum.TextFilterContext.PublicChat):GetNonChatStringForBroadcastAsync() == name then
		local rpName = name
		print(rpName)
		
		local head = player.Character.Head
		local usertag = game:GetService("ServerStorage"):WaitForChild("UserTag")
		local clonedtag = head:FindFirstChild("UserTag") or usertag:Clone()
		clonedtag.TextLabel.Text = rpName
		clonedtag.Parent = head
	 else 
		error('Text filtered! Original string: '..name..' filtered string: '..game:GetService('TextService'):FilterStringAsync(name,player.userId,Enum.TextFilterContext.Public)):GetNonChatStringForBroadcastAsync() --replace this line with whatever you want to do when the string gets filtered
	end
end)

I’m pretty confident that this should finally work. If it doesn’t, then I’m at a loss for how to fix it.

THANK YOU SO MUCH, that solved it completely! Thank you for finding that, I couldn’t find that info anywhere. I confirmed it filters when published!

Now I have some extra features I want to implement, but all in all, thank you all for the assistance. You are all extremely helpful, and I’ve learnt so much from figuring this out.

For the sake of others, here are the solutions I feel summarize the method;

3 Likes