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)
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.
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)
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:
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)
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
}
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.
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.
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.
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