Roblox · Tower Defense · урок 6
У игрока кошелёк. Башня стоит денег, убитый башней враг приносит награду её владельцу.
Синим — что добавляем на этом уроке.
ReplicatedStorage ├─ Functions (Folder) │ └─ SpawnTowerFunction (RemoteFunction) — клиент спрашивает — сервер отвечает └─ Towers (Folder) └─ Tower (Model) └─ Configuration (Configuration) └─ Price (NumberValue) — цена башни ServerStorage └─ Mobs (Folder) └─ NormalRig (Model) └─ Configuration (Configuration) └─ Reward (NumberValue) — награда за убийство StarterGui └─ TowerGui (ScreenGui) ├─ Money (TextLabel) — деньги игрока └─ MoneyLocal (LocalScript) ServerScriptService └─ PlayerController (Script) — бывший PlayerCollisionGroup
Где: ServerScriptService · Script
Зачем: Кошелёк Money (300) + группа коллизий персонажа.
-- игрок зашёл в игру
game.Players.PlayerAdded:Connect(function(player)
-- кошелёк внутри игрока
local money = Instance.new("IntValue", player)
-- называем Money
money.Name = "Money"
-- стартовые деньги — хватит на первую башню
money.Value = 300
-- появился персонаж
player.CharacterAdded:Connect(function(character)
-- все его части
for _, obj in pairs(character:GetDescendants()) do
-- только физические
if obj:IsA("BasePart") then
-- группа коллизий Player
obj.CollisionGroup = "Player"
-- конец проверки
end
-- конец перебора
end
-- конец обработчика персонажа
end)
-- конец обработчика игрока
end)Где: ServerScriptService → MainScript · ModuleScript
Зачем: Проверка денег, списание цены, награда за убитого врага.
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- удаление объектов по таймеру
local Debris = game:GetService("Debris")
-- сигнал «поставь башню»
local towerEvent = RS.Events:WaitForChild("TowerEvent")
-- вопрос клиента «хватает денег?»
local spawnTowerFunction = RS.Functions:WaitForChild("SpawnTowerFunction")
-- таблица модуля
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, player)
-- характеристики башни
local configuration = towerModel:WaitForChild("Configuration")
-- башня ещё на карте
while towerModel.Parent do
-- живая цель в радиусе
local target = FindNearestTarget(towerModel, configuration.Range.Value)
-- цель есть
if target then
-- рисуем луч
Trail(towerModel, target)
-- наносим урон
target.Humanoid:TakeDamage(configuration.Damage.Value)
-- этот выстрел убил врага, игрок ещё в игре
if target.Humanoid.Health <= 0 and player.Parent then
-- награда за врага
player.Money.Value += target.Configuration.Reward.Value
-- конец проверки
end
-- перезарядка
task.wait(configuration.Cooldown.Value)
-- целей нет
else
-- проверим снова чуть позже
task.wait(0.1)
-- конец проверки
end
-- следующий выстрел
end
-- конец функции
end
-- можно ли игроку поставить башню name
function tower.CanSpawn(player, name)
-- образец башни
local towerModel = RS.Towers:FindFirstChild(name)
-- такая башня есть
if towerModel then
-- денег хватает
if towerModel.Configuration.Price.Value <= player.Money.Value then
-- можно
return true
-- денег мало
else
-- сообщение в Output
warn("No money")
-- конец проверки денег
end
-- башни нет
else
-- сообщение в Output
warn("No tower")
-- конец проверки
end
-- нельзя
return false
-- конец функции
end
-- ставит башню, если хватает денег
function tower.Spawn(player, name, cframe)
-- сервер сам ещё раз проверяет деньги
if tower.CanSpawn(player, name) then
-- копия башни
local towerClone = RS.Towers[name]: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
-- списываем цену
player.Money.Value -= towerClone.Configuration.Price.Value
-- атака параллельно
coroutine.wrap(tower.Attack)(towerClone, player)
-- конец проверки
end
-- конец функции
end
-- клиент просит поставить башню
towerEvent.OnServerEvent:Connect(tower.Spawn)
-- клиент спрашивает «хватает денег?» — отвечаем
spawnTowerFunction.OnServerInvoke = tower.CanSpawn
-- отдаём таблицу модуля
return towerГде: StarterGui → TowerGui · LocalScript
Зачем: Перед превью спрашивает сервер, хватает ли денег.
-- добавь к переменным в начале скрипта
local spawnTowerFunction = RS.Functions:WaitForChild("SpawnTowerFunction")
-- по каждой башне
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()
-- спрашиваем сервер: хватает денег?
if spawnTowerFunction:InvokeServer(tower.Name) then
-- хватает — показываем превью
AddTowerPreview(tower.Name)
-- конец проверки
end
-- конец обработчика
end)
-- следующая башня
endГде: StarterGui → TowerGui · LocalScript
Зачем: Показывает деньги игрока.
-- этот игрок
local player = game.Players.LocalPlayer
-- его кошелёк (создаёт сервер)
local money = player:WaitForChild("Money")
-- надпись с деньгами
local moneyText = script.Parent.Money
-- обновляет надпись
local function UpdateMoney()
-- «$ 300»
moneyText.Text = "$ " .. money.Value
-- конец функции
end
-- показываем сразу
UpdateMoney()
-- и при каждом изменении
money.Changed:Connect(UpdateMoney)Запусти Play (или Run) и убедись: