Roblox · Песочница · урок 3

Инструменты, разрушение и лопата

Кирка ломает камни, топор рубит деревья — за несколько ударов, с полоской прочности. Инструменты изнашиваются. Лопата копает ямы и насыпает холмы в ландшафте.

Структура

Синим — что добавляем на этом уроке.

StarterPack
├─ Pickaxe, Axe (Tool)
│  ├─ Handle (Part) — Swing (Sound)
│  ├─ Mining (BoolValue) — true — идёт удар
│  ├─ Wear (IntValue) — прочность, 50
│  └─ ToolScript (Script) — Animation — взмах
└─ Shovel (Tool) — Handle, Terraform (RemoteEvent), ShovelLocal, ShovelServer
Workspace
└─ Nature (Folder)
   └─ Tree, Rock (Model) — тег Breakable; атрибуты Tool, Drop, Respawn
      ├─ MainPart (Part) — невидимая коробка вокруг объекта, CanCollide выкл.; PrimaryPart
      │  └─ Health (BillboardGui) — Back, Green (Frame)
      └─ HitsToBreak (IntValue) — сколько ударов выдержит
ServerScriptService
└─ BreakServer (Script)

Шаги

  1. Инструмент: Tool с Handle (за ручку держат) и головкой, приваренной к Handle. Внутри BoolValue «Mining», IntValue «Wear» = 50, Script «ToolScript» с Animation (стандартный взмах R15: 522635514) и звук «Swing» в Handle.
  2. Ломаемый объект — модель с MainPart (невидимая коробка вокруг, PrimaryPart), IntValue «HitsToBreak» и полоской «Health» (BillboardGui в MainPart: Frame «Back» и Frame «Green»).
  3. Атрибуты модели (Properties → Attributes → «+»): Tool — каким инструментом ломать (Pickaxe или Axe), Drop — что выпадет (Stone, Wood), Respawn (boolean) — вырастет ли снова.
  4. Тег: View → Tag Editor (или Properties → Tags) → «Breakable» на каждой модели. CollectionService находит объекты по тегу.
  5. Блоки из ObjectFolder тоже ломаемые: тег Breakable, Tool и Drop = своё имя — поставил, сломал, вернул.
  6. BreakServer: один удар за взмах (номер взмаха — атрибут Swing инструмента). В методичке подписки добавлялись при каждом новом объекте в Workspace, и один удар засчитывался много раз.
  7. Лопата: Tool «Shovel» с RemoteEvent «Terraform», LocalScript «ShovelLocal» и Script «ShovelServer». ЛКМ — яма, ПКМ — холмик. Terrain:FillBall заполняет шар воздухом или землёй.

Скрипты

ToolScript

Где: StarterPack → Pickaxe / Axe · Script
Зачем: Взмах и износ.

-- кирка или топор
local tool = script.Parent
-- взмах
local anim = script:WaitForChild("Animation")
-- звук взмаха
local swingSound = tool:WaitForChild("Handle"):WaitForChild("Swing")
-- BoolValue: true — идёт удар
local mining = tool:WaitForChild("Mining")
-- IntValue: прочность
local wear = tool:WaitForChild("Wear")
-- взмах идёт
local busy = false

-- клик
tool.Activated:Connect(function()
    -- кто держит
    local humanoid = tool.Parent:FindFirstChildOfClass("Humanoid")
    -- взмах идёт или погиб
    if busy or not humanoid or humanoid.Health <= 0 then return end
    -- начали
    busy = true
    -- номер взмаха — один удар за взмах
    tool:SetAttribute("Swing", (tool:GetAttribute("Swing") or 0) + 1)
    -- звук
    swingSound:Play()
    -- взмах
    if anim.AnimationId ~= "" then humanoid:FindFirstChildOfClass("Animator"):LoadAnimation(anim):Play() end
    -- удар
    mining.Value = true
    -- длина взмаха
    task.wait(0.4)
    -- конец удара
    mining.Value = false
    -- можно снова
    busy = false
-- конец функции
end)
-- прочность изменилась
wear.Changed:Connect(function(value)
    -- сломался
    if value <= 0 then tool:Destroy() end
-- конец функции
end)

BreakServer

Где: ServerScriptService · Script
Зачем: Разрушение по тегу.

-- теги: все ломаемые объекты помечены «Breakable»
local CS = game:GetService("CollectionService")
-- игроки
local Players = game:GetService("Players")
-- тег
local TAG = "Breakable"
-- через сколько секунд вырастает дерево или камень
local RESPAWN = 60

