Roblox · Интерфейсы

Окно настроек с сохранением

Окно «Настройки»: громкость музыки, громкость звуков, угол обзора камеры. Кнопки «−» и «+», всё применяется сразу и сохраняется до следующего захода.

Структура

Синим — что нужно создать.

ReplicatedStorage
└─ SettingsEvent (RemoteEvent)
SoundService
└─ Music, Effects (SoundGroup) — группы звуков
ServerScriptService
└─ SettingsServer (Script)
StarterGui
└─ SettingsGui (ScreenGui) — ResetOnSpawn выкл.
   ├─ OpenButton (TextButton) — справа вверху
   ├─ Window (Frame) — Visible выкл.
   │  ├─ Music, Effects, FOV (Frame) — Caption, Minus, Value, Plus
   │  └─ CloseButton (TextButton)
   └─ SettingsLocal (LocalScript)

Шаги

  1. SoundService → две SoundGroup: «Music» и «Effects». У каждого звука в игре укажи свойство SoundGroup — музыке Music, остальным Effects. Громкость группы меняет громкость всех её звуков.
  2. ReplicatedStorage → RemoteEvent «SettingsEvent». ServerScriptService → Script «SettingsServer».
  3. Настройки хранятся атрибутами игрока: сервер загружает их из DataStore при входе и сохраняет при выходе. Клиент только просит изменить — сервер проверяет границы.
  4. SettingsGui → TextButton «OpenButton» и Frame «Window». В окне три строки-Frame с именами «Music», «Effects», «FOV»; в каждой TextLabel «Caption», кнопки «Minus» и «Plus», TextLabel «Value» между ними.
  5. LocalScript «SettingsLocal»: громкость — у каждого игрока своя, угол обзора — свойство FieldOfView камеры.
  6. Сохранение в Studio — после публикации и Game Settings → Security → Enable Studio Access to API Services.

Скрипты

SettingsServer

Где: ServerScriptService · Script
Зачем: Загрузка, проверка и сохранение настроек.

-- игроки
local Players = game:GetService("Players")
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- сохранение
local DSS = game:GetService("DataStoreService")
-- игрок меняет настройку
local event = RS:WaitForChild("SettingsEvent")
-- хранилище настроек
local store = DSS:GetDataStore("PlayerSettings")
-- значения по умолчанию
local DEFAULTS = {Music = 5, Effects = 5, FOV = 70}
-- границы: от и до
local LIMITS = {Music = {0, 10}, Effects = {0, 10}, FOV = {60, 100}}
-- у кого загрузка удалась
local loaded = {}

-- игрок зашёл
local function Setup(player)
    -- загружаем
    local ok, data = pcall(function() return store:GetAsync("settings_" .. player.UserId) end)
    -- ничего нет — пусто
    if type(data) ~= "table" then data = {} end
    -- каждая настройка
    for name, value in pairs(DEFAULTS) do
        -- сохранённое значение
        local saved = data[name]
        -- атрибут игрока — его видит и клиент
        player:SetAttribute(name, type(saved) == "number" and saved or value)
    -- конец перебора
    end
    -- можно сохранять, только если загрузилось
    loaded[player] = ok
-- конец функции
end

-- сохранить
local function Save(player)
    -- не загрузилось — не затираем
    if not loaded[player] then return end
    -- что сохраняем
    local data = {}
    -- все настройки
    for name in pairs(DEFAULTS) do data[name] = player:GetAttribute(name) end
    -- сохраняем
    pcall(function() store:SetAsync("settings_" .. player.UserId, data) end)
-- конец функции
end

-- игрок нажал «+» или «−»
event.OnServerEvent:Connect(function(player, name, value)
    -- есть такая настройка?
    local limit = LIMITS[name]
    -- чужое имя, не число или NaN
    if not limit or type(value) ~= "number" or value ~= value then return end
    -- в границах, целое
    player:SetAttribute(name, math.clamp(math.floor(value + 0.5), limit[1], limit[2]))
-- конец функции
end)
-- новые игроки
Players.PlayerAdded:Connect(Setup)
-- кто зашёл раньше скрипта
for _, player in ipairs(Players:GetPlayers()) do task.spawn(Setup, player) end
-- игрок вышел
Players.PlayerRemoving:Connect(function(player)
    -- сохраняем
    Save(player)
    -- забываем
    loaded[player] = nil
-- конец функции
end)
-- сервер закрывается
game:BindToClose(function()
    -- сохраняем всех
    for _, player in ipairs(Players:GetPlayers()) do Save(player) end
-- конец функции
end)

SettingsLocal

Где: StarterGui → SettingsGui · LocalScript
Зачем: Окно настроек.

-- игроки
local Players = game:GetService("Players")
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- группы звуков
local SoundService = game:GetService("SoundService")
-- этот игрок
local player = Players.LocalPlayer
-- сообщить серверу
local event = RS:WaitForChild("SettingsEvent")
-- SettingsGui
local gui = script.Parent
-- окно настроек
local window = gui:WaitForChild("Window")
-- от, до, шаг кнопки
local STEPS = {Music = {0, 10, 1}, Effects = {0, 10, 1}, FOV = {60, 100, 5}}

-- применить настройки
local function Apply()
    -- группа музыки
    local music = SoundService:FindFirstChild("Music")
    -- группа эффектов
    local effects = SoundService:FindFirstChild("Effects")
    -- громкость музыки 0…1 — только у этого игрока
    if music then music.Volume = (player:GetAttribute("Music") or 5) / 10 end
    -- громкость звуков
    if effects then effects.Volume = (player:GetAttribute("Effects") or 5) / 10 end
    -- угол обзора
    workspace.CurrentCamera.FieldOfView = player:GetAttribute("FOV") or 70
    -- каждая строка окна
    for name in pairs(STEPS) do
        -- число в строке
        window[name].Value.Text = tostring(player:GetAttribute(name) or "")
    -- конец перебора
    end
-- конец функции
end

-- каждая настройка
for name, step in pairs(STEPS) do
    -- её строка
    local row = window:WaitForChild(name)
    -- нажали «−» или «+»
    local function Change(direction)
        -- сейчас
        local current = player:GetAttribute(name) or step[1]
        -- новое значение — серверу
        event:FireServer(name, math.clamp(current + direction * step[3], step[1], step[2]))
    -- конец функции
    end
    -- «−»
    row:WaitForChild("Minus").Activated:Connect(function() Change(-1) end)
    -- «+»
    row:WaitForChild("Plus").Activated:Connect(function() Change(1) end)
    -- сервер принял — применяем
    player:GetAttributeChangedSignal(name):Connect(Apply)
-- конец перебора
end
-- закрыть
window:WaitForChild("CloseButton").Activated:Connect(function() window.Visible = false end)
-- кнопка настроек
gui:WaitForChild("OpenButton").Activated:Connect(function() window.Visible = not window.Visible end)
-- сразу при входе
Apply()

Проверка

Запусти Play (или Run) и убедись:

Собрать окно настроек одной командой