This one handles datastores and a bit more complicated code, so make sure you have some experience first!
Warning to less experienced programmers: Contains someone intermediate programming and some complicated functions/API’s
-This also probably looks more complicate than it is
-this WILL INCLUDE: How to make it, and an example of how to use it with a sprinting keybind
-this took me a good few hours to make and bug fix, any support in the replies is greatly thanked!
-edits will by typo fixes, bug fixes, and optimizations
-remember to save/publish often!
First things we need to set up:
- GUI to edit keybinds with
- Folder to store default keybinds values & copy able instances for data loading
- DataStore code to save, load, and edit data
API Links:
Read up on the pages to solve any basic confusion and if the problem still persists leave a reply!
- ScreenGui, Frame, TextButton
- LocalScript, Script
- DataStores, UserInputService, HTTP Service
- Client-to-Server replication (remote events)
- ServerStorage, ReplicatedStorage, StarterGui
- StringValue, Folder, RemoteEvent
- Functions
–API Links might look a bit overkill, but I want to help everyone get knowledge on programming!
How I will name things:
- DataStores = DS
- UserInputService = UIS
- ReplicatedStorage = repStorage
- Anything in [ ] on GUI’s will be edited by the code and loaded when the player joins
Explorer Setup:
- Folder in ServerStorage named “SettingsTemplate”
- StringValue in SettingsTamplte named “SprintKeybind”, Value set to “LeftShift” for the default key
- RemoteEvent in ReplicatedStorage named “UpdateKeybinds”
- Script in ServerScriptService named “SettingsHandler” PS: Name doesn’t matter on this script
- ScreenGui in StarterGui, named “KeybindsGUI”
- Frame in KeybindsGUI, named “MainFrame”
- TextButton in MainFrame, named “Sprintkeybind” (should be the same as the value in SettingsTemplate but it doesnt matter to much)
- A TextButton in KeybindsGUI, named “Activate”
You should end up with something like this (Sizes, Colors, Fonts, etc on GUI’s are all up to you)
we will be programming the client side then the server side because thats how I feel like doing it
Now onto the GUI setup:
It can get a little messy here having LocalScripts under multiple buttons and such so I will use 1 LocalScript that controls it all called “Main”, but first we’ll do the Close/Open button first
Inside Main we will need variables to get the objects and such that we will need for the programming, the code comments should explain what they do, any questions should be asked in replies!
local repStorage = game:GetService("ReplicatedStorage") -- Anything placed here can be seen by the Client and the Server so we will use it for events
local updateBindsEvent = repStorage:WaitForChild("UpdateKeybinds") -- The RemoteEvent we will use to update the keybinds in the DS's
local Player = game:GetService("Players").LocalPlayer -- The client object, not the physical character, just the client object
local GUI = script.Parent -- The ScreenGui
local MainFrame = GUI.MainFrame -- The Frame
local ActivateBtn = GUI.Activate -- The TextButton used the open said Frame
local SprintKeybindBtn = MainFrame.SprintKeybind -- The TextButton used to update the players keybind
local opened = false -- Checking if the GUI is already opened so if we close it or open it
local sprintSelected -- will be used to find out what the player selected to update the data stores!
Now we have that you should take a moment to understand what those do and means and then lets continue!
Now we have to add the actual open and closing function to the button, like so, as always, coding comments should explain it!
MainFrame.Visible = false -- sets the frame to un-seeable by default
ActivateBtn.Activated:Connect(function() -- .Activate means it was clicked by a mouse, or tapped on a mobile device's screen, and then creates the function to run what happens
if opened == true then -- checking if the GUI is already opened, double equal signs means we are checking the value not setting it "=="
opened = false -- if they click it and its opened it closes
MainFrame.Visible = false -- sets the visibilty on the frame so the player can or cant see it
else
opened = true -- if they click it and its closed it opens
MainFrame.Visible = true -- sets the visibilty on the frame so the player can or cant see it
end
end)
If you wish you can test to make sure you got it all right, otherwise lets move on!
We now have to add the GUI function to our keybind button, but no actual data saving stuff yet! We have to add a UIS detection, and 2 new variables that I forgot at the start to find out of its listening or not
local listeningForSprintInput = false -- used to detect if UIS should be detecting inputs or not
local UIS = game:GetService("UserInputService") -- UserInputSerivce, detects when the player inputs anything, a key click, mouse move, xbox controller button click, etc
Now we add the UIS detection, now for the 3rd time, code comments!
SprintKeybindBtn.Activated:Connect(function()
listeningForSprintInput = true -- tells UIS that we are listening for a new keybind input
SprintKeybindBtn.Text = "Sprint keybind: listening for new key input" -- changes the text to tell the player we got it and are listening for a new keycode input
end)
UIS.InputBegan:Connect(function(input) -- .InputBegan is when you give any input, "input" is all the info on what you pressed
if input.UserInputType == Enum.UserInputType.Keyboard then -- Checking if the detection was frame a keyboard (this check if un-needed if your game is VR/Xbox based or anything of the like)
if listeningForSprintInput == true then -- if we are listening for detection of a new keybinds
sprintSelected = UIS:GetStringForKeyCode(input.KeyCode) -- this UIS function I recently found, it turns they KeyCode from an Enum to the letter like "Enum.KeyCode.E" to "E"
SprintKeybindBtn.Text = "Sprint keybind: ".. tostring(sprintSelected) -- resets the GUI to the new keybind
listeningForSprintInput = false -- sets it to false so we know that we dont want to continue listening for inputs on the keybind
end
end
end)
The entire LocalScript so far:
local repStorage = game:GetService("ReplicatedStorage") -- Anything placed here can be seen by the Client and the Server so we will use it for events
local updateBindsEvent = repStorage:WaitForChild("UpdateKeybinds") -- The RemoteEvent we will use to update the keybinds in the DS's
local Player = game:GetService("Players").LocalPlayer -- The client object, not the physical character, just the client object
local UIS = game:GetService("UserInputService") -- UserInputSerivce, detects when the player inputs anything, a key click, mouse move, xbox controller button click, etc
local GUI = script.Parent -- The ScreenGui
local MainFrame = GUI.MainFrame -- The Frame
local ActivateBtn = GUI.Activate -- The TextButton used the open said Frame
local SprintKeybindBtn = MainFrame.SprintKeybind -- The TextButton used to update the players keybind
local opened = false -- Checking if the GUI is already opened so if we close it or open it
local sprintSelected -- will be used to find out what the player selected to update the data stores!
local listeningForSprintInput = false -- used to detect if UIS should be detecting inputs or not
MainFrame.Visible = false
ActivateBtn.Activated:Connect(function() -- .Activate means it was clicked by a mouse, or tapped on a mobile device's screen, and then creates the function to run what happens
if opened == true then -- checking if the GUI is already opened, double equal signs means we are checking the value not setting it "=="
opened = false -- if they click it and its opened it closes
MainFrame.Visible = false -- sets the visibilty on the frame so the player can or cant see it
else
opened = true -- if they click it and its closed it opens
MainFrame.Visible = true -- sets the visibilty on the frame so the player can or cant see it
end
end)
SprintKeybindBtn.Activated:Connect(function()
listeningForSprintInput = true -- tells UIS that we are listening for a new keybind input
SprintKeybindBtn.Text = "Sprint keybind: listening for new key input" -- changes the text to tell the player we got it and are listening for a new keycode input
end)
UIS.InputBegan:Connect(function(input) -- .InputBegan is when you give any input, "input" is all the info on what you pressed
if input.UserInputType == Enum.UserInputType.Keyboard then -- Checking if the detection was frame a keyboard (this check if un-needed if your game is VR/Xbox based or anything of the like)
if listeningForSprintInput == true then -- if we are listening for detection of a new keybinds
sprintSelected = UIS:GetStringForKeyCode(input.KeyCode) -- this UIS function I recently found, it turns they KeyCode from an Enum to the letter like "Enum.KeyCode.E" to "E"
SprintKeybindBtn.Text = "Sprint keybind: ".. tostring(sprintSelected) -- resets the GUI to the new keybind
listeningForSprintInput = false -- sets it to false so we know that we dont want to continue listening for inputs on the keybind
end
end
end)
Now im remembering, we have to add 2 more TextButtons to MainFrame, “SaveSettings” and “DiscardSettings”
And we have to add functionality to these, discard settings will make the settings go back to previous values, and save settings will save them and lock them in place
New variables for the buttons:
local saveSettingsBtn = MainFrame.SaveSettings
local discardSettingsBtn = MainFrame.DiscardSettings
And now the functions for what they need to do!
MAKE SURE TO GO TO GAME SETTINGS AND ENABLE HTTPS AND API SERVICES IN SECURITY IF YOU HAVENT ALREADY
And the new functions (as always, code comments)
saveSettingsBtn.Activated:Connect(function()
updateBindsEvent:FireServer("SprintKeybind", sprintSelected)
--setting, changeValue
--settings = the case specific setting name we are changing
--changeValue = the value we need to change it to, either a key or a bool value, etc
MainFrame.Visible = false -- they finished up with settings so lets close the UI
opened = false -- reset the opened value so they can open it again later
--!!!! If there is ever an issue where your game is sending to many data requests and it rate limits you, saving data when they close the GUI is not required as long as it saves when they leave, I just include it as an extra precaution
end)
discardSettingsBtn.Activated:Connect(function()
SprintKeybindBtn.Text = "Sprint keybind: ".. tostring(Player:WaitForChild("Settings"):WaitForChild("SprintKeybind").Value) -- reset the GUI to the previous settings because they want to discard these new ones
MainFrame.Visible = false -- they finished up with settings so lets close the UI
opened = false -- reset the opened value so they can open it again later
end)
Now I will show you the server side code, and this will allow your settings to work!
code comments as always and this part gets a little messy so just ask any questions in replies!
And as im writing and testing this, I realize we dont set any of the GUI text thats in [ ] on load, lets do that quickly
SprintKeybindBtn.Text = "Sprint keybind: ".. tostring(Player:WaitForChild("Settings"):WaitForChild("SprintKeybind").Value) -- just sets the text on load
And for anyone lost here is the LocalScript “Main” so far:
local repStorage = game:GetService("ReplicatedStorage") -- Anything placed here can be seen by the Client and the Server so we will use it for events
local updateBindsEvent = repStorage:WaitForChild("UpdateKeybinds") -- The RemoteEvent we will use to update the keybinds in the DS's
local Player = game:GetService("Players").LocalPlayer -- The client object, not the physical character, just the client object
local UIS = game:GetService("UserInputService") -- UserInputSerivce, detects when the player inputs anything, a key click, mouse move, xbox controller button click, etc
local GUI = script.Parent -- The ScreenGui
local MainFrame = GUI.MainFrame -- The Frame
local ActivateBtn = GUI.Activate -- The TextButton used the open said Frame
local saveSettingsBtn = MainFrame.SaveSettings
local discardSettingsBtn = MainFrame.DiscardSettings
local SprintKeybindBtn = MainFrame.SprintKeybind -- The TextButton used to update the players keybind
local opened = false -- Checking if the GUI is already opened so if we close it or open it
local sprintSelected -- will be used to find out what the player selected to update the data stores!
local listeningForSprintInput = false -- used to detect if UIS should be detecting inputs or not
MainFrame.Visible = false
SprintKeybindBtn.Text = "Sprint keybind: ".. tostring(Player:WaitForChild("Settings"):WaitForChild("SprintKeybind").Value) -- just sets the text on load
ActivateBtn.Activated:Connect(function() -- .Activate means it was clicked by a mouse, or tapped on a mobile device's screen, and then creates the function to run what happens
if opened == true then -- checking if the GUI is already opened, double equal signs means we are checking the value not setting it "=="
opened = false -- if they click it and its opened it closes
MainFrame.Visible = false -- sets the visibilty on the frame so the player can or cant see it
else
opened = true -- if they click it and its closed it opens
MainFrame.Visible = true -- sets the visibilty on the frame so the player can or cant see it
end
end)
SprintKeybindBtn.Activated:Connect(function()
listeningForSprintInput = true -- tells UIS that we are listening for a new keybind input
SprintKeybindBtn.Text = "Sprint keybind: listening for new key input" -- changes the text to tell the player we got it and are listening for a new keycode input
end)
UIS.InputBegan:Connect(function(input) -- .InputBegan is when you give any input, "input" is all the info on what you pressed
if input.UserInputType == Enum.UserInputType.Keyboard then -- Checking if the detection was frame a keyboard (this check if un-needed if your game is VR/Xbox based or anything of the like)
if listeningForSprintInput == true then -- if we are listening for detection of a new keybinds
sprintSelected = UIS:GetStringForKeyCode(input.KeyCode) -- this UIS function I recently found, it turns they KeyCode from an Enum to the letter like "Enum.KeyCode.E" to "E"
SprintKeybindBtn.Text = "Sprint keybind: ".. tostring(sprintSelected) -- resets the GUI to the new keybind
listeningForSprintInput = false -- sets it to false so we know that we dont want to continue listening for inputs on the keybind
end
end
end)
saveSettingsBtn.Activated:Connect(function()
updateBindsEvent:FireServer("SprintKeybind", sprintSelected)
--setting, changeValue
--settings = the case specific setting name we are changing
--changeValue = the value we need to change it to, either a key or a bool value, etc
MainFrame.Visible = false -- they finished up with settings so lets close the UI
opened = false -- reset the opened value so they can open it again later
--!!!! If there is ever an issue where your game is sending to many data requests and it rate limits you, saving data when they close the GUI is not required as long as it saves when they leave, I just include it as an extra precaution
end)
discardSettingsBtn.Activated:Connect(function()
SprintKeybindBtn.Text = "Sprint keybind: ".. tostring(Player:WaitForChild("Settings"):WaitForChild("SprintKeybind").Value) -- reset the GUI to the previous settings because they want to discard these new ones
MainFrame.Visible = false -- they finished up with settings so lets close the UI
opened = false -- reset the opened value so they can open it again later
end)
And here is the server code and explained in our favorite code comments
local serverStorage = game:GetService("ServerStorage")
local SettingsTemplate = serverStorage:WaitForChild("SettingsTemplate")
local DTS = game:GetService("DataStoreService")
local Http = game:GetService("HttpService")
local MainDataStore = DTS:GetDataStore("MyDataStore") -- Change "MyDataStore" to your datastore name and keep private
local repStorage = game:GetService("ReplicatedStorage")
local updateEvent = repStorage:WaitForChild("UpdateKeybinds")
function saveData(plr)
local plrUserId = "Player_"..plr.UserId
--the key would end up being Player_[their UserID]
local data = {}
for i, v in pairs(plr:WaitForChild("Settings"):GetChildren()) do
data[v.Name] = v.Value
end
local Encoded_Data = Http:JSONEncode(data)
local Success, Error_Message = pcall(function()
MainDataStore:SetAsync(plrUserId, Encoded_Data)
end)
--all the stuff above does some fancy HTTPS work and makes all the data into a nice table for us to look at
if Success == true then
print("saved data")
else
print("Error: "..Error_Message) -- never got this, no clue how to fix if you got this, would recommend just rewriting this
end
end
function loadData(player, gameData)
--loads the players data!
--"gameData" is the term used for the folder under the player that stores the settings value
local plrUserId = "Player_".. player.UserId
local User_Data = nil
local Success, Error_Message = pcall(function()
User_Data = MainDataStore:GetAsync(plrUserId)
end)
if Success == true then --if it worked and we didnt have any error finding their dataa
if User_Data ~= nil then
local Decoded_User_Data = Http:JSONDecode(User_Data)
for Index, Value in pairs(Decoded_User_Data) do
if Index then
if Value ~= nil then
if gameData then
if gameData:FindFirstChild(Index) then
--above are just checks for
gameData:WaitForChild(Index).Value = Value
end
end
end
end
end
else
print("players data is nil setting default values")
for Index, Value in pairs(gameData:GetChildren()) do
Value.Value = game.ServerStorage.SettingsTemplate:FindFirstChild(Value).Value
end
end
else
error("failed to load data for "..player.Name) --something went wrong, idk, never got this so no clue how to fix
end
--while wait(600) do
-- saveData(player) --Uncomment if you want badly optimized auto saves or something, would recommend leaving commented
--end
end
game.Players.PlayerAdded:Connect(function(player)
--sets up the players gameData (settings) and loads it when they join the game
local gameData = SettingsTemplate:Clone()
gameData.Name = "Settings"
gameData.Parent = player
loadData(player, gameData)
end)
game.Players.PlayerRemoving:Connect(function(plr)
saveData(plr, plr:WaitForChild("Settings"):GetChildren())
--saves the players data when they leave the game
end)
updateEvent.OnServerEvent:Connect(function(plr, setting, changeValue)
--when we fire from the LocalScript to change the value this will change it
local selectedSetting = plr:WaitForChild("Settings"):WaitForChild(setting)
selectedSetting.Value = changeValue
saveData(plr)
end)
Now I can show you how to actually use the selected keybinds for stuff in your game!
We will be using Enum.KeyCode[MySetting.Value] for the UIS inputs here, so just take a look
- This is a LocalScript in StartCharacterScripts named “SprintHandler” just for an example on how the keybinds would be used
local plr = game.Players.LocalPlayer
local char = plr.Character or plr.CharacterAdded:Wait()
local humanoid = char:WaitForChild("Humanoid")
local cam = game.Workspace.Camera
local UIS = game:GetService("UserInputService")
local sprinting = false
local function checkSprint()
if sprinting == true then
humanoid.WalkSpeed = humanoid.WalkSpeed + 10
wait()
else
humanoid.WalkSpeed = humanoid.WalkSpeed - 10
wait()
end
--a very nifty way of doing sprinting I used for my game because of biomes that have different speeds, for example snow
--instead of setting it to a value like "20" you can just add 10 to whatever it is and get a sprint boost!
end
-- just the functions to activate or de-activate sprinting
UIS.InputBegan:Connect(function(input)
if input.UserInputType == Enum.UserInputType.Keyboard then
if input.KeyCode == Enum.KeyCode[plr:WaitForChild("Settings"):WaitForChild("SprintKeybind").Value] then
-- the line above me (27) is how we detect the sprint setting, and we use [] to attach a value to the enum detection and
-- boom, it works!
sprinting = true
checkSprint()
end
end
end)
UIS.InputEnded:Connect(function(input)
if input.UserInputType == Enum.UserInputType.Keyboard then
if input.KeyCode == Enum.KeyCode[plr:WaitForChild("Settings"):WaitForChild("SprintKeybind").Value] then
sprinting = false
checkSprint()
end
end
end)
Yes this way of doing it with remote events for every keybind is sloppy and not the best, so if anyone has any better ideas they will be tried and cred
(This took me a bit to make and it was really late & early while writing this, so if I missed anything please let me know!)
Ask any questions in the replies, and let me know if anyone uses this, I wish more games allowed keybind editting
I use this datastore framework from a few forum posts, if this is anyones original design link the post and credit will be given!
With that I hope everyone has a happy holiday, and here is the downloadable place