Roblox · Дополнительное

Сундук с наградой

Открыл сундук — крышка откидывается, +50 монет. Следующий раз — только через 5 минут: на надписи идёт обратный отсчёт, у каждого игрока свой. Перезаход отсчёт не сбрасывает.

Структура

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

Workspace
└─ Chests (Folder) — все сундуки
   └─ Chest1 (Model) — имя у каждого сундука своё
      ├─ Base (Part) — короб; внутри Sound «Open» и ProximityPrompt
      ├─ Lid (Part) — крышка
      ├─ Hinge (Part) — невидимая петля у заднего края крышки
      ├─ Configuration (Configuration) — Reward, Cooldown (секунд)
      └─ ChestServer (Script)
StarterPlayer → StarterPlayerScripts
└─ ChestLocal (LocalScript) — отсчёт на надписи

Шаги

  1. Нужна система монет (раздел «Системы» → «Монеты»). Сборщик поставит её сам.
  2. Сундук: короб «Base» (ProximityPrompt и Sound «Open»), крышка «Lid», невидимая деталь «Hinge» вдоль заднего края крышки — вокруг неё крышка откидывается.
  3. Workspace → Folder «Chests». У каждого сундука своё имя (Chest1, Chest2…) — по нему считается свой таймер.
  4. Сервер хранит время следующего открытия по номеру игрока (UserId) — выйти и зайти, чтобы сбросить таймер, не выйдет. Время — workspace:GetServerTimeNow(), одинаковое у сервера и игроков.
  5. ChestLocal меняет надпись только у своего игрока: «Через 4:59» или «Открыть».

Скрипты

ChestServer

Где: Workspace → Chests → Chest1 · Script
Зачем: Награда, таймер и крышка.

-- плавное движение
local TS = game:GetService("TweenService")
-- все игроки
local Players = game:GetService("Players")
-- модуль монет (раздел «Системы»)
local Coins = require(game:GetService("ServerScriptService"):WaitForChild("Coins"))
-- сундук
local model = script.Parent
-- короб
local base = model:WaitForChild("Base")
-- крышка
local lid = model:WaitForChild("Lid")
-- петля крышки (невидимая деталь у заднего края)
local hinge = model:WaitForChild("Hinge")
-- «Открыть»
local prompt = base:WaitForChild("ProximityPrompt")
-- Reward, Cooldown
local config = model:WaitForChild("Configuration")
-- имя атрибута игрока для этого сундука
local key = "ChestReady_" .. model.Name
-- когда сундук снова откроется для игрока (по номеру — переживает перезаход)
local readyAt = {}
-- крышка закрыта
local closedCF = lid.CFrame
-- крышка откинута на петле
local openCF = hinge.CFrame * CFrame.Angles(math.rad(-100), 0, 0) * hinge.CFrame:ToObjectSpace(lid.CFrame)

-- игрок зашёл (или вернулся)
Players.PlayerAdded:Connect(function(player)
    -- показать ему его таймер
    player:SetAttribute(key, readyAt[player.UserId])
-- конец обработчика
end)

-- нажал «Открыть»
prompt.Triggered:Connect(function(player)
    -- время сервера
    local now = workspace:GetServerTimeNow()
    -- ещё рано
    if now < (readyAt[player.UserId] or 0) then return end
    -- следующее открытие
    readyAt[player.UserId] = now + config.Cooldown.Value
    -- клиент покажет обратный отсчёт
    player:SetAttribute(key, readyAt[player.UserId])
    -- награда
    Coins.Add(player, config.Reward.Value)
    -- звук
    local sound = base:FindFirstChild("Open")
    -- скрип
    if sound then sound:Play() end
    -- крышка открывается
    TS:Create(lid, TweenInfo.new(0.4, Enum.EasingStyle.Back), {CFrame = openCF}):Play()
    -- через полторы секунды
    task.delay(1.5, function()
        -- закрывается
        TS:Create(lid, TweenInfo.new(0.4), {CFrame = closedCF}):Play()
    -- конец отложенной функции
    end)
-- конец обработчика
end)

ChestLocal

Где: StarterPlayer → StarterPlayerScripts · LocalScript
Зачем: Обратный отсчёт на надписи.

-- этот игрок
local player = game.Players.LocalPlayer
-- все сундуки
local chests = workspace:WaitForChild("Chests")

-- постоянно
while true do
    -- два раза в секунду
    task.wait(0.5)
    -- каждый сундук
    for _, chest in ipairs(chests:GetChildren()) do
        -- его надпись
        local prompt = chest:FindFirstChild("Base") and chest.Base:FindFirstChild("ProximityPrompt")
        -- есть
        if prompt then
            -- сколько ждать
            local left = (player:GetAttribute("ChestReady_" .. chest.Name) or 0) - workspace:GetServerTimeNow()
            -- ещё рано
            if left > 0 then
                -- целые секунды
                local secs = math.ceil(left)
                -- обратный отсчёт — только у этого игрока
                prompt.ActionText = string.format("Через %d:%02d", secs // 60, secs % 60)
            -- можно
            else
                -- надпись
                prompt.ActionText = "Открыть"
            -- конец проверки
            end
        -- конец проверки
        end
    -- конец перебора
    end
-- конец цикла
end

Проверка

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

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