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

Урон, счётчик патронов и прицел

Пуля ранит всё живое (кроме стрелка) и исчезает. На экране — патроны «28 / 30». ПКМ — прицел с приближением.

Структура

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

Workspace
└─ Gun (Tool)
   ├─ Handle (Part)
   ├─ GunGui (ScreenGui) — появится на экране, когда оружие в руках
   │  └─ Frame (Frame) — подложка
   │     └─ TextLabel (TextLabel) — «30 / 30»
   ├─ GunLocal (LocalScript)
   └─ GunServer (Script)

Шаги

  1. GunGui собери в StarterGui (там его видно), потом перенеси внутрь Gun.
  2. Размеры — через Scale, тогда на любом экране одинаково.
  3. Перекрестье: Toolbox → Images → «Crosshair», id — в строку mouse.Icon.

Скрипты

GunServer

Где: Workspace → Gun · Script
Зачем: Пуля один раз ранит того, в кого попала, и исчезает.

-- оружие (Tool)
local gun = script.Parent
-- модель оружия
local handle = gun:WaitForChild("Handle")
-- сигнал «выстрел» от клиента
local shotEvent = gun:WaitForChild("Shot")
-- звук выстрела
local shootSound = handle:WaitForChild("Shoot")
-- удаление по таймеру
local Debris = game:GetService("Debris")
-- скорость пули
local bulletSpeed = 500
-- урон за попадание
local damage = 15

-- клиент сообщил о выстреле и точке под курсором
shotEvent.OnServerEvent:Connect(function(player, mousePosition)
    -- стреляет только тот, у кого оружие в руках
    if gun.Parent ~= player.Character then return end
    -- новая пуля
    local bullet = Instance.new("Part")
    -- шарик
    bullet.Shape = Enum.PartType.Ball
    -- маленький
    bullet.Size = Vector3.new(0.3, 0.3, 0.3)
    -- чёрный
    bullet.BrickColor = BrickColor.new("Black")
    -- не отскакивает от своего же оружия
    bullet.CanCollide = false
    -- у ствола, смотрит на курсор
    bullet.CFrame = CFrame.new(handle.Position, mousePosition)
    -- в мир
    bullet.Parent = workspace
    -- физику пули считает сервер — попадания честные
    bullet:SetNetworkOwner(nil)
    -- летит вперёд
    bullet.AssemblyLinearVelocity = bullet.CFrame.LookVector * bulletSpeed
    -- звук выстрела
    shootSound:Play()
    -- пуля ещё никого не задела
    local hit = false
    -- пуля чего-то коснулась
    bullet.Touched:Connect(function(otherPart)
        -- уже попала или задела стрелка — пропускаем
        if hit or otherPart:IsDescendantOf(player.Character) then return end
        -- запоминаем попадание
        hit = true
        -- модель, которой принадлежит деталь
        local model = otherPart:FindFirstAncestorOfClass("Model")
        -- есть ли у неё Humanoid (живой ли)
        local humanoid = model and model:FindFirstChildOfClass("Humanoid")
        -- попали в живого
        if humanoid then
            -- урон
            humanoid:TakeDamage(damage)
        -- конец проверки
        end
        -- пуля исчезает при любом попадании
        bullet:Destroy()
    -- конец обработчика касания
    end)
    -- промах — пуля удалится через 10 секунд
    Debris:AddItem(bullet, 10)
-- конец обработчика
end)

GunLocal

Где: Workspace → Gun · LocalScript
Зачем: Счётчик патронов на экране, курсор-перекрестье, прицел на ПКМ.

-- оружие (Tool)
local gun = script.Parent
-- сигнал «выстрел» на сервер
local shotEvent = gun:WaitForChild("Shot")
-- этот игрок
local player = game.Players.LocalPlayer
-- его мышь: mouse.Hit — точка под курсором
local mouse = player:GetMouse()
-- оружие в руках?
local equipped = false
-- нажатия клавиш
local UIS = game:GetService("UserInputService")
-- модель оружия
local handle = gun:WaitForChild("Handle")
-- щелчок пустого магазина
local emptySound = handle:WaitForChild("Empty")
-- звук перезарядки
local reloadSound = handle:WaitForChild("Reload")
-- патроны в магазине сейчас
local ammo = gun:WaitForChild("Ammo")
-- вместимость магазина
local maxAmmo = gun:WaitForChild("MaxAmmo")
-- анимация перезарядки
local reloadAnimation = script:WaitForChild("Animation")
-- загрузим, когда возьмём оружие
local animTrack = nil
-- идёт перезарядка?
local reloading = false
-- счётчик патронов
local gunGui = gun:WaitForChild("GunGui")
-- надпись «30 / 30»
local ammoText = gunGui.Frame.TextLabel
-- камера — для прицела
local camera = workspace.CurrentCamera

-- обновляет счётчик
local function UpdateAmmo()
    -- «патроны / вместимость»
    ammoText.Text = ammo.Value .. " / " .. maxAmmo.Value
-- конец функции
end
-- патроны изменились — обновляем
ammo.Changed:Connect(UpdateAmmo)

