How to make a Advanced Chat System!

Greetings!

I’m dev_fractility,

Any feedback onto this topic or any improvement on this is always appreciated! Sorry in advance, this is my first ever post and I hope to get a decent understanding on how to make an Advanced Chat System! I haven’t seen many topics about this, and when I started scripting it always seemed confusing so hopefully this helps clear some issues people had making their own!

First
We are going to create a script underneath ServerScriptService name doesn’t matter!
5e5096e0d76c8e0038900b74fdd705bf

Inside of the script we will set our locals and create our remote that we are going to use!

local chat_remote = Instance.new("RemoteEvent", game.ReplicatedStorage)
chat_remote.Name = "ChatEvent"

local ServerScriptService = game:GetService("ServerScriptService")
local ChatService = require(ServerScriptService:WaitForChild("ChatServiceRunner").ChatService)

local emojis = {
	["devforum"] = "http://www.roblox.com/asset/?id=1251795601"
}

Secondly

We are going to be making the chat message splice function. We will return to a Character Set essentially, it’s a table with all of our data for our chat. The reason we do this is so we can have special effects on pretty much anything allowing our creativity to sprout in ways. I am aware this may be sending too much data through the remote but again this is for example reasons. We will also be setting some for our emotes!

local split = function(Player, Message)
	local characterSet = {
		--[[ 
		{
			Text = "[Owner] ",
			Color = Color3.fromgRGB(255, 85, 85)
		},
		]]-- This is for a rank, you can use images as well using the emoji format.
		{
			Text = Player.Name..": ",
			Color = Color3.fromRGB(85, 85, 255) --  Name Color
		}
	}
	local split_msg = Message:split(" ")
	
	for i, str in ipairs(split_msg) do
		local emoted = (emojis[str:lower()] ~= nil and true or false)
		if emoted then
			table.insert(characterSet, {
				Image = emojis[str:lower()] --Making a Image Variable to have our decal id
			})
		else
			table.insert(characterSet, {
				Text = str..(#split_msg == i and "" or " "), 
				--[[
					Actually making the text whether its a space after or not depending on the index.
				]]
				Color = Color3.fromRGB(255, 255, 255) --Color of the chat.
			})
		end
	end
	return characterSet
end

Next

This whole function is because “.Chatted” doesn’t return a proper instance for whispers. For those who are wondering what I mean.

e.x

Player.Chatted:connect(function(Message, Whisper)
	if Whisper == nil then -- Whisper is always nil.
		print("All / Message")
	else
		print("Whispered")
	end
end)

So to switch this up a bit, we can use a hook from Roblox’s Lua Chat System.

local function speakerAdded(speakerName)
	local speaker = ChatService:GetSpeaker(speakerName)
	local player = speaker:GetPlayer()
	speaker.SaidMessage:Connect(function (message, channelName)
		if message.MessageType == "Whisper" then
			return -- Don't want to send out whispers!
		end
		for _, plr in next, game.Players:GetChildren() do
			local msg = tostring(message.FilterResult) --Studio's Version
			if typeof(message.FilterResult) == "Instance" then --Live Version
				msg = message.FilterResult:GetChatForUserAsync(plr.UserId) --Filtering
			end
			chat_remote:FireClient(plr, split(player, msg))
		end
	end)
end

ChatService.SpeakerAdded:Connect(speakerAdded)
for _, speaker in ipairs(ChatService:GetSpeakerList()) do
	speakerAdded(speaker)
end

Lastly

We setup our Chat GUI!

471777afbb69d031dce1c5651fa87633

e04720cb76d7055852a8b04dc23b7a4d

These are just my properties, yours can be whatever, this is just for example!

Inside of our LocalScript (ClientHandler)

We are going to setup our locals and a simple check function for text going out of our chat frame holder.

local chat_remote = game.ReplicatedStorage:WaitForChild("ChatEvent")
local Holder = script.Parent:WaitForChild("Holder")
local chat_size, chat_font = 14, Enum.Font.Merriweather

local function checkOutOfBounds(CurrentTextBounds, Label)
	if CurrentTextBounds + (Label:IsA'TextLabel' and Label.TextBounds.X or (chat_size + 8)) > Holder.AbsoluteSize.X then 
		return true
	end
	return false
end

Here we are connecting to our OnClientEvent signal to instantiate our chat to be created.

chat_remote.OnClientEvent:connect(function(CharacterSet)
	local CurrentLine, CurrentTextBounds = 1, 0 --Positioning for our labels.
	--[[ Frame for our Chat ]]--
	local chat = Instance.new("Frame", Holder)
	chat.Size = UDim2.new(1, 0, 0, chat_size + 4)
	chat.BackgroundTransparency = 1
	
	for _, entry in ipairs(CharacterSet) do
		if entry["Image"] == nil then -- Checking if its text or image.
			for i=1, #entry.Text do -- We will create a label for each letter in our text
				--Reasons we do this is to prevent chat breaking when people use things such as "AAAAAAAAAAAAAAAAAAAAA"
				local label = Instance.new("TextLabel", chat)
				label.Size = UDim2.new(0, 1, 0, chat_size + 4)
				label.BackgroundTransparency = 1
				label.Font = chat_font
				label.TextSize = chat_size
				label.TextXAlignment = "Left" -- Better positioning.
				label.Text = entry.Text:sub(i,i)
				label.TextColor3 = entry.Color
				label.TextStrokeTransparency = 0.7 -- Stroke.
				--This is just a simple shadow effect (vv)
				--[[
				local shadow = label:Clone()
				label.ZIndex = 2
				shadow.Parent = label
				shadow.Position = UDim2.new(0, 2, 0, 2)
				]]--
				if checkOutOfBounds(CurrentTextBounds, label) then
					CurrentTextBounds = 0
					CurrentLine +=1
				end
				label.Position = UDim2.new(0, CurrentTextBounds, 0, (CurrentLine-1) * chat_size) -- Positioning based on line-1 to not have a skipped line.
				CurrentTextBounds += label.TextBounds.X
			end
		else -- Images get made here.
			local label = Instance.new("ImageLabel", chat)
			label.BackgroundTransparency = 1
			label.Image = entry.Image
			label.Size = UDim2.new(0, chat_size+8, 0, chat_size+8)
			if checkOutOfBounds(CurrentTextBounds, label) then
				CurrentTextBounds = 0
				CurrentLine += 1
			end
			label.Position = UDim2.new(0, CurrentTextBounds, 0, (CurrentLine-1) * chat_size) -- Positioning based on line-1 to not have a skipped line.
			CurrentTextBounds += (chat_size + 8)
		end
	end

This is where we move old chat frames to make room for our new frame.

	for _, old in next, Holder:GetChildren() do
		if old:IsA'Frame' and old ~= chat then
			old.Position += UDim2.new(0, 0, 0, -(chat_size * CurrentLine))
			if math.abs(old.AbsolutePosition.Y) >= Holder.AbsoluteSize.Y then
				old:Remove()
			end
		else
			old.Position = UDim2.new(0, 0, 1, -(chat_size * CurrentLine))
		end
	end
end)

This is our finished result!

a925748616424970cd6c9c59b8d55a7e

Uncopylocked Game Link
Hope you enjoyed this tutorial, and blox on!

Edit 1: Added link for game!
Edit 2: Fixed grammatical issues!

48 Likes

Well made tutorial is all I can say.

How can you make a box where you can actually type your message in cause it works fine what you did but i want an chat box where people can Type how can i do that cause chat systems are confusing me all the time?

I feel like you could make it a little less complicated using RichText.
But great job :clap:

4 Likes

Hello @DevTurtIe,

You are talking about a chat input box.
You would accomplish this using a TextBox!

Reference to Wiki: Link

You would focus on, Focus State.

Best Wishes,
dev_fractility


@Mega_Hypex
Please elaborate! I would love to hear more, I haven’t gotten around learning about RichText and what’s capable of it.


@Thereasonableplayer

Thank you so much :smiling_face_with_three_hearts: !

what about bad words? After all, players will be able to swear in chat

This is a great tutorial! I would only say that because the explanations do not go into much depth, it isn’t the best tutorial for beginner coders like myself.

Theres this in the code that does filtering for bad words!

I don’t think it works because I can use bad words in the game