How to code a phone like system in Roblox

Hello members of the forum!

I recently saw a game on Roblox which is basically a phone system on Roblox where you can actually use a phone

Here is it:

As the title states I want to know how to code a phone that is somewhat like this with things like functioning apps and notifications.

I did try making it but I wanted to know if there’s anything I need to add or if you would do something else.

My code:

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local PhoneFolder = ReplicatedStorage:FindFirstChild("Phone")
if not PhoneFolder then
	PhoneFolder = Instance.new("Folder")
	PhoneFolder.Name = "Phone"
	PhoneFolder.Parent = ReplicatedStorage
end

local function makeRemote(name)
	local r = PhoneFolder:FindFirstChild(name)
	if not r then
		r = Instance.new("RemoteEvent")
		r.Name = name
		r.Parent = PhoneFolder
	end
	return r
end

local SendMessage = makeRemote("SendMessage")
local ReceiveMessage = makeRemote("ReceiveMessage")
local BatteryUpdate = makeRemote("BatteryUpdate")

local Profiles = {}

local function newProfile()
	return {
		Battery = 100,
		Messages = {},
		Unlocked = true
	}
end

Players.PlayerAdded:Connect(function(player)
	Profiles[player] = newProfile()
	task.spawn(function()
		while player.Parent do
			task.wait(60)
			local profile = Profiles[player]
			if profile then
				profile.Battery = math.clamp(profile.Battery - 1, 0, 100)
				BatteryUpdate:FireClient(player, profile.Battery)
			end
		end
	end)
end)

Players.PlayerRemoving:Connect(function(player)
	Profiles[player] = nil
end)

SendMessage.OnServerEvent:Connect(function(player, targetUserId, text)
	if typeof(text) ~= "string" then return end
	if #text > 200 then return end

	local senderProfile = Profiles[player]
	if not senderProfile or senderProfile.Battery <= 0 then return end

	local target = Players:GetPlayerByUserId(targetUserId)
	if not target then return end

	local targetProfile = Profiles[target]
	if not targetProfile then return end

	table.insert(targetProfile.Messages, {
		From = player.UserId,
		Text = text,
		Time = os.time()
	})

	senderProfile.Battery = math.clamp(senderProfile.Battery - 2, 0, 100)
	BatteryUpdate:FireClient(player, senderProfile.Battery)
	ReceiveMessage:FireClient(target, player.UserId, text)
end)

local ReplicatedStorageClient = game:GetService("ReplicatedStorage")
local Phone = ReplicatedStorageClient:WaitForChild("Phone")

Phone.ReceiveMessage.OnClientEvent:Connect(function(fromId, text)
	print(fromId, text)
end)

Phone.BatteryUpdate.OnClientEvent:Connect(function(level)
	print(level)
end)

Thank you have a good day!

1 Like

Apart from the PIN code for the phone which acts as a security feature for users who get their accounts hacked and don’t want someone to access their in-game phone You shouldn’t be using a remote event to communicate anything from server to client or vice versa.

you can just avoid using remote events in your game and using the server at all for basically everything this will reduce network traffick which helps. (apart from the pin when the user creates their pin and u save that with datastoreservice)

Since your game will probably be completely UI based aswell, you can destroy everything in the workspace since the client doesn’t need to see it and disable characterautospawns, you really shouldn’t have any instances in your game. This will reduce memory usage for server and client and put less work on the CPU which can lead to a smoother experience for players.

And you don’t need to use getservice replicated storage twice.

99.9% of the entire game should be client-sided basically.

You can optimize the part of your code where you’re trying to do stuff when the battery is at a certain level by just using a getpropertychanged event on the client. Which would also help the client’s CPU.

Just some optimizations here and there and you should be good.

1 Like

Thank you I’m gonna try this!

Remaking the code right now based off the information you provided.

I’ll try to reply with the code but yeah thank you very much :+1:

1 Like

Alright I fixed it up a little bit.

Server Script (Pin)

local DataStoreService = game:GetService("DataStoreService")
local PinStore = DataStoreService:GetDataStore("PhonePins")

Players.PlayerAdded:Connect(function(player)
	player.CharacterAutoLoads = false

	local pinValue = Instance.new("StringValue")
	pinValue.Name = "PhonePIN"
	pinValue.Parent = player

	local success, data = pcall(function()
		return PinStore:GetAsync(player.UserId)
	end)

	if success and data then
		pinValue.Value = data
	end
end)

Players.PlayerRemoving:Connect(function(player)
	local pin = player:FindFirstChild("PhonePIN")
	if not pin or pin.Value == "" then return end

	pcall(function()
		PinStore:SetAsync(player.UserId, pin.Value)
	end)
end)

Client Phone system

local DataStoreService = game:GetService("DataStoreService")
local PinStore = DataStoreService:GetDataStore("PhonePins")

Players.PlayerAdded:Connect(function(player)
	player.CharacterAutoLoads = false

	local pinValue = Instance.new("StringValue")
	pinValue.Name = "PhonePIN"
	pinValue.Parent = player

	local success, data = pcall(function()
		return PinStore:GetAsync(player.UserId)
	end)

	if success and data then
		pinValue.Value = data
	end
end)

Players.PlayerRemoving:Connect(function(player)
	local pin = player:FindFirstChild("PhonePIN")
	if not pin or pin.Value == "" then return end

	pcall(function()
		PinStore:SetAsync(player.UserId, pin.Value)
	end)
end)

Would this work?

Some things i’d say:

You shouldn’t actually create the pinvalue instance on the server because it’ll be replicated to all connected clients. Unnecessarily taking up memory for both server and client.

Just a small optimization would be to create the pinvalue instance on the client so it’s only taking up memory for that specific client

and then use one remote event to fire to the client that joined and send over the data to that client with the :FireClient() parameters. Then in the OnClientEvent just set the client’s pinvalue to the data. None of this will be replicated so that helps server CPU and lowers memory usage.

Also, you need to have a more secure datastore system. If a datastore setasync/getasync call fails then the player just won’t have their pin code data. Which can be annoying.

Have a retry system that tries to save the data again if setasync fails (do about 5-6 retries because datastores have rate limits), an autosave system if servers crash due to roblox backend problems, etc.

There’s alot of features you need in datastores to make them secure, like session locking aswell. I suggest you read up on the documentation to understand how datastores work in more detail so you can secure it more:

https://create.roblox.com/docs/cloud-services/data-stores

1 Like

Hey!

It’s gonna take me a little bit to make the main code but I did add lots of details to the PIN and tried going off the information you gave me.

local DataStoreService = game:GetService("DataStoreService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local PinStore = DataStoreService:GetDataStore("PhonePins_V2")

local Remote = Instance.new("RemoteEvent")
Remote.Name = "PhonePinTransfer"
Remote.Parent = ReplicatedStorage

local ActiveSessions = {}

local RETRIES = 6

local function getWithRetry(key)
	for i = 1, RETRIES do
		local success, result = pcall(function()
			return PinStore:GetAsync(key)
		end)
		if success then
			return result
		end
		task.wait(i)
	end
end

local function setWithRetry(key, value)
	for i = 1, RETRIES do
		local success = pcall(function()
			PinStore:SetAsync(key, value)
		end)
		if success then
			return true
		end
		task.wait(i)
	end
end

Players.PlayerAdded:Connect(function(player)
	player.CharacterAutoLoads = false

	local userId = player.UserId
	if ActiveSessions[userId] then
		player:Kick("Session already active")
		return
	end

	ActiveSessions[userId] = true

	local data = getWithRetry(userId)
	if data and data.Session and data.Session ~= game.JobId then
		player:Kick("Data already in use")
		return
	end

	Remote:FireClient(player, data and data.Pin or nil)
end)

Players.PlayerRemoving:Connect(function(player)
	local userId = player.UserId
	ActiveSessions[userId] = nil

	local pin = player:GetAttribute("PendingPIN")
	if not pin then return end

	setWithRetry(userId, {
		Pin = pin,
		Session = game.JobId
	})
end)

game:BindToClose(function()
	for _, player in ipairs(Players:GetPlayers()) do
		local pin = player:GetAttribute("PendingPIN")
		if pin then
			setWithRetry(player.UserId, {
				Pin = pin,
				Session = game.JobId
			})
		end
	end
end)

Thank you!

I made the main system for the phone!:

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local player = Players.LocalPlayer
local Remote = ReplicatedStorage:WaitForChild("PhonePinTransfer")

for _, v in ipairs(workspace:GetChildren()) do
	v:Destroy()
end

local phone = {
	Battery = Instance.new("IntValue"),
	Messages = {},
	Pin = nil,
	Unlocked = false
}

phone.Battery.Value = 100

phone.Battery:GetPropertyChangedSignal("Value"):Connect(function()
	if phone.Battery.Value <= 0 then
		phone.Unlocked = false
	end
end)

task.spawn(function()
	while true do
		task.wait(60)
		phone.Battery.Value = math.clamp(phone.Battery.Value - 1, 0, 100)
	end
end)

Remote.OnClientEvent:Connect(function(pin)
	phone.Pin = pin
end)

local function unlock(input)
	if not phone.Pin then
		phone.Pin = input
		player:SetAttribute("PendingPIN", input)
		phone.Unlocked = true
		return true
	end

	if input == phone.Pin then
		phone.Unlocked = true
		return true
	end

	return false
end

local function sendMessage(userId, text)
	if not phone.Unlocked then return end
	if phone.Battery.Value <= 0 then return end
	if #text > 200 then return end

	table.insert(phone.Messages, {
		To = userId,
		Text = text,
		Time = os.time()
	})

	phone.Battery.Value -= 2
end

_G.Phone = {
	Unlock = unlock,
	SendMessage = sendMessage,
	Data = phone
}

This seem good?

Just a few more things which can cause issues:

1: if your getasync/setasync is successful then you should break the loop otherwise its unnecessarily calling async methods and also because you never know it could work on the second try but then it fails on the fourth again, if you break the loop as soon as your getasync/setasync succeeds then you’ve already loaded/saved the required data.

2: A nice little trick you can do for getasync is, if for whatever reason even after 6 retries of loading your data with getasync it’s still failing. Then you can teleport the player into a different server and then in the other server it will try to load the data and it may work because it could be due to something wrong with the server.

3: When I said to destroy all the children in the workspace I meant to do it on the server, because although all the clients will have memory free’d up if you delete the stuff in workspace on their side, the server will still have increased memory usage due to stuff in workspace.

4: there’s no need to use task.spawn on that while loop you can just move the loop to the bottom of your code, and you should move the task.wait(60) aswell because it’s yielding for 60 seconds before the battery is even changed:

while true do
phone.Battery.Value = math.clamp(phone.Battery.Value - 1, 0, 100)
task.wait(60)
end

5: Don’t use ipairs, use the new “in” iterator to loop through tables/dictionaries. It’s faster.

1 Like

Would this work?

Server:

local DataStoreService = game:GetService("DataStoreService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TeleportService = game:GetService("TeleportService")

local PinStore = DataStoreService:GetDataStore("PhonePins_V3")

local Remote = Instance.new("RemoteEvent")
Remote.Name = "PhonePinTransfer"
Remote.Parent = ReplicatedStorage

for _, instance in workspace:GetChildren() do
	instance:Destroy()
end

local ActiveSessions = {}
local RETRIES = 6
local PLACE_ID = game.PlaceId

local function getWithRetry(key)
	for i = 1, RETRIES do
		local success, data = pcall(function()
			return PinStore:GetAsync(key)
		end)
		if success then
			return data
		end
		task.wait(i)
	end
end

local function setWithRetry(key, value)
	for i = 1, RETRIES do
		local success = pcall(function()
			PinStore:SetAsync(key, value)
		end)
		if success then
			return true
		end
		task.wait(i)
	end
end

Players.PlayerAdded:Connect(function(player)
	player.CharacterAutoLoads = false

	local userId = player.UserId
	if ActiveSessions[userId] then
		player:Kick("Duplicate session")
		return
	end

	ActiveSessions[userId] = true

	local data = getWithRetry(userId)
	if not data then
		TeleportService:Teleport(PLACE_ID, player)
		return
	end

	if data.Session and data.Session ~= game.JobId then
		player:Kick("Data in use")
		return
	end

	Remote:FireClient(player, data.Pin)
end)

Players.PlayerRemoving:Connect(function(player)
	local userId = player.UserId
	ActiveSessions[userId] = nil

	local pin = player:GetAttribute("PendingPIN")
	if not pin then return end

	setWithRetry(userId, {
		Pin = pin,
		Session = game.JobId
	})
end)

game:BindToClose(function()
	for _, player in Players:GetPlayers() do
		local pin = player:GetAttribute("PendingPIN")
		if pin then
			setWithRetry(player.UserId, {
				Pin = pin,
				Session = game.JobId
			})
		end
	end
end)

Client:

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local player = Players.LocalPlayer
local Remote = ReplicatedStorage:WaitForChild("PhonePinTransfer")

local phone = {
	Battery = Instance.new("IntValue"),
	Messages = {},
	Pin = nil,
	Unlocked = false
}

phone.Battery.Value = 100

phone.Battery:GetPropertyChangedSignal("Value"):Connect(function()
	if phone.Battery.Value <= 0 then
		phone.Unlocked = false
	end
end)

while true do
	phone.Battery.Value = math.clamp(phone.Battery.Value - 1, 0, 100)
	task.wait(60)
end

Remote.OnClientEvent:Connect(function(pin)
	phone.Pin = pin
end)

local function unlock(input)
	if not phone.Pin then
		phone.Pin = input
		player:SetAttribute("PendingPIN", input)
		phone.Unlocked = true
		return true
	end

	if input == phone.Pin then
		phone.Unlocked = true
		return true
	end

	return false
end

local function sendMessage(userId, text)
	if not phone.Unlocked then return end
	if phone.Battery.Value <= 0 then return end
	if #text > 200 then return end

	table.insert(phone.Messages, {
		To = userId,
		Text = text,
		Time = os.time()
	})

	phone.Battery.Value -= 2
end

_G.Phone = {
	Unlock = unlock,
	SendMessage = sendMessage,
	Data = phone
}

How did you reply so fast lol? Are you using AI to code it?

I see the alot of the same problems, you need to break within the for loop of retries for both getasync and setasync if the success boolean is true.

And you didn’t move the while loop down to the bottom of the local script, currently if the while loop and anything below the loop is ran at the same time then the code won’t work properly.

1 Like

I’m not using AI., It may seem like it and if I saw this to lol I would think that. I’m using it off of a module that my friend made so I may be copying a little bit as he’s tryna help me but no AI. Thanks for the worry though. I think I’ll try using your info and ask him.

Oh alright lol nah I was just thinking it was a possibility because AI is basically just a pattern matcher so like for example when I told you to break the for loop you showed me your new code which didn’t break the for loop aswell. But alright good luck.

1 Like

Thank you have a great day!

(character limit :sob:)

1 Like

There is an video tutorial on this I’ve seen floating around, you could use this instead, maybe as a learning curve for you or to implement it into your game, here are some videos I found for you.

Have a look yourself feel free to look around at the code or something whatever you scriptes do

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.