-- настраиваем один ломаемый объект
local function Setup(model)
    -- образец в хранилище или уже настроен
    if not model:IsDescendantOf(workspace) or model:GetAttribute("Ready") then return end
    -- MainPart — невидимая коробка вокруг объекта
    local main = model:IsA("Model") and model.PrimaryPart
    -- сколько ударов выдержит
    local hits = model:FindFirstChild("HitsToBreak")
    -- объект собран неправильно
    if not main or not hits then return end
    -- копия — чтобы вырастить снова
    local sample = model:Clone()
    -- настроен
    model:SetAttribute("Ready", true)
    -- полная прочность
    local maxHits = hits.Value
    -- полоска прочности
    local gui = main:FindFirstChild("Health")
    -- пока не били — не видно
    if gui then gui.Enabled = false end
    -- инструмент → номер последнего засчитанного взмаха
    local lastSwing = {}
    -- чего-то коснулись
    main.Touched:Connect(function(hit)
        -- инструмент
        local tool = hit:FindFirstAncestorOfClass("Tool")
        -- идёт ли удар
        local mining = tool and tool:FindFirstChild("Mining")
        -- не удар или не тот инструмент
        if not mining or not mining.Value or tool.Name ~= model:GetAttribute("Tool") then return end
        -- номер взмаха
        local swing = tool:GetAttribute("Swing")
        -- этот взмах уже засчитан
        if lastSwing[tool] == swing then return end
        -- запоминаем
        lastSwing[tool] = swing
        -- удар
        hits.Value -= 1
        -- прочность инструмента
        local wear = tool:FindFirstChild("Wear")
        -- изнашивается
        if wear then wear.Value -= 1 end
        -- полоска есть
        if gui then
            -- показываем
            gui.Enabled = true
            -- сколько осталось
            gui.Green.Size = UDim2.fromScale(math.max(hits.Value, 0) / maxHits, 1)
        -- конец проверки
        end
        -- ещё держится
        if hits.Value > 0 then return end
        -- где стоял
        local parent = model.Parent
        -- сломан
        model:Destroy()
        -- природный объект
        if model:GetAttribute("Respawn") then
            -- через минуту вырастет снова
            task.delay(RESPAWN, function() sample:Clone().Parent = parent end)
        -- конец проверки
        end
    -- конец функции
    end)
-- конец функции
end

-- все, что уже есть
for _, model in ipairs(CS:GetTagged(TAG)) do Setup(model) end
-- новые: поставленные блоки, выросшие деревья
CS:GetInstanceAddedSignal(TAG):Connect(Setup)

ShovelLocal

Где: StarterPack → Shovel · LocalScript
Зачем: Клики лопатой.

-- игроки
local Players = game:GetService("Players")
-- лопата
local tool = script.Parent
-- просим сервер изменить ландшафт
local terraform = tool:WaitForChild("Terraform")
-- куда указывает курсор
local mouse = Players.LocalPlayer:GetMouse()
-- лопата в руках?
local equipped = false

-- взяли
tool.Equipped:Connect(function() equipped = true end)
-- убрали
tool.Unequipped:Connect(function() equipped = false end)
-- уже в руках
if tool.Parent and tool.Parent:FindFirstChildOfClass("Humanoid") then equipped = true end
-- ЛКМ
tool.Activated:Connect(function()
    -- копать
    terraform:FireServer("Dig", mouse.Hit.Position)
-- конец функции
end)
-- ПКМ
mouse.Button2Down:Connect(function()
    -- насыпать землю
    if equipped then terraform:FireServer("Raise", mouse.Hit.Position) end
-- конец функции
end)

ShovelServer

Где: StarterPack → Shovel · Script
Зачем: Яма или холмик.

-- игроки
local Players = game:GetService("Players")
-- лопата
local tool = script.Parent
-- команда от клиента
local terraform = tool:WaitForChild("Terraform")
-- размер ямы или холмика
local RADIUS = 3
-- дальше не дотянуться
local MAX_DISTANCE = 25
-- не чаще
local COOLDOWN = 0.3
-- время прошлого копка
local last = 0

-- клиент копает
terraform.OnServerEvent:Connect(function(player, action, position)
    -- лопата не у него или мусор
    if Players:GetPlayerFromCharacter(tool.Parent) ~= player or typeof(position) ~= "Vector3" then return end
    -- слишком часто или далеко
    if os.clock() - last < COOLDOWN or player:DistanceFromCharacter(position) > MAX_DISTANCE then return end
    -- запоминаем
    last = os.clock()
    -- копать
    if action == "Dig" then
        -- шар воздуха — яма
        workspace.Terrain:FillBall(position, RADIUS, Enum.Material.Air)
    -- насыпать
    elseif action == "Raise" then
        -- шар земли — холмик
        workspace.Terrain:FillBall(position, RADIUS, Enum.Material.Ground)
    -- конец проверки
    end
-- конец функции
end)

Проверка

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