Roblox · Tower Defense · урок 4

Меню башен и характеристики

Кнопки башен строятся сами по папке Towers. У каждой башни свои радиус, урон, скорость атаки и цвет луча.

Структура

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

ReplicatedStorage
└─ Towers (Folder)
   └─ Tower (Model)
      └─ Configuration (Configuration)
         ├─ Range (NumberValue) — радиус атаки
         ├─ Damage (NumberValue) — урон за выстрел
         ├─ Cooldown (NumberValue) — секунд между выстрелами
         ├─ Image (Decal) — Texture = картинка башни
         └─ Trail (Color3Value) — цвет луча
StarterGui
└─ TowerGui (ScreenGui)
   ├─ TowerFrame (Frame) — панель кнопок
   │  ├─ Title (TextLabel) — «Башни»
   │  ├─ UICorner (UICorner) — скругление углов
   │  ├─ UIListLayout (UIListLayout) — кнопки встают в ряд сами
   │  └─ Sample (ImageButton) — шаблон кнопки, Visible выкл.
   └─ TowerPlacementPreview (LocalScript)

Шаги

  1. Размеры интерфейса задавай через Scale (1-е и 3-е число), не Offset — тогда на телефоне и ПК он одинаковый.
  2. Старую кнопку Tower удали.
  3. Configuration — в каждую башню. Cooldown — NumberValue (IntValue не хранит 0.5).
  4. Картинка: скриншот башни → загрузи (Asset Manager) → id в Image.Texture.
  5. У врагов должен быть PrimaryPart — от него рисуется луч.

Скрипты

TowerPlacementPreview — блок кнопок

Где: StarterGui → TowerGui · LocalScript
Зачем: Заменяет три строки gui.Tower.Activated: по кнопке на каждую башню.

-- по каждой башне из ReplicatedStorage.Towers
for _, tower in pairs(towers:GetChildren()) do
    -- копия шаблона кнопки
    local button = gui.TowerFrame.Sample:Clone()
    -- характеристики башни
    local configuration = tower:WaitForChild("Configuration")
    -- кнопка называется как башня
    button.Name = tower.Name
    -- картинка башни на кнопке
    button.Image = configuration.Image.Texture
    -- шаблон скрыт, копию показываем
    button.Visible = true
    -- в список кнопок
    button.Parent = gui.TowerFrame
    -- нажали на кнопку
    button.Activated:Connect(function()
        -- превью этой башни
        AddTowerPreview(tower.Name)
    -- конец обработчика
    end)
-- следующая башня
end

Tower

Где: ServerScriptService → MainScript · ModuleScript
Зачем: Радиус, урон и перезарядка берутся из Configuration; при выстреле — неоновый луч.

-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- удаление объектов по таймеру
local Debris = game:GetService("Debris")
-- сигнал «поставь башню» от клиента
local towerEvent = RS.Events:WaitForChild("TowerEvent")

-- таблица модуля
local tower = {}

-- ищет ближайшего живого врага в радиусе range
local function FindNearestTarget(towerModel, range)
    -- пока никого
    local nearestTarget = nil
    -- все враги на карте
    for _, target in ipairs(workspace.Mobs:GetChildren()) do
        -- мёртвых не считаем
        if target.Humanoid.Health > 0 then
            -- расстояние до башни
            local distance = (target.HumanoidRootPart.Position - towerModel.PrimaryPart.Position).Magnitude
            -- ближе лучшего найденного
            if distance < range then
                -- запоминаем врага
                nearestTarget = target
                -- теперь ищем ещё ближе
                range = distance
            -- конец проверки расстояния
            end
        -- конец проверки здоровья
        end
    -- конец перебора
    end
    -- ближайший враг или nil
    return nearestTarget
-- конец функции
end

-- луч от башни к врагу
local function Trail(towerModel, target)
    -- новый парт-луч
    local trail = Instance.new("Part")
    -- длина луча
    local distance = (towerModel.PrimaryPart.Position - target.PrimaryPart.Position).Magnitude
    -- тонкий и длинный
    trail.Size = Vector3.new(0.1, 0.1, distance)
    -- сдвиг на полдлины, чтобы луч начинался у башни
    local offset = CFrame.new(0, 0, -distance / 2)
    -- направляем на врага
    trail.CFrame = CFrame.new(towerModel.PrimaryPart.Position, target.PrimaryPart.Position) * offset
    -- не падает
    trail.Anchored = true
    -- ни с чем не сталкивается
    trail.CanCollide = false
    -- полупрозрачный
    trail.Transparency = 0.5
    -- цвет луча этой башни
    trail.Color = towerModel.Configuration.Trail.Value
    -- светится
    trail.Material = Enum.Material.Neon
    -- показываем
    trail.Parent = workspace
    -- через 0,1 с удаляется сам
    Debris:AddItem(trail, 0.1)
-- конец функции
end

-- башня стреляет, пока стоит на карте
function tower.Attack(towerModel)
    -- характеристики башни
    local configuration = towerModel:WaitForChild("Configuration")
    -- башня ещё на карте
    while towerModel.Parent do
        -- цель в радиусе Range
        local target = FindNearestTarget(towerModel, configuration.Range.Value)
        -- цель есть
        if target then
            -- рисуем луч
            Trail(towerModel, target)
            -- урон Damage
            target.Humanoid:TakeDamage(configuration.Damage.Value)
            -- перезарядка Cooldown
            task.wait(configuration.Cooldown.Value)
        -- целей нет
        else
            -- проверим снова чуть позже
            task.wait(0.1)
        -- конец проверки
        end
    -- следующий выстрел
    end
-- конец функции
end

-- ставит башню по сигналу от клиента
function tower.Spawn(player, name, cframe)
    -- образец башни
    local towerModel = RS.Towers:FindFirstChild(name)
    -- такая башня есть
    if towerModel then
        -- копия
        local towerClone = towerModel:Clone()
        -- ставим всю модель туда, где выбрал игрок
        towerClone:PivotTo(cframe)
        -- на карту
        towerClone.Parent = workspace.Towers
        -- все детали башни
        for _, obj in pairs(towerClone:GetDescendants()) do
            -- только физические
            if obj:IsA("BasePart") then
                -- группа коллизий Tower
                obj.CollisionGroup = "Tower"
            -- конец проверки
            end
        -- конец перебора
        end
        -- атака параллельно, скрипт не ждёт
        coroutine.wrap(tower.Attack)(towerClone)
    -- конец проверки
    end
-- конец функции
end

-- клиент прислал сигнал → ставим башню
towerEvent.OnServerEvent:Connect(tower.Spawn)

-- отдаём таблицу модуля
return tower

Проверка

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