Hello there DevForum members,
Usuaully im really good with programming but today I can’t grasp my mind over how to do a concept or what I need to do to achieve it.
I have already browsed around looking for solutions to do predictive text of ingame user’s usernames to my luck finding nothing except examples, I have tried reaching out to people who have achieved this but with no response.
Today my goals and achievements are:
To find out how to make predictive textbox’s relating to ingame usernames.
If anyone could shed some light on how to achieve this it would be greatfully appreciated,
I would also like it to show the full username of the prediction under “placeholder” and then if they press enter it would continue and put that as the user registered.
Thanks and I hope to seeing some constructive solutions!
If you want to see if a certain username contains a search, (ignoring casing) you could use the following:
local search = 'Bob'
local username = 'ohboB'
if string.lower(username) ~= string.gsub(string.lower(username), string.lower(search), '' then
--Matches slightly
end
That’s the simplest way you can do it, but if you want to use more advanced ways, the reply below will likely contain them:
local sorter = function(a, b)
return a.Pos > b.Pos
end
local closestPlayerNames = function(myText)
local closestMatches = {}
local fpos
myText = myText:lower()
for _, player in ipairs(Players:GetPlayers()) do
fpos = player.Name:lower():find(myText, 1, true)
if fpos then
closestMatches[#closestMatches + 1] = {Pos = fpos, Player = player}
end
end
table.sort(closestMatches, sorter)
return closestMatches
end
This is also good because it can find between names, so “abby” can match “ImNot_AbbyLol”
It is supposed to sort by find position too, so it puts “Wunder_Wulfe” ahead of “Some_Wulfe” if you type “wu” but still puts them both in the list. Then you can do something like
for i, match in ipairs(closestMatches) do
print(i, match.Player)
end
Hm I dont want to do it specifically like this, I want it to specifically search for the user from the start of the user and register from the next letters, not a search.
For example it would find “ohboB” from “oh” not “bob” and you register more letters to get a new search that is close to the final username.
So you just want to check the beginning? You can do this:
local search = 'oH'
local username = 'ohboB'
if string.len(search) < string.len(username) then --Otherwise, out of bounds errors
if string.lower(username).sub(1, string.len(search)) == string.lower(search) then
--Yay!
end
end
@OP said his use case required only the first bit to work. Additionally, your method does not sort based on the matching, it just gives the matches with their starting indexes sorted, which isn’t what the @OP wanted.
I don’t see what you mean, it correctly sorts all the usernames by which one matches its contents first. Likely if you want to search “noob”, you are looking for something that has “noob” earlier in their username than others do. If you search between “I_am_noob”, “noob_master” and “big_noob”, it sorts with “noob_master”, “big_noob”, “I_am_noob”.
Is what they want. I didn’t say your sorting doesn’t work (it does, goob job), and it will for a different use case or if it was modified, but that doesn’t make your solution any better when it strays from what the @OP wants.
This discussion is derailing the topic, so if you want to continue it we should in PMs.
Here’s a really over-simplified algorithm. I used this sort of method for a small-scale call center application at my job recently. Basically you just check if the searched string contains part of the name (or vice versa). In the “Display results” loop near the bottom, you could update a UI instead of printing the names.
It determines the score simply by how much of a match was made. Perfect matches score larger. It does this simply by finding the absolute size difference between the two strings. Like I said, this is an over-simplified algorithm, but it’s very fast.
local maxResults = 4
textBox:GetPropertyChangedSignal("Text"):Connect(function()
local results = {}
local search = textBox.Text:lower()
-- Score results:
for _,player in ipairs(game.Players:GetPlayers()) do
local name = player.Name:lower()
if (name:find(search, 1, true) or search:find(name, 1, true)) then
local score = math.abs(#name - #search)
table.insert(results, {player, score})
end
end
-- Sort by score:
table.sort(results, function(a, b)
return (a[2] < b[2])
end)
-- Trim results table:
if (#results > maxResults) then
results = {table.unpack(results, 1, maxResults)}
end
-- Display results:
for i,result in ipairs(results) do
-- Here is where you update your UI
local player = result[1]
print(i, player.Name)
end
end)
In the “real world” you would want to add a cooldown debounce to type-ahead predictive searches like this. In Roblox though, with a max of 100 players in a game, there’s no problem running it on every text change right away.
Video example:
Place file from the above video: Typeahead.rbxl (27.0 KB)
You would need to create a text label underneath the textbox or something and change its text to copy the best match. You can also use the UserInputService/Input events to do something like fill in the full name when you press tab or the arrow keys.
Add spaces to offset the text and also put the placeholder text under the text input box. You likely want to make the input box completely transparent and have the label have the appropriate values. It is much easier if you just use chat-style alignment to the left though, as then you don’t have to worry about placeholder text size.
Question, how would i trigger an event to check whenever the user changes a value in the text??
Currently I got this:
script.Parent.Frame.TextBox:GetPropertyChangedSignal("Text"):Connect(function()
script.Parent.Frame.TextBox.Text = script.Parent.Frame.TextBox.Text:upper()
for _,v in pairs(game.Players:GetPlayers()) do
if string.lower(v.Name:upper()).sub(1, string.len(script.Parent.Frame.TextBox.Text)) == string.lower(script.Parent.Frame.TextBox.Text:upper()) then
script.Parent.Frame.TextBox.TextLabel.Text = v.Name:upper()
end
end
end)
But it wont update the “script.Parent.Frame.TextBox.TextLabel”