Roblox · Шутер · урок 7

Граната

Замах по клику, в нужный момент анимации граната вылетает вперёд-вверх и через полторы секунды взрывается.

Структура

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

Workspace
└─ Grenade (Tool)
   ├─ Handle (Part) — модель гранаты
   │  ├─ Throw (Sound) — звук броска
   │  ├─ Boom (Sound) — звук взрыва
   │  ├─ Attachment (Attachment) — точка приложения силы
   │  └─ VectorForce (VectorForce) — Attachment0 = Attachment
   ├─ Animation (Animation) — AnimationId броска
   ├─ Throw (RemoteEvent) — клиент → сервер
   ├─ GrenadeLocal (LocalScript)
   └─ GrenadeServer (Script)

Шаги

  1. Анимация броска (Rig Builder + Animation Editor): кадр, где рука отпускает гранату, — ПКМ → Rename Key Keyframe → «Release».
  2. Приоритет Action, Publish, id → Animation.AnimationId.
  3. VectorForce: ApplyAtCenterOfMass вкл., Force = 0, 0, 0, RelativeTo = World, Attachment0 = Attachment.
  4. Handle: Anchored выкл., CanCollide выкл., Massless вкл.

Скрипты

GrenadeLocal

Где: Workspace → Grenade · LocalScript
Зачем: По клику играет замах; на кадре Release просит сервер бросить.

-- граната (Tool)
local grenade = script.Parent
-- анимация броска
local anim = grenade:WaitForChild("Animation")
-- сигнал «бросок» на сервер
local throwEvent = grenade:WaitForChild("Throw")
-- загрузим, когда возьмём гранату
local animTrack = nil
-- бросок уже начат?
local thrown = false

-- взяли гранату
local function OnEquipped()
    -- анимации ещё нет — бросим без замаха
    if anim.AnimationId == "" then return end
    -- Humanoid персонажа
    local humanoid = grenade.Parent:WaitForChild("Humanoid")
    -- готовим анимацию
    animTrack = humanoid.Animator:LoadAnimation(anim)
    -- анимация дошла до именованного кадра
    animTrack.KeyframeReached:Connect(function(kfName)
        -- кадр, где рука отпускает гранату
        if kfName == "Release" then
            -- просим сервер бросить
            throwEvent:FireServer()
        -- конец проверки
        end
    -- конец обработчика кадра
    end)
-- конец функции
end
-- каждый раз, когда берут в руки
grenade.Equipped:Connect(OnEquipped)
-- подобрали с земли: скрипт запустился уже в руках — Equipped был раньше
if grenade.Parent and grenade.Parent:FindFirstChildOfClass("Humanoid") then OnEquipped() end

-- убрали гранату
grenade.Unequipped:Connect(function()
    -- бросок отменён — можно снова
    thrown = false
-- конец обработчика
end)

-- клик, пока граната в руках
grenade.Activated:Connect(function()
    -- уже бросаем — второй раз не надо
    if thrown then return end
    -- бросок начат
    thrown = true
    -- анимация есть
    if animTrack then
        -- замах — бросок на кадре Release
        animTrack:Play()
    -- анимации нет
    else
        -- бросаем сразу
        throwEvent:FireServer()
    -- конец проверки
    end
-- конец обработчика
end)

GrenadeServer

Где: Workspace → Grenade · Script
Зачем: Бросает копию гранаты, взрывает её и убирает гранату из инвентаря.

-- граната (Tool)
local grenade = script.Parent
-- модель гранаты
local handle = grenade:WaitForChild("Handle")
-- сигнал «бросок» от клиента
local throwEvent = grenade:WaitForChild("Throw")
-- удаление по таймеру
local Debris = game:GetService("Debris")
-- граната одна — бросок один
local thrown = false

-- клиент дошёл до кадра Release
throwEvent.OnServerEvent:Connect(function(player)
    -- уже брошена или не в руках у этого игрока
    if thrown or grenade.Parent ~= player.Character then return end
    -- запоминаем
    thrown = true
    -- звук броска
    handle.Throw:Play()
    -- летящая копия гранаты
    local grenadeClone = handle:Clone()
    -- отскакивает от земли и стен
    grenadeClone.CanCollide = true
    -- в мир
    grenadeClone.Parent = workspace
    -- граната в руке исчезла
    handle.Transparency = 1
    -- корпус персонажа
    local hrp = player.Character:WaitForChild("HumanoidRootPart")
    -- направление: вперёд и вверх
    local dir = hrp.CFrame.LookVector + hrp.CFrame.UpVector
    -- толчок; учитываем массу — любая граната летит одинаково
    grenadeClone.VectorForce.Force = dir * grenadeClone.AssemblyMass * 300
    -- толкаем 0,2 секунды
    task.wait(0.2)
    -- дальше летит сама
    grenadeClone.VectorForce.Enabled = false
    -- запал
    task.wait(1.5)
    -- звук взрыва
    grenadeClone.Boom:Play()
    -- взрыв
    local exp = Instance.new("Explosion")
    -- там, где граната
    exp.Position = grenadeClone.Position
    -- радиус
    exp.BlastRadius = 20
    -- сила отбрасывания
    exp.BlastPressure = 500000
    -- взрываем
    exp.Parent = workspace
    -- граната исчезла
    grenadeClone.Transparency = 1
    -- стоит на месте, пока доигрывает звук
    grenadeClone.Anchored = true
    -- ни с чем не сталкивается
    grenadeClone.CanCollide = false
    -- через 5 с удаляется
    Debris:AddItem(grenadeClone, 5)
    -- убираем гранату из инвентаря — в самом конце: скрипт внутри неё
    grenade:Destroy()
-- конец обработчика
end)

Проверка

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