Roblox · Приключение · урок 4

Меч, зелья и награды

Отбиваемся мечом, лечимся зельем, прячемся зельем невидимости. Побеждённые гоблины роняют монеты и возвращаются, а охотник даёт квест на их уничтожение.

Структура

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

StarterPack
└─ Sword (Tool)
   ├─ Handle (Part) — клинок
   └─ SwordScript (Script)
ServerStorage
├─ Drop (Part) — мешочек с монетами
│  └─ DropScript (Script)
└─ Shop (Folder) — товары (урок «Магазин»)
   ├─ HealingPotion (Tool) — Handle → Drink (Sound); PotionScript
   └─ InvisiblePotion (Tool) — Handle; InvisibleScript
ServerScriptService
└─ EnemyManager (Script)
Workspace
└─ Hunter (Model) — QuestGiver: «Уничтожение», 3, 30

Шаги

  1. Меч: Tool «Sword», в нём Part «Handle» (0.3 × 4 × 0.8, Anchored и CanCollide выкл.). Tool → GripPos (0, −1.5, 0) — держим за низ. Script «SwordScript». Tool — в StarterPack.
  2. Взмах бьёт каждую цель один раз и вешает метку «creator» — кто ударил. По ней EnemyManager даёт монеты и прогресс квеста.
  3. Зелье лечения: Tool «HealingPotion», Handle (Anchored выкл.) со звуком «Drink», Script «PotionScript».
  4. Зелье невидимости: Tool «InvisiblePotion» со Script «InvisibleScript». Сначала зелье переносится в ServerScriptService — если удалить его сразу, удалится и скрипт, и игрок останется невидимым навсегда.
  5. Зелья положи в ServerStorage → Folder «Shop» — их будет продавать торговец. Для проверки скопируй в StarterPack.
  6. Награда: Part «Drop» (мешочек, Anchored выкл.) со Script «DropScript» — в ServerStorage.
  7. ServerScriptService → Script «EnemyManager»: возрождение врагов, Drop, монеты и квест. В методичке скрипт возрождения лежал в самом враге — и удалялся вместе с ним.
  8. Охотник «Hunter» — копия старосты с другими значениями: Title «Уничтожение», «Победи 3 гоблинов», 3, 30.

Скрипты

SwordScript

Где: StarterPack → Sword · Script
Зачем: Удар мечом.

-- игроки
local Players = game:GetService("Players")
-- удаляет объекты через время
local Debris = game:GetService("Debris")
-- меч
local tool = script.Parent
-- клинок
local handle = tool:WaitForChild("Handle")
-- урон за удар
local DAMAGE = 25
-- длина взмаха
local COOLDOWN = 0.6

-- анимация взмаха
local slash = Instance.new("Animation")
-- стандартная R15
slash.AnimationId = "rbxassetid://522635514"
-- идёт взмах
local swinging = false
-- кого уже ударили этим взмахом
local hitList = {}

-- клик мышью
tool.Activated:Connect(function()
    -- взмах ещё идёт
    if swinging then return end
    -- владелец
    local humanoid = tool.Parent:FindFirstChildOfClass("Humanoid")
    -- погибший не бьёт
    if not humanoid or humanoid.Health <= 0 then return end
    -- начали
    swinging = true
    -- новый взмах — список пуст
    table.clear(hitList)
    -- анимации владельца
    local animator = humanoid:FindFirstChildOfClass("Animator")
    -- взмах
    if animator then animator:LoadAnimation(slash):Play() end
    -- ждём
    task.wait(COOLDOWN)
    -- можно снова
    swinging = false
-- конец функции
end)

-- клинок коснулся
handle.Touched:Connect(function(hit)
    -- бьём только во время взмаха
    if not swinging then return end
    -- модель, которой принадлежит деталь
    local model = hit:FindFirstAncestorOfClass("Model")
    -- её Humanoid
    local target = model and model:FindFirstChildOfClass("Humanoid")
    -- не цель, сам себя или уже ударили
    if not target or target.Health <= 0 or model == tool.Parent or hitList[target] then return end
    -- игроков не бьём — только противников
    if Players:GetPlayerFromCharacter(model) then return end
    -- этим взмахом больше не бьём
    hitList[target] = true
    -- прошлая метка
    local old = target:FindFirstChild("creator")
    -- убираем
    if old then old:Destroy() end
    -- метка «кто ударил»
    local tag = Instance.new("ObjectValue")
    -- стандартное имя метки в Roblox
    tag.Name = "creator"
    -- игрок с мечом
    tag.Value = Players:GetPlayerFromCharacter(tool.Parent)
    -- в Humanoid противника
    tag.Parent = target
    -- через 2 секунды метка исчезнет
    Debris:AddItem(tag, 2)
    -- урон
    target:TakeDamage(DAMAGE)
-- конец функции
end)

PotionScript

Где: ServerStorage → Shop → HealingPotion · Script
Зачем: Лечит на 50.

-- удаляет объекты через время
local Debris = game:GetService("Debris")
-- зелье
local tool = script.Parent
-- сколько лечит
local HEAL = 50

-- клик — выпить
tool.Activated:Connect(function()
    -- кто пьёт
    local humanoid = tool.Parent:FindFirstChildOfClass("Humanoid")
    -- погибшим не помочь
    if not humanoid or humanoid.Health <= 0 then return end
    -- +50, но не больше максимума
    humanoid.Health = math.min(humanoid.Health + HEAL, humanoid.MaxHealth)
    -- звук глотка
    local sound = tool.Handle:FindFirstChild("Drink")
    -- звук есть
    if sound and humanoid.RootPart then
        -- переносим в игрока — иначе пропадёт вместе с зельем
        sound.Parent = humanoid.RootPart
        -- буль
        sound:Play()
        -- потом удалим
        Debris:AddItem(sound, 3)
    -- конец проверки
    end
    -- зелье выпито
    tool:Destroy()
-- конец функции
end)

InvisibleScript

Где: ServerStorage → Shop → InvisiblePotion · Script
Зачем: Невидимость на 5 секунд.

-- здесь скрипт доработает после того, как зелье пропадёт из рук
local SSS = game:GetService("ServerScriptService")
-- зелье
local tool = script.Parent
-- секунд невидимости
local DURATION = 5
-- уже выпито
local used = false

-- плавно меняет прозрачность
local function Fade(saved, toInvisible)
    -- 10 шагов
    for i = 1, 10 do
        -- доля невидимости
        local k = toInvisible and i / 10 or 1 - i / 10
        -- каждая деталь
        for obj, base in pairs(saved) do
            -- от своей прозрачности к 1
            if obj.Parent then obj.Transparency = base + (1 - base) * k end
        -- конец перебора
        end
        -- полсекунды на всё
        task.wait(0.05)
    -- конец цикла
    end
-- конец функции
end

-- клик — выпить
tool.Activated:Connect(function()
    -- уже
    if used then return end
    -- кто пьёт
    local character = tool.Parent
    -- Humanoid
    local humanoid = character:FindFirstChildOfClass("Humanoid")
    -- погибший не пьёт
    if not humanoid or humanoid.Health <= 0 then return end
    -- выпито
    used = true
    -- убираем из рук, но скрипт продолжает работать
    tool.Parent = SSS
    -- деталь → её прозрачность
    local saved = {}
    -- всё в персонаже
    for _, obj in ipairs(character:GetDescendants()) do
        -- видимые детали и лицо
        if (obj:IsA("BasePart") or obj:IsA("Decal")) and obj.Name ~= "HumanoidRootPart" then
            -- запоминаем
            saved[obj] = obj.Transparency
        -- конец проверки
        end
    -- конец перебора
    end
    -- противники перестают видеть
    character:SetAttribute("Invisible", true)
    -- исчезаем
    Fade(saved, true)
    -- ждём
    task.wait(DURATION)
    -- появляемся
    Fade(saved, false)
    -- снова виден
    character:SetAttribute("Invisible", nil)
    -- теперь зелье можно удалить
    tool:Destroy()
-- конец функции
end)

DropScript

Где: ServerStorage → Drop · Script
Зачем: Мешочек с монетами.

-- игроки
local Players = game:GetService("Players")
-- мешочек
local drop = script.Parent
-- сколько монет внутри
local COINS = 5
-- через сколько секунд исчезнет
local LIFETIME = 30
-- уже подобран
local taken = false

-- касание
drop.Touched:Connect(function(hit)
    -- уже взяли
    if taken then return end
    -- игрок?
    local player = Players:GetPlayerFromCharacter(hit.Parent)
    -- его таблица
    local stats = player and player:FindFirstChild("leaderstats")
    -- не игрок
    if not stats then return end
    -- больше не берётся
    taken = true
    -- монеты
    stats.Coins.Value += COINS
    -- мешочек пропал
    drop:Destroy()
-- конец функции
end)

-- ждём
task.wait(LIFETIME)
-- никто не взял — убираем
if drop.Parent then drop:Destroy() end

EnemyManager

Где: ServerScriptService · Script
Зачем: Возрождение, награды, квест.

-- здесь лежит награда Drop
local SS = game:GetService("ServerStorage")
-- все противники
local enemies = workspace:WaitForChild("Enemies")
-- через сколько секунд противник вернётся
local RESPAWN_TIME = 10
-- монет за победу
local KILL_REWARD = 3
-- квест, которому засчитываются победы
local KILL_QUEST = "Уничтожение"

-- следим за одним противником
local function Setup(enemy)
    -- копия целого — для возрождения
    local sample = enemy:Clone()
    -- где стоял
    local spot = enemy:GetPivot()
    -- Humanoid
    local humanoid = enemy:WaitForChild("Humanoid")
    -- противник погиб
    humanoid.Died:Connect(function()
        -- метка от меча
        local tag = humanoid:FindFirstChild("creator")
        -- кто победил
        local player = tag and tag.Value
        -- игрок ещё в игре
        if player and player.Parent then
            -- таблица
            local stats = player:FindFirstChild("leaderstats")
            -- монеты
            if stats then stats.Coins.Value += KILL_REWARD end
            -- квест
            local quest = player:FindFirstChild("QuestInfo")
            -- взят квест на противников
            if quest and quest.Title.Value == KILL_QUEST then
                -- +1
                quest.Progress.Value += 1
            -- конец проверки
            end
        -- конец проверки
        end
        -- мешочек с монетами
        local drop = SS:FindFirstChild("Drop")
        -- есть
        if drop then
            -- копия
            local copy = drop:Clone()
            -- там, где упал противник
            copy:PivotTo(CFrame.new(enemy:GetPivot().Position))
            -- в мир
            copy.Parent = workspace
        -- конец проверки
        end
        -- ждём
        task.wait(RESPAWN_TIME)
        -- убираем тело
        enemy:Destroy()
        -- новый противник
        local new = sample:Clone()
        -- на старое место
        new:PivotTo(spot)
        -- в игру — его EnemyScript запустится сам
        new.Parent = enemies
        -- следим и за ним
        Setup(new)
    -- конец функции
    end)
-- конец функции
end

-- все противники
for _, enemy in ipairs(enemies:GetChildren()) do
    -- следим
    Setup(enemy)
-- конец перебора
end

Проверка

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