Проект «Королівська битва». Частина 3.

Таймери та події

Під час раунду сценарії повинні будуть відстежувати час і надсилати сигнали між різними сценаріями. Час керуватиметься за допомогою сценарію часу, тоді як події, концепція кодування Roblox, сигналізуватимуть про зміни, наприклад про закінчення матчу.

Створення прив'язуваних подій

Почніть із створення зв’язуваних об’єктів подій для початку та кінця матчу. Оскільки зв’язувані події не взаємодіють із клієнтом, їх можна зберігати в серверному сховищі.

У ServerStorage створіть нову папку під назвою Events. У цій папці створіть два BindableEvents з іменами MatchStart і MatchEnd.


Використання таймера

Однією з умов, яка призведе до закінчення матчу, є закінчення таймера, яке буде оброблено через скрипт.

Налаштування таймера

Щоб додати таймер у гру, скористайтеся готовим сценарієм модуля, виконавши наведені нижче дії. Він містить функції запуску та завершення таймера, а також повернення часу, що залишився.

У ServerStorage > ModuleScripts створіть новий сценарій модуля під назвою Timer.


Замініть код на код нижче.

local Timer = {}
Timer.__index = Timer

function Timer.new()
  local self = setmetatable({}, Timer)

  self._finishedEvent = Instance.new("BindableEvent")
  self.finished = self._finishedEvent.Event

  self._running = false
  self._startTime = nil
  self._duration = nil

  return self
end

function Timer:start(duration)
  if not self._running then
    task.spawn(function()
      self._running = true
      self._duration = duration
      self._startTime = tick()
      while self._running and tick() - self._startTime < duration do
        task.wait()
      end
      local completed = self._running
      self._running = false
      self._startTime = nil
      self._duration = nil
      self._finishedEvent:Fire(completed)
    end)
  else
    warn("Warning: timer could not start again as it is already running.")
  end
end

function Timer:getTimeLeft()
  if self._running then
    local now = tick()
    local timeLeft = self._startTime + self._duration - now
    if timeLeft < 0 then
      timeLeft = 0
    end
    return timeLeft
  else
    warn("Warning: could not get remaining time, timer is not running.")
  end
end

function Timer:isRunning()
  return self._running
end

function Timer:stop()
  self._running = false
end

return Timer

Створення GUI

Зараз велика частина інформації про гру знаходиться у вікні виводу, невидимому для гравців. Щоб гравці могли бути поінформовані про те, що відбувається в грі, ви створите графічний інтерфейс користувача (GUI) і закодуєте його.

Відображення інформації за допомогою графічного інтерфейсу користувача

Для цієї гри текстова мітка відображатиме поточний статус гри, а також кількість гравців і час, що залишився.
Під час антракту
Під час матчу

Налаштування GUI

По-перше, створіть об’єкт графічного інтерфейсу користувача Screen для зберігання різних текстових елементів. Коли гравець переміщує камеру, графічний інтерфейс користувача на екрані залишається на тому самому місці.

Щоб переконатися, що всі гравці бачать однаковий дисплей, помістіть GUI у папку StarterGUI . Під час запуску гри ця папка копіюється для всіх гравців.

У папці StarterGUI створіть новий ScreenGUI. Потім у ScreenGUI додайте нову TextLabel під назвою StatusText.

Налаштування сценарію

Сценарій StatusDisplay використовуватиметься для оновлення графічного інтерфейсу гравця щоразу, коли змінюється стан гри.

У ReplicatedStorage створіть папку під назвою DisplayValues. У цю папку додайте StringValue під назвою Status. Щоб перевірити значення пізніше, дайте йому тимчасове значення, наприклад «Ласкаво просимо до битви!».


Оскільки локальні сценарії запускаються лише на пристрої гравця, їх не можна зберігати в папках сервера, як-от ServerStorage. ReplicatedStorage — це папка, яка доступна як клієнту (пристрою), так і серверу.

У StarterGUI > ScreenGUI > Status додайте новий локальний сценарій під назвою StatusDisplay. Сценарії, які впливають на графічний інтерфейс користувача, часто пов’язані з цим елементом графічного інтерфейсу користувача.

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local displayValues = ReplicatedStorage:WaitForChild("DisplayValues")
local status = displayValues:WaitForChild("Status")

local textLabel = script.Parent

local function updateText()
textLabel.Text = status.Value
end

status.Changed:Connect(updateText)
updateText()

Створення менеджера дисплеїв

Під час гри текстова мітка потребуватиме отримання інформації від GameManager, MatchManager та, можливо, інших сценаріїв. Щоб ці різні сценарії могли оновлювати текстову мітку за потреби, створіть сценарій модуля під назвою DisplayManager.

Налаштування сценарію DisplayManager

Оскільки DisplayManager має спілкуватися з іншими сценаріями, це буде сценарій модуля.

У ServerStorage > ModuleScripts створіть новий сценарій модуля під назвою DisplayManager. Перейменуйте таблицю модулів відповідно до назви сценарію.

Додайте наступний код: 

local DisplayManager = {}

-- Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Display Values used to update Player GUI
local displayValues = ReplicatedStorage:WaitForChild("DisplayValues")
local status = displayValues:WaitForChild("Status")
local playersLeft = displayValues:WaitForChild("PlayersLeft")
local timeLeft = displayValues:WaitForChild("TimeLeft")

-- Local Functions
local function updateRoundStatus()
status.Value = "Players Left: " .. playersLeft.Value .. " / Time Left: " .. timeLeft.Value
end

-- Module Functions
function DisplayManager.updateStatus(newStatus)
status.Value = newStatus
end

playersLeft.Changed:Connect(updateRoundStatus)
timeLeft.Changed:Connect(updateRoundStatus)

return DisplayManager

Відображення статусу відповідності

Під час матчу графічний інтерфейс відображає два числа: кількість гравців, що залишилися, і час. Із зміною цих чисел зміниться також і текстова мітка.

Налаштування значень і функцій

IntValues ​​буде використовуватися для зберігання кількості гравців і часу, що залишився.

У ReplicatedStorage > DisplayValues ​​створіть два IntValue з іменами PlayersLeft і TimeLeft.

Завершені сценарії

Нижче наведено завершені сценарії.

Скрипт GameSettings

local GameSettings = {}

-- Game Variables
GameSettings.intermissionDuration = 5
GameSettings.matchDuration = 10
GameSettings.minimumPlayers = 2
GameSettings.transitionTime = 5

-- Possible ways that the game can end.
GameSettings.endStates = {
TimerUp = "TimerUp",
FoundWinner = "FoundWinner"
}

return GameSettings

Скрипт GameManager

-- Services
local ServerStorage = game:GetService("ServerStorage")
local Players = game:GetService("Players")

-- Module Scripts
local moduleScripts = ServerStorage:WaitForChild("ModuleScripts")
local matchManager = require(moduleScripts:WaitForChild("MatchManager"))
local gameSettings = require(moduleScripts:WaitForChild("GameSettings"))
local displayManager = require(moduleScripts:WaitForChild("DisplayManager"))

-- Events
local events = ServerStorage:WaitForChild("Events")
local matchEnd = events:WaitForChild("MatchEnd")

while true do
displayManager.updateStatus("Waiting for Players")

repeat
task.wait(gameSettings.intermissionDuration)
until #Players:GetPlayers() >= gameSettings.minimumPlayers

displayManager.updateStatus("Get ready!")
task.wait(gameSettings.transitionTime)

matchManager.prepareGame()
local endState = matchEnd.Event:Wait()

local endStatus = matchManager.getEndStatus(endState)
displayManager.updateStatus(endStatus)

matchManager.cleanupMatch()
task.wait(gameSettings.transitionTime)

matchManager.resetMatch()
end

Скрипт MatchManager

local MatchManager = {}

-- Services
local ServerStorage = game:GetService("ServerStorage")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Module Scripts
local moduleScripts = ServerStorage:WaitForChild("ModuleScripts")
local playerManager = require(moduleScripts:WaitForChild("PlayerManager"))
local gameSettings = require(moduleScripts:WaitForChild("GameSettings"))
local displayManager = require(moduleScripts:WaitForChild("DisplayManager"))
local timer = require(moduleScripts:WaitForChild("Timer"))
-- Events
local events = ServerStorage:WaitForChild("Events")
local matchStart = events:WaitForChild("MatchStart")
local matchEnd = events:WaitForChild("MatchEnd")
-- Values
local displayValues = ReplicatedStorage:WaitForChild("DisplayValues")
local timeLeft = displayValues:WaitForChild("TimeLeft")
-- Creates a new timer object to be used to keep track of match time.
local myTimer = timer.new()
-- Local Functions
local function stopTimer()
myTimer:stop()
end
local function timeUp()
matchEnd:Fire(gameSettings.endStates.TimerUp)
end
local function startTimer()
myTimer:start(gameSettings.matchDuration)
myTimer.finished:Connect(timeUp)
while myTimer:isRunning() do
-- Adding +1 makes sure the timer display ends at 1 instead of 0.
timeLeft.Value = (math.floor(myTimer:getTimeLeft() + 1))
-- By not setting the time for wait, it offers more accurate looping
task.wait()
end
end
-- Module Functions
function MatchManager.prepareGame()
playerManager.sendPlayersToMatch()
matchStart:Fire()
end
function MatchManager.getEndStatus(endState)
local messageToReturn
if endState == gameSettings.endStates.FoundWinner then
local winnerName = playerManager.getWinnerName()
messageToReturn = "Winner is : " .. winnerName
elseif endState == gameSettings.endStates.TimerUp then
messageToReturn = "Time ran out!"
else
messageToReturn = "Error found"
end
return messageToReturn
end
function MatchManager.cleanupMatch()
playerManager.removeAllWeapons()
end
function MatchManager.resetMatch()
playerManager.resetPlayers()
end
matchStart.Event:Connect(startTimer)
matchEnd.Event:Connect(stopTimer)
return MatchManager

Скрипт PlayerManager

local PlayerManager = {}
-- Services
local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Modules
local moduleScripts = ServerStorage:WaitForChild("ModuleScripts")
local gameSettings = require(moduleScripts:WaitForChild("GameSettings"))
-- Events
local events = ServerStorage:WaitForChild("Events")
local matchEnd = events:WaitForChild("MatchEnd")
-- Map Variables
local lobbySpawn = workspace.Lobby.StartSpawn
local arenaMap = workspace.Arena
local spawnLocations = arenaMap.SpawnLocations
-- Values
local displayValues = ReplicatedStorage:WaitForChild("DisplayValues")
local playersLeft = displayValues:WaitForChild("PlayersLeft")
-- Player Variables
local activePlayers = {}
local playerWeapon = ServerStorage.Weapon
local function checkPlayerCount()
if #activePlayers == 1 then
matchEnd:Fire(gameSettings.endStates.FoundWinner)
print("Found winner")
end
end
local function removeActivePlayer(player)
print("removing player")
for playerKey, whichPlayer in activePlayers do
if whichPlayer == player then
table.remove(activePlayers, playerKey)
playersLeft.Value = #activePlayers
checkPlayerCount()
end
end
end
local function respawnPlayerInLobby(player)
player.RespawnLocation = lobbySpawn
player:LoadCharacter()
end
local function preparePlayer(player, whichSpawn)
player.RespawnLocation = whichSpawn
player:LoadCharacter()
local character = player.Character or player.CharacterAdded:Wait()
-- Give the player a tool
local sword = playerWeapon:Clone()
sword.Parent = character
local humanoid = character:WaitForChild("Humanoid")
humanoid.Died:Connect(function()
respawnPlayerInLobby(player)
removeActivePlayer(player)
end)
end
local function onPlayerJoin(player)
player.RespawnLocation = lobbySpawn
end
local function removePlayerWeapon(whichPlayer)
-- Check to see if a player exist in case they disconnected or left.
if whichPlayer then
local character = whichPlayer.Character
-- If the player has it currently on their character
local weapon = character:FindFirstChild("Weapon")
if weapon then
weapon:Destroy()
end
-- If the player has the weapon in their backpack
local backpackWeapon = whichPlayer.Backpack:FindFirstChild("Weapon") 
if backpackWeapon then
backpackWeapon:Destroy()
end
else
print("No player to remove weapon")
end
end
function PlayerManager.sendPlayersToMatch()
local availableSpawnPoints = spawnLocations:GetChildren()
for playerKey, whichPlayer in Players:GetPlayers() do
table.insert(activePlayers,whichPlayer)
-- Gets a spawn location and then removes it from the table so the next player gets the next spawn
local spawnLocation = availableSpawnPoints[1]
table.remove(availableSpawnPoints, 1)
preparePlayer(whichPlayer, spawnLocation)
end
playersLeft.Value = #activePlayers
end
function PlayerManager.getWinnerName()
local winningPlayer = activePlayers[1]
if winningPlayer then
return winningPlayer.Name
else
return "Error: No player found"
end
end
function PlayerManager.removeAllWeapons()
for playerKey, whichPlayer in activePlayers do
removePlayerWeapon(whichPlayer)
end
end
function PlayerManager.resetPlayers()
for playerKey, whichPlayer in activePlayers do
respawnPlayerInLobby(whichPlayer)
end
activePlayers = {}
end
-- Events
Players.PlayerAdded:Connect(onPlayerJoin)
return PlayerManager