В этом разделе вы узнаете, как создать и настроить для игроков команды и классы.
Используемые устройства:
Параметры команды и инвентарь

Воспользуйтесь устройствами «Параметры команды и инвентарь» для назначения командам названий и цветов, которые появятся в таблице со счётом.
Разместите по одному устройству для каждой команды в месте, которое не видно игрокам. Для создания прячущейся команды внесите изменения в Пользовательские настройки как показано в таблице ниже.

Параметр | Значение | Пояснение |
---|---|---|
Название команды | Предметы | Устанавливает строку текста, который будет соответствовать команде в таблице со счётом и интерфейсе. |
Цвет команды | Небесно-голубой | Позволяет выбрать цвет для команды, который будет использоваться в таблице со счётом, в интерфейсе и на некоторых устройствах. |
Команда | Индекс команды: 1 | Определяет, на какую команду будут действовать параметры этого устройства. |
Для создания команды охотников внесите изменения в Пользовательские настройки другого устройства как показано в таблице ниже.
!Параметры команды и инвентарь охотников](hunter-team-setting-and-inventory.png)(w:800)
Параметр | Значение | Пояснение |
---|---|---|
Название команды | Охотники | Устанавливает строку текста, который будет относиться к команде в таблице со счётом и интерфейсе. |
Цвет команды | Оранжевый | Позволяет выбрать цвет для команды, который будет использоваться в таблице со счётом, в интерфейсе и на некоторых устройствах. |
Команда | Индекс команды: 2 | Определяет, на какую команду будут действовать параметры этого устройства. |
Устройство «Создание класса»

Используйте устройство «Создание класса», чтобы изменить только что созданные команды.
Разместите два устройства «Создание класса», по одному для каждой команды, в месте, которое не видно игроками. Для настройки прячущейся команды внесите изменения в Пользовательские настройки как показано в таблице ниже.
Параметр | Значение | Пояснение |
---|---|---|
Название класса | Предмет | Определяет название этого класса. |
Описание класса | Прятаться от охотников. Оставаться в живых. | Задаёт описание этого класса. |
Идентификатор класса | Ячейка класса: 1 | Задает уникальный идентификатор для этого класса. |
Максимальное здоровье | 1 | Определяет максимальную величину здоровья, которое игроки могут получить во время игры. Предметы будут устранены после одного попадания. |
Список предметов | Пушка-копирка | Задаёт список предметов, которые будет содержать этот класс. |
Использовать полученный предмет | Первый предмет | Определяет предмет из списка, который будет использован. |
Для настройки команды охотников внесите изменения в Пользовательские настройки другого устройства как показано в таблице ниже.

Параметр | Значение | Пояснение |
---|---|---|
Название класса | Охотник | Определяет название этого класса. |
Описание класса | Искать объекты окружения. Устранять их. | Задаёт описание этого класса. |
Идентификатор класса | Ячейка класса: 2 | Задает уникальный идентификатор для этого класса. |
Список предметов | Пистолет с фонариком | Задаёт список предметов, которые будет содержать этот класс. |
Использовать полученный предмет | Первый предмет | Определяет предмет из списка, который будет использован. |
Выбор класса

Свяжите устройство «Создание класса» с устройством «Выбор класса», чтобы иметь возможность управлять индивидуально настраиваемыми классами и создаваемыми командами.
Вместе с кодом Verse настройки этого устройства позволят переносить игроков при возрождении из слота класса 1 в слот класса 2.
Разместите два устройства «Выбор класса», по одному для каждой команды, в месте, которое не видно игроками. Для управления прячущейся командой используйте настройки из таблицы ниже для внесения изменений в Пользовательские настройки этого устройства.

Параметр | Значение | Пояснение |
---|---|---|
Новый класс при переходе | Ячейка класса: 1 | Определяет класс, в который должен перейти игрок. |
Отображение во время игры | False | Это устройство будет невидимым на протяжении всей игры. |
Звук зоны | False | Определяет, будет ли устройство выбора класса воспроизводить звуковые эффекты, когда игроки входят в зону. |
Новая команда при переходе | Индекс команды: 1 | Определяет, в какую команду перейдёт игрок. |
Удалить предметы при смене класса | True | Определяет, пропадут ли предметы из инвентаря игрока при смене класса. |
Видимость области в игре | False | Отображение области устройства во время игры. |
Воспроизводить визуальный эффект при активации | False | Определяет, будет ли устройство воспроизводить визуальный эффект при смене игроком класса или команды. |
Для управления командой охотников используйте настройки из таблицы ниже для внесений изменений в Пользовательские настройки данного устройства.

