Roblox · Захват флага · урок 8
Итог курса на одной странице: полная структура захвата флага и финальные версии всех скриптов. Удобно сверить свою игру или собрать её заново.
Синим — что добавляем на этом уроке.
Teams — Синие, Красные (AutoAssignable выкл.) Workspace ├─ Lobby (Model) — комната на высоте 100, Neutral-спавн ├─ LobbySpawn, GameSpawn (Folder) — SpawnLocation «Синие» и «Красные» ├─ Field (Model) — земля, ограда, укрытия └─ Bases (Folder) └─ Синие, Красные (Model) — Stand, GivePart, ArmorGiver (ArmorScript), Flag → Hitbox, Pole, Cloth ReplicatedStorage ├─ TeamEvent, WinEvent (RemoteEvent) ├─ Lobby (BoolValue) └─ TimeLeft (IntValue) ServerScriptService └─ TeamsServer, FlagServer, TimerServer (Script) StarterGui — все ResetOnSpawn выкл. ├─ TeamGui (ScreenGui) — Frame → Синие, Красные; TeamsLocal ├─ TimerGui (ScreenGui) — TimerLabel, TimerLocal └─ WinnerGui (ScreenGui) — WinLabel, WinLocal
Где: ServerScriptService · Script
Зачем: Выбор команды.
-- команды
local Teams = game:GetService("Teams")
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- событие выбора
local teamEvent = RS:WaitForChild("TeamEvent")
-- true — идёт лобби, false — игра
local lobby = RS:WaitForChild("Lobby")
-- игрок нажал кнопку
teamEvent.OnServerEvent:Connect(function(player, teamName)
-- от клиента пришло не имя — игнорируем
if typeof(teamName) ~= "string" then return end
-- команда с таким именем
local team = Teams:FindFirstChild(teamName)
-- нет такой или игрок уже в ней
if not team or not team:IsA("Team") or player.Team == team then return end
-- записываем в команду
player.Team = team
-- лобби или поле — смотря что идёт
local folder = lobby.Value and workspace.LobbySpawn or workspace.GameSpawn
-- точка появления команды
player.RespawnLocation = folder:FindFirstChild(team.Name)
-- возрождаем там
player:LoadCharacter()
-- конец функции
end)Где: ServerScriptService · Script
Зачем: Флаги и очки.
-- игроки
local Players = game:GetService("Players")
-- команды
local Teams = game:GetService("Teams")
-- базы; имя базы = имя команды
local bases = workspace:WaitForChild("Bases")
-- имя базы → функция «вернуть её флаг»
local returns = {}
-- игрок зашёл
Players.PlayerAdded:Connect(function(player)
-- таблица по Tab
local leaderstats = Instance.new("Folder")
-- имя строго такое
leaderstats.Name = "leaderstats"
-- в игрока
leaderstats.Parent = player
-- принесённые флаги
local flags = Instance.new("IntValue")
-- столбец
flags.Name = "Flags"
-- в таблицу
flags.Parent = leaderstats
-- конец функции
end)
-- живой игрок, чья деталь коснулась
local function GetPlayer(hit)
-- модель персонажа
local character = hit.Parent
-- Humanoid
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
-- не персонаж или погиб
if not humanoid or humanoid.Health <= 0 then return nil end
-- игрок
return Players:GetPlayerFromCharacter(character)
-- конец функции
end
-- настраиваем одну базу
local function Setup(base)
-- команда этой базы
local team = Teams:WaitForChild(base.Name)
-- модель флага
local flag = base:WaitForChild("Flag")
-- главная деталь флага
local hitbox = flag:WaitForChild("Hitbox")
-- куда приносить чужой флаг
local givePart = base:WaitForChild("GivePart")
-- место флага на подставке
local home = hitbox.CFrame
-- кто несёт флаг
local carrier = nil
-- подписки на смерть носителя
local connections = {}
-- флаг — обратно на подставку
local function ReturnFlag()
-- отписываемся
for _, connection in ipairs(connections) do connection:Disconnect() end
-- список пуст
table.clear(connections)
-- у игрока больше нет флага
if carrier then carrier:SetAttribute("HasFlag", nil) end
-- никто не несёт
carrier = nil
-- сварка со спиной
local weld = hitbox:FindFirstChild("CarryWeld")
-- убираем
if weld then weld:Destroy() end
-- закрепляем
hitbox.Anchored = true
-- на подставку
hitbox.CFrame = home
-- конец функции
end
-- чтобы другая база могла вернуть этот флаг
returns[base.Name] = ReturnFlag
-- кто-то коснулся флага
hitbox.Touched:Connect(function(hit)
-- флаг уже несут
if carrier then return end
-- игрок
local player = GetPlayer(hit)
-- свой флаг не берут
if not player or not player.Team or player.Team == team then return end
-- два флага сразу нельзя
if player:GetAttribute("HasFlag") then return end
-- Humanoid носителя
local humanoid = player.Character:FindFirstChildOfClass("Humanoid")
-- к нему крепим
local root = player.Character:FindFirstChild("HumanoidRootPart")
-- персонаж не собран
if not root then return end
-- запоминаем носителя
carrier = player
-- чей флаг у игрока
player:SetAttribute("HasFlag", base.Name)
-- сварка со смещением
local weld = Instance.new("Weld")
-- имя — чтобы найти и убрать
weld.Name = "CarryWeld"
-- спина игрока
weld.Part0 = root
-- флаг
weld.Part1 = hitbox
-- чуть выше и за спиной
weld.C0 = CFrame.new(0, 1.5, 1.2)
-- в флаг
weld.Parent = hitbox
-- флаг едет за игроком
hitbox.Anchored = false
-- погиб — флаг домой
table.insert(connections, humanoid.Died:Connect(ReturnFlag))
-- вышел или возродился — тоже
table.insert(connections, player.CharacterRemoving:Connect(ReturnFlag))
-- конец функции
end)
-- кто-то пришёл на свою базу
givePart.Touched:Connect(function(hit)
-- игрок
local player = GetPlayer(hit)
-- только игроки этой команды
if not player or player.Team ~= team then return end
-- чей флаг принёс
local enemyName = player:GetAttribute("HasFlag")
-- ничего не несёт
if not enemyName or not returns[enemyName] then return end
-- чужой флаг возвращается на свою подставку
returns[enemyName]()
-- таблица игрока
local stats = player:FindFirstChild("leaderstats")
-- +1 флаг
if stats then stats.Flags.Value += 1 end
-- конец функции
end)
-- конец функции
end
-- все базы
for _, base in ipairs(bases:GetChildren()) do
-- настраиваем
task.spawn(Setup, base)
-- конец перебора
endГде: ServerScriptService · Script
Зачем: Раунды и победитель.
-- игроки
local Players = game:GetService("Players")
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- идёт лобби?
local lobby = RS:WaitForChild("Lobby")
-- сколько секунд осталось
local timeLeft = RS:WaitForChild("TimeLeft")
-- команды
local Teams = game:GetService("Teams")
-- объявить победителя
local winEvent = RS:WaitForChild("WinEvent")
-- секунд в лобби
local LOBBY_TIME = 30
-- секунд игры (5 минут)
local GAME_TIME = 300
-- всех игроков с командой — на точки своей команды
local function SendAll(folder)
-- каждый игрок
for _, player in ipairs(Players:GetPlayers()) do
-- команда выбрана
if player.Team then
-- точка команды в этой папке
player.RespawnLocation = folder:FindFirstChild(player.Team.Name)
-- возрождаем там (флаг сам вернётся)
player:LoadCharacter()
-- конец проверки
end
-- конец перебора
end
-- конец функции
end
-- обратный отсчёт
local function Countdown(seconds)
-- от seconds до 0
for t = seconds, 0, -1 do
-- игроки видят время
timeLeft.Value = t
-- секунда
task.wait(1)
-- конец цикла
end
-- конец функции
end
-- какая команда принесла больше флагов
local function Winner()
-- лучшая, её счёт, ничья ли
local bestTeam, bestScore, draw = nil, -1, false
-- каждая команда
for _, team in ipairs(Teams:GetTeams()) do
-- сумма флагов
local score = 0
-- игроки команды
for _, player in ipairs(team:GetPlayers()) do
-- таблица
local stats = player:FindFirstChild("leaderstats")
-- прибавляем
if stats then score += stats.Flags.Value end
-- конец перебора
end
-- больше всех
if score > bestScore then
-- новый лидер
bestTeam, bestScore, draw = team, score, false
-- столько же
elseif score == bestScore then
-- ничья
draw = true
-- конец проверки
end
-- конец перебора
end
-- белым
if not bestTeam or draw then return "Ничья!", Color3.new(1, 1, 1) end
-- цветом команды
return "Победили " .. bestTeam.Name .. "! Флагов: " .. bestScore, bestTeam.TeamColor.Color
-- конец функции
end
-- раунды по кругу
while true do
-- лобби
lobby.Value = true
-- все в лобби
SendAll(workspace.LobbySpawn)
-- ждём
Countdown(LOBBY_TIME)
-- игра
lobby.Value = false
-- обнуляем счёт
for _, player in ipairs(Players:GetPlayers()) do
-- таблица
local stats = player:FindFirstChild("leaderstats")
-- с нуля
if stats then stats.Flags.Value = 0 end
-- конец перебора
end
-- все на поле
SendAll(workspace.GameSpawn)
-- играем
Countdown(GAME_TIME)
-- показываем победителя всем
winEvent:FireAllClients(Winner())
-- пока читают
task.wait(5)
-- конец цикла
endГде: StarterGui → TeamGui · LocalScript
Зачем: Кнопки команд.
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- событие выбора команды
local teamEvent = RS:WaitForChild("TeamEvent")
-- TeamGui
local gui = script.Parent
-- окно с кнопками
local frame = gui:WaitForChild("Frame")
-- все объекты окна
for _, button in ipairs(frame:GetChildren()) do
-- только кнопки
if button:IsA("TextButton") then
-- нажатие
button.MouseButton1Click:Connect(function()
-- имя кнопки = имя команды
teamEvent:FireServer(button.Name)
-- прячем окно
gui.Enabled = false
-- конец функции
end)
-- конец проверки
end
-- конец перебора
endГде: StarterGui → TimerGui · LocalScript
Зачем: Время на экране.
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- идёт лобби?
local lobby = RS:WaitForChild("Lobby")
-- секунды
local timeLeft = RS:WaitForChild("TimeLeft")
-- надпись
local label = script.Parent:WaitForChild("TimerLabel")
-- обновить надпись
local function Update()
-- секунды
local t = timeLeft.Value
-- что идёт
local prefix = lobby.Value and "До начала: " or "До конца: "
-- минуты и секунды с нулём: 01:05
label.Text = prefix .. string.format("%02d:%02d", t // 60, t % 60)
-- конец функции
end
-- каждую секунду
timeLeft.Changed:Connect(Update)
-- лобби ↔ игра
lobby.Changed:Connect(Update)
-- сразу при входе
Update()Где: StarterGui → WinnerGui · LocalScript
Зачем: Победитель.
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- событие победы
local winEvent = RS:WaitForChild("WinEvent")
-- WinnerGui
local gui = script.Parent
-- надпись
local label = gui:WaitForChild("WinLabel")
-- сколько секунд показывать
local SHOW_TIME = 5
-- сервер объявил победителя
winEvent.OnClientEvent:Connect(function(text, color)
-- текст
label.Text = text
-- цвет команды
label.TextColor3 = color
-- показываем
gui.Enabled = true
-- ждём
task.wait(SHOW_TIME)
-- прячем
gui.Enabled = false
-- конец функции
end)Где: Bases → команда → ArmorGiver · Script
Зачем: Броня.
-- деталь, выдающая броню
local giver = script.Parent
-- сколько здоровья добавляет
local BONUS = 100
-- касание
giver.Touched:Connect(function(hit)
-- персонаж
local character = hit.Parent
-- Humanoid
local humanoid = character:FindFirstChildOfClass("Humanoid")
-- не игрок, погиб или броня уже есть
if not humanoid or humanoid.Health <= 0 or character:GetAttribute("Armor") then return end
-- туловище R15 или R6
local torso = character:FindFirstChild("UpperTorso") or character:FindFirstChild("Torso")
-- нет туловища
if not torso then return end
-- у этого персонажа броня есть (после смерти — снова нет)
character:SetAttribute("Armor", true)
-- больше максимум
humanoid.MaxHealth += BONUS
-- полное здоровье
humanoid.Health = humanoid.MaxHealth
-- нагрудник
local plate = Instance.new("Part")
-- имя
plate.Name = "ArmorPlate"
-- чуть больше туловища
plate.Size = torso.Size + Vector3.new(0.3, 0.1, 0.3)
-- цвет как у выдачи
plate.Color = giver.Color
-- металл
plate.Material = Enum.Material.Metal
-- не мешает ходить
plate.CanCollide = false
-- лучи сквозь
plate.CanQuery = false
-- без веса
plate.Massless = true
-- на туловище
plate.CFrame = torso.CFrame
-- сварка
local weld = Instance.new("WeldConstraint")
-- туловище
weld.Part0 = torso
-- нагрудник
weld.Part1 = plate
-- в нагрудник
weld.Parent = plate
-- в персонажа
plate.Parent = character
-- конец функции
end)Запусти Play (или Run) и убедись: