User settings, how?

Sooo, i really want my game to have some really cool settings!

like… field of view, retro ADS, custom UI colors and so on

but the thing is, i’m not really sure how to accomplish that

i had the idea of using a modulescript inside starter player scripts to store those settings,
but then i got the revelation that i’d have no clue how to store those settings in a datastore

i’d be glad if any of you helped :steamhappy:

2 Likes

You can use a basic module, like you suggested, to store these settings, and apply them globally.

For example:

--i know settings is Studio settings global but ill just overwrite it here sorry!
local settings = { --example settings
    ["FOV"] = 10,
    ["UIColour"] = Color3.fromRGB(85, 170, 0),
    ["SettingChanged"] = nil :: ((string, any) -> (nil))? --this will be a callback
}

--create methods as modifying externally won't always work
function settings:GetSetting(name: string)
    return settings[name]
end

function settings:UpdateSetting(name: string, val: any)
    settings[name] = val
    settings.SettingChanged(name, val)
end

return settings

Now, we can have a LocalScript to apply the changes:

settings.SettingChanged = function(setting: string, value: any)
    --example where UIColour
    if setting == "UIColour" then
        for _, ui in next, playerGui:GetDescendants(), nil do
            --apply changes
        end
end :: (string, any) -> (nil)

As for saving it, sadly you can’t use PlayerRemoving because the module will be destroyed by then but you can save on a regular interval or when settings are changed (not write to the store then, but store the new settings ready for when the player leaves and write to the store then). You can store them in a table and retrieve them when the player joins, pass them along a remote and have the client apply them.

I hope this helps, let me know if you need me to explain any of it!

2 Likes

I was just making user settings like a couple days ago so here is the approach that I took:

  1. First, store the player’s settings in a form of a table in their data via DataStoreService or ProfileService
  2. On the client, have a RemoteFunction – In my case, It’s called ‘GetUserSettings’ – to retrieve the settings the player has saved in their data and invoke it. It should return the player’s settings
  3. Loop through the player’s settings and set an attribute to the player with the name and value using the function :SetAttribute()
  4. Connect a function to the :GetAttributeChangedSignal() event inside the loop
  5. Every time the attribute value changes (e.x. from a true to a false and vice versa) you would want to fire a RemoteEvent to the server and change the player’s settings data on the server
  6. And finally, to actually have functionality you would want to have a folder underneath your ModuleScript or LocalScript or whatever and have ModuleScripts like update_fov or update_uicolors or whatever. Afterwards, inside the ModuleScripts you would want to return a function. Inside of this function is going to be the main functionality you would want for that setting.

For example, for one of my settings I have a setting where you can turn on and off in game music and this is the function I have for that:

--!strict
local SoundService = game:GetService("SoundService")
local Players = game:GetService("Players")

return function(): ()
	local local_player = Players.LocalPlayer
	local music_enabled = local_player:GetAttribute("Music") :: boolean
	if music_enabled == nil then return end

	local sound_group = SoundService:FindFirstChild("Music")
	if not sound_group then return end

	sound_group.Volume = music_enabled and 0.5 or 0
end

And for my :GetAttributeChangedSignal() part, I am using this function:

local function on_update(): ()
	local current_value = local_player:GetAttribute(settingName)
	local module = utility_folder:FindFirstChild(
		`update_{string.lower(settingName)}`) :: ModuleScript
	if module then
		require(module)()
	end
	
	update_button(settingName, current_value)
	update_usersettings:FireServer(settingName, current_value)
end
on_update()
local_player:GetAttributeChangedSignal(settingName):Connect(on_update)

If you have any more questions or would like for me to send more code please ask! I would gladly do it for you.

1 Like

@Free_Br1cks

wellllll

rrright now my module script looks like this D:

image

i kind of find both replies confusing idk why
…like, how would i apply and save those changes?
D:

1 Like

is this really needed? is it not possible to update every single setting using one script?

1 Like

Using attributes may not be the best system as it takes more memory (connections, etc.), but as for applying the settings, you can do it all in one script, you can also group it out into multiple functions:

local settingUpdaters = {
    ["Music"] = function(newVolume: number)
        yourSoundInstance.Volume = newVolume / 10
    end,
    ["UIColours"] = function(colour: Color3)
        for _, ui in next, playerGui:GetDescendants(), nil do
            --update
        end
    end
}

settings.SettingChanged = function(setting: string, ...: values) --... if you need multiple values
    if settingUpdaters[setting] then
        settingUpdaters[setting](table.unpack({...}))
    end
end

But for saving and retrieving, @Free_Br1cks explained a good system for that. Just make sure:

  • DoS/DDoS protection
  • Don’t access the DataStore each time, rather have a table on the server of player settings. Only access the data store during saving and loading (i.e. don’t save or load when player settings update / player requests settings)
1 Like

oookayy, i think i get it?

is this what i’m supposed to do? (step by step)

  • Have a local(?) script that handles changing settings →

  • Send changed information to the server everytime something is changed + fire a BindableEvent(?) and receive it from the module script that changes said settings →

  • Store the changed data in a datastore server-side →

  • Apply said settings whenever the player joins


some questions i find necessary

1: What does the “nil” mean in this for loop?

image


2: Table unpack? why? What is .SettingChanged?

image


3: DDoS protection? where? how? why?

1 Like

Yes

Yes

No, if you use my module structure you can require it from every script and call settings:UpdateSetting()

When receiving the remote, store it on the server but don’t save it until data would be saved anyway (otherwise exploiters could really mess with your data store system)

Yes.

Your necessary questions

all that does is explicitely tell next to start at the beginning of the table when iterating. It’s not needed, I just do it anyway.

table.unpack here because it was an example if you passed multiple different amounts of values when a setting is changed. You likely won’t need it.

Exploiters can spam your remote events and cause lag/script crash or even server crashing. You need to have a cooldown system where they get kicked/server refuses to process their requests if they spam it.

DoS means Denial of Service - it’s where clients flood the server with useless information to stop legitimate requests from getting through and cause the server to crash. It’s kind of the same here, except it’s just a ton of requests.

DDoS means Distributed Denial of Service - the same thing but from multiple computers.

2 Likes

hmmm

image
what is this confusing one liner, it’s nil, but it’s actually a string and it’s nil again??


are you saying i should save said settings on random intervals? or is there some built-in data saving thing

Whenever you would normally save data for the second part. It’s a good idea to have an autosave save data about every ~10 minutes.

For the top bit, that’s just a type annotation, I was trying to indicate there is a callback.

((string, any) -> (nil))?
--(string, any) means it's a callback that takes a string type as first parameter and anything as second parameter
-- -> means returns
-- (nil) means it won't return anything
-- ? means it might be that type or it might be nil
-- so basically we are saying that section might be a callback that takes a string and anything and returns nothing OR it might be nil
-- we later use :: ((string, any) -> (nil)) with no ? to indicate that the callback has been set
--doing it this way just works out with the type solver
--again, it's not necessary I was just trying to show it's a callback

--sorry for not using multiline commenting to explain this :(

dear god,

maybe i’ll put this away for later?
this looks very complicated to me :sob:

this is some next-level luau code

i added a bit of more advanced bits to try and show things, sorry that’s my bad :sob:

dont put it away from later, trying it now will help you learn!
just tell me what bits you need me to explain :slight_smile:

No it is not needed really. It’s an aesthetic choice, I think it looks less condensed and more cleaner because you don’t have a big table of settings and functions

it’s just,
like

all these type definings, double colons, a lot of brackets and question marks
this is very confusing to me
literally NEVER touched things like this

i have also never heard of table.unpack :man_facepalming:

You don’t need to know about the double colon and colon things. Like I said, they’re optional.

table.unpack just returns all the values from a table. It’s the same as unpack.

local myTable = {1, 3, "hi", "foo", "bar"}

print(unpack(myTable)) --> 1 3 hi foo bar
print(table.unpack(myTable)) --> 1 3 hi foo bar

local a, b, c, d, e = unpack(myTable)
print(a) --> 1
print(b) --> 3
print(e) --> bar
1 Like

alright

what exactly should an autosave do, just save user settings or everything?
stupid question, i know, sorry

Yes, it should save everything along side with the player’s settings.

2 Likes

hey, sorry for the bother but uh

am i doing this right?


module script;

image


local script;


(both scripts are in the same directory)’

i’m asking this because how will the local script detect any sort of setting changing?
i need some explanation for this : (

You can use a RemoteEvent to tell the client from the server, or a BindableEvent to tell that between LocalScripts that something has changed.

also, whats that color theme? i like it

…so

fire a remote event with the changed setting in the module script, then fire another remote event on the server that tells the client something has changed and passes that value

kind of a good idea, verify the information on the server first before applying it


Courier, 10ot, Normal ← Font