Параметр | Значение | Пояснение |
---|---|---|
Новый класс при переходе | Ячейка класса: 2 | Определяет класс, в который должен перейти игрок. |
Новая команда при переходе | Индекс команды: 2 | Определяет, в какую команду перейдёт игрок. |
Создание функций команды на языке Verse
В игре в прятки с предметами предусмотрены две команды: охотники и предметы окружения. Чтобы игра заработала, необходимо проделать одни и те же действия для обеих команд. Пример:
-
добавление игроков в команду;
-
удаление игроков из команды;
-
отображение информации игрокам об их командах.
Чтобы создать такой функционал для обеих команд, не дублируя код, создадим класс со спецификатором <abstract>
. Классы со спецификатором abstract
частично реализуют функциональные возможности своих подклассов, в которых эти возможности расширяются. Сначала создадим абстрактный класс под названием base_team
и зададим ему функции, одинаковые как для команды прячущихся игроков, так и для команды охотников.
В данном документе описаны фрагменты кода на Verse, показывающие, как запустить механику, необходимую для этого режима. Выполните следующие действия и скопируйте весь код из этой инструкции на шаге 6.
Создайте новый файл Verse в проекте под названием base_team.verse. Это не будет устройство Verse, так что его можно создать пустым.
using { /Fortnite.com/Characters }
using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Colors }
using { /Verse.org/Simulation }
log_team := class(log_channel){}
# Этот класс определяет устройства, необходимые для разных команд в этом режиме.
# Этот класс является абстрактным, поэтому сам по себе не используется. Он должен наследоваться другим классом.
base_team := class<abstract>:
Logger:log = log{Channel:=log_team}
@editable # Используется для настройки игрока для команды.
ClassSelector:class_and_team_selector_device = class_and_team_selector_device{}
@editable # Используется для присвоения очков агентам команды.
ScoreManager:score_manager_device = score_manager_device{}
@editable # Используется для отображения заголовка задания команды.
TeamTitle:hud_message_device = hud_message_device{}
@editable # Используется для отображения описания задания команды.
TeamDescription:hud_message_device = hud_message_device{}
@editable # Используется для подписки на события устранения члена (прячущейся) команды или команды врага (охотников).
TeamManager:team_settings_and_inventory_device = team_settings_and_inventory_device{}
# Это массив агентов команды.
var TeamAgents<private>:[]agent = array{}
# Сигнал о таком событии подаётся в момент, когда массив TeamAgents становится пустым (сигнал об окончании раунда).
TeamEmptyEvent:event() = event(){}
# Возвращает текущий массив TeamAgents.
# Нужен, потому что массив TeamAgents является закрытым, из-за чего другие классы не могут обратиться к нему напрямую.
GetAgents()<decides><transacts>:[]agent =
TeamAgents
# Вернуть размер массива TeamAgents
# Для этого требуется функция, поскольку массив TeamAgents является закрытым, из-за чего другие классы не могут обратиться к нему напрямую.
Count()<transacts>:int =
TeamAgents.Length
# Возвращает индекс агента в массиве TeamAgents; в противном случае операция завершается ошибкой.
FindOnTeam(Agent:agent)<decides><transacts>: int =
Index := TeamAgents.Find[Agent]
# Назначаем агента в команду и уведомляем игрока.
InitializeAgent(Agent:agent):void =
AddAgentToTeam(Agent)
ClassSelector.ChangeTeamAndClass(Agent)
DisplayTeamInformation(Agent)
# Добавляем агента в TeamAgents.
AddAgentToTeam(AgentToAdd:agent):void =
if (not FindOnTeam[AgentToAdd]):
Logger.Print("Добавление агента в команду.")
set TeamAgents += array{AgentToAdd}
# Активирует устройства отображения сообщений в HUD-интерфейсе, по которым игрок определяет, в какой он находится команде
DisplayTeamInformation(Agent:agent):void =
TeamTitle.Show(Agent)
TeamDescription.Show(Agent)
# Если агент покинет матч, удаляем его из массива TeamAgents и выполняем проверку на окончание раунда.
EliminateAgent(Agent:agent)<suspends>:void =
Sleep(0.0) # Выполняем задержку на 1 игровой такт для возможности возрождения игрока перед тем, как продолжить игру.
RemoveAgentFromTeam(Agent)
# Удаляем агента из TeamAgents.
# Если удалённый агент оказался последним, передаём сигнал о событии TeamEmptyEvent.
RemoveAgentFromTeam(AgentToRemove:agent):void =
set TeamAgents = TeamAgents.RemoveAllElements(AgentToRemove)
Logger.Print("Осталось агентов в команде: {Count()}.")
if (Count() < 1):
Logger.Print("Агентов в команде больше нет. Раунд завершён.")
TeamEmptyEvent.Signal()
После создания этого класса можно приступить к созданию классов для прячущейся команды и команды охотников. То, что каждый из них будет наследоваться от base_team
, имеет ряд преимуществ:
-
Код будет намного короче, поскольку у них уже имеются общие функции и данные, определённые в
base_team
. -
Благодаря созданию двух отдельных классов будет легче понять, какой код относится к прячущейся команде, а какой — к команде охотников.
-
Добавлять дополнительные команды в режим игры будет намного проще. Любая новая команда будет наследоваться от
base_team
, а код, по которому она получает отличие от остальных, будет находиться в отдельном классе.
Помните, что нельзя создать экземпляр класса со спецификатором <abstract>
. Необходимо создать класс, который будет наследоваться от абстрактного класса, а затем создать экземпляр такого класса.
Команда охотников
Сначала создайте класс для команды охотников. Создайте новый файл Verse в проекте под названием hunter_team.verse. Это не будет устройство Verse, так что его можно создать пустым.
Объявите класс под названием hunter_team
. Он должен иметь спецификатор <concrete>
и наследоваться от base_team
.
hunter_team := class<concrete>(base_team):
Класс со спецификатором <concrete>
характеризуется тем, что все его поля должны иметь значение по умолчанию. Дополнительную информацию см. в разделе Спецификаторы и атрибуты.
Ниже приведён полный код для сценария hunter_team.verse.
В классе hunter_team
есть две функции с теми же названиями, что и функции в классе base_team
. Это допустимо, поскольку обе они имеют спецификатор <override>
. Когда эти функции вызываются в экземпляре hunter_team
, то используется версия в классе hunter_team
.
Например, в следующем коде InitializeAgent()
будет использоваться именно из hunter_team
, поскольку она переопределяет функцию под тем же названием в base_team
. Сравните эту ситуацию с вызовом Count()
, где из-за отсутствия функции переопределения используется версия из base_team
.
HunterTeam:hunter_team = hunter_team{}
# Использует функцию из hunter_team
HunterTeam.InitializeAgent(StartingHunterAgent)
# Использует функцию из base_team
HunterTeam.Count()
Обе переопределённые функции также используют (super:)
. Это позволяет им вызывать версию функций из base_team
, поскольку base_team
является суперклассом hunter_team
. В случае с InitializeAgent()
и EliminateAgent()
обе они используют Logger.Print()
для вывода информации в журнал. Впоследствии они вызывают свои соответствующие функции из base_team
. Другими словами, эти функции действуют точно так же, как версии в base_team
, лишь с добавлением Logger.Print()
.
Подробнее об <override>
и (super:)
читайте в разделе Подкласс.
Прячущаяся команда
А теперь давайте создадим класс для прячущейся команды. Создайте новый файл Verse в проекте под названием prop_team.verse. Это не будет устройство Verse, так что его можно создать пустым.
Для членов прячущейся команды будет необходимо выполнить кое-какие дополнительные действия. Им будет необходимо добавить эффекты сердцебиения, которые будут активироваться и останавливаться с учётом таймера и дальности передвижения. Кроме того, после устранения их будет необходимо переносить в команду охотников.
Для управления членами прячущейся команды используется метод RunPropGameLoop()
. Этот метод представляет собой диспетчер всех действий прячущегося игрока на протяжении игры. С момента появления до момента устранения или ухода из игры этот метод будет непрерывно использоваться для каждого члена прячущейся команды.
# Если агент прячущейся команды перестанет двигаться, запускаем конкурентное выполнение, чтобы проконтролировать перемещение агента за пределы расстояния MinimumMoveDistance, завершение отсчёта таймера эффекта сердцебиения или устранение агента.
RunPropGameLoop(PropAgent:agent)<suspends>:void =
Logger.Print("Запуск игрового цикла агента прячущейся команды.")
# Выполняем цикл бесконечно с учётом поведения спрятавшегося игрока до тех пор, пока агент прячущейся команды не будет устранён или игрок не покинет сеанс.
race:
PropAgent.AwaitNoLongerAProp()
loop:
# Дожидаемся, пока агент прячущейся команды не переместится на расстояние меньше минимального, и переходим к дальнейшим действиям.
PropAgent.AwaitStopMoving(MinimumMoveDistance)
# Пока агент прячущейся команды не переместится на расстояние больше минимального, отсчитываем время до сердцебиения, а затем непрерывно воспроизводим эффект сердцебиения.
race:
PropAgent.AwaitStartMoving(MinimumMoveDistance)
block:
CountdownTimer(PropAgent)
PropAgent.StartHeartbeat()
Sleep(0.0) # Как только конкурентное выполнение завершится (агент прячущейся команды начнёт двигаться), вновь запустить цикл.
RunPropGameLoop()
имеет один parameter — PropAgent
. Это константа, представляющая игрока в прячущейся команде. Она также имеет спецификатор <suspends>
, то есть завершение занимает некоторое время. В этом случае она не завершится до тех пор, пока содержащийся в ней PropAgent
не будет удалён из команды Prop
.
Все функциональные возможности этого метода содержатся в выражении race. То есть, метод не будет завершён до тех пор, пока не завершится одно из выполняемых конкурентно выражений. Вот те самые выражения:
-
PropAgent.AwaitNoLongerAProp()
-
loop
Выражение loop при таком конкурентном выполнении никогда не завершается. Цикл здесь намеренно сделан бесконечным. Это значит, что AwaitNoLongerAProp()
является методом, который всегда будет завершаться первым при конкурентном выполнении и завершать метод. Конкурентное выполнение здесь используется для того, что заставить программу непрерывно исполнять определённый код до определённого события. Подробнее об этом полезном выражении читайте в разделе Конкурентное выполнение.
В этом коде AwaitNoLongerAProp()
завершается первым при конкурентном выполнении.
# Повторяем цикл до тех пор, пока агент прячущейся команды не будет удалён из массива PropAgents. Удаление происходит тогда, когда агент прячущейся команды устраняется и превращается в охотника, либо когда игрок покидает сеанс.
(PropAgent:agent).AwaitNoLongerAProp()<suspends>:void =
loop:
if (not FindOnTeam[PropAgent]):
Logger.Print("Отмена поведения агента прячущейся команды.")
break
Sleep(0.0) # Переходим к следующему игровому такту.
Этот метод постоянно проверяет наличие PropAgent
в прячущейся команде. Он запускает цикл, который выполняется до успешного результата not FindOnTeam[PropAgent]
, после чего прерывает его, в результате чего метод завершается. Подробности об этом читайте в разделе Цикл и его прерывание.
FindOnTeam[]
— это функция с неоднозначным результатом, объявляемая в base_team
. Она завершается успешно при обнаружении PropAgent
в прячущейся команде. Однако необходимо использовать оператор not
, поскольку требуется прервать цикл только в случае, когда PropAgent
не будет обнаружен в прячущейся команде. Об операторе not
читайте в разделе Операторы.
Наконец, нужно добавить метод Sleep(0.0)
в конце цикла. Благодаря этому цикл будет запущен один раз, а затем перейдёт к следующему обновлению симуляции. Нет необходимости запускать эту проверку чаще, поэтому Sleep(0.0)
добавляется, чтобы помочь с производительностью. Подробности читайте на странице справочника API Verse, на которой описана функция Sleep.
Итак, вы знаете, как действует функция AwaitNoLongerAProp()
, и теперь вы можете создать цикл, который будет выполняться с ней конкурентно, в RunPropGameLoop()
.