-- события каждого кадра
local RunService = game:GetService("RunService")
-- показывать оружие в виде от первого лица
local SHOW_GUN = true
-- сейчас целимся?
local aiming = false
-- режим камеры до прицеливания
local normalMode = nil
-- минимальное приближение до прицеливания
local normalMinZoom = nil
-- подписка «оружие видно»
local showConnection = nil

-- включить или выключить вид от первого лица
local function FirstPerson(on)
    -- уже так — ничего не делаем
    if aiming == on then return end
    -- запоминаем
    aiming = on
    -- начали целиться
    if on then
        -- запоминаем режим камеры
        normalMode = player.CameraMode
        -- и минимальное приближение
        normalMinZoom = player.CameraMinZoomDistance
        -- камера в голове персонажа
        player.CameraMode = Enum.CameraMode.LockFirstPerson
        -- оружие должно быть видно
        if SHOW_GUN then
            -- каждый кадр
            showConnection = RunService.RenderStepped:Connect(function()
                -- все детали оружия
                for _, part in ipairs(gun:GetDescendants()) do
                    -- Roblox прячет их от первого лица — возвращаем
                    if part:IsA("BasePart") then part.LocalTransparencyModifier = 0 end
                -- конец перебора
                end
            -- конец обработчика
            end)
        -- конец проверки
        end
    -- перестали целиться
    else
        -- оружие снова прячется как обычно
        if showConnection then showConnection:Disconnect() end
        -- подписки нет
        showConnection = nil
        -- режим камеры, который был
        player.CameraMode = normalMode or Enum.CameraMode.Classic
        -- камера обычная
        if player.CameraMode == Enum.CameraMode.Classic then
            -- отодвигаем камеру назад — иначе останется в голове
            player.CameraMinZoomDistance = 10
            -- один кадр
            task.wait()
            -- возвращаем приближение, как было
            player.CameraMinZoomDistance = normalMinZoom or 0.5
        -- конец проверки
        end
    -- конец проверки
    end
-- конец функции
end

-- взяли оружие в руки
local function OnEquipped()
    -- запоминаем
    equipped = true
    -- в руках — значит, родитель оружия персонаж
    local humanoid = gun.Parent:FindFirstChild("Humanoid")
    -- анимация задана
    if reloadAnimation.AnimationId ~= "" then
        -- готовим анимацию перезарядки
        animTrack = humanoid:LoadAnimation(reloadAnimation)
    -- конец проверки
    end
    -- показываем счётчик на экране
    gunGui.Parent = player.PlayerGui
    -- с актуальным числом
    UpdateAmmo()
    -- курсор-перекрестье
    mouse.Icon = "rbxassetid://6884107931"
-- конец функции
end
-- каждый раз, когда берут в руки
gun.Equipped:Connect(OnEquipped)
-- подобрали с земли: скрипт запустился, когда оружие уже в руках, — Equipped был раньше
if gun.Parent and gun.Parent:FindFirstChildOfClass("Humanoid") then OnEquipped() end

-- убрали оружие
gun.Unequipped:Connect(function()
    -- запоминаем
    equipped = false
    -- прячем счётчик обратно в оружие
    gunGui.Parent = gun
    -- обычный курсор
    mouse.Icon = ""
    -- выходим из прицела
    camera.FieldOfView = 70
    -- и из вида от первого лица
    FirstPerson(false)
-- конец обработчика
end)

-- клик, пока оружие в руках
gun.Activated:Connect(function()
    -- во время перезарядки не стреляем
    if reloading then return end
    -- патроны есть
    if ammo.Value > 0 then
        -- выстрел
        shotEvent:FireServer(mouse.Hit.Position)
        -- минус патрон
        ammo.Value -= 1
    -- магазин пуст
    else
        -- щелчок
        emptySound:Play()
    -- конец проверки
    end
-- конец обработчика
end)

-- нажата клавиша
UIS.InputBegan:Connect(function(input, gp)
    -- печатает в чат / оружие не в руках / уже перезаряжаемся
    if gp or not equipped or reloading then return end
    -- R, и магазин не полный
    if input.KeyCode == Enum.KeyCode.R and ammo.Value < maxAmmo.Value then
        -- началась перезарядка
        reloading = true
        -- звук
        reloadSound:Play()
        -- анимация, если задана
        if animTrack then animTrack:Play() end
        -- ждём длину звука (минимум 1 с — даже если звук не загрузился)
        task.wait(math.max(reloadSound.TimeLength, 1))
        -- полный магазин
        ammo.Value = maxAmmo.Value
        -- перезарядка закончилась
        reloading = false
    -- конец проверки
    end
-- конец обработчика
end)

-- нажата ПКМ
mouse.Button2Down:Connect(function()
    -- приближаем камеру — прицел
    if equipped then camera.FieldOfView = 30 end
    -- смотрим от первого лица
    if equipped then FirstPerson(true) end
-- конец обработчика
end)
-- ПКМ отпущена
mouse.Button2Up:Connect(function()
    -- обычный обзор
    camera.FieldOfView = 70
    -- камера снова за спиной
    FirstPerson(false)
-- конец обработчика
end)

Проверка

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