В многопользовательских играх команды игроков либо соревнуются, либо сотрудничают для достижения какой-либо цели. Количество игроков в каждой команде может радикально повлиять на игровой процесс, и многие разработчики для создания увлекательной игры выбирают определённое соотношение игроков.
Путём распределения (балансировки) можно разделить игроков на команды с учётом заданного соотношения. В большинстве многопользовательских игр команды формируются таким образом, чтобы ни у одной из них не было преимущества. В некоторых играх намеренно создаются сценарии с неравномерным распределением, например, в них могут поставить четырёх игроков против одного очень сильного игрока. Независимо от вида игры распределение по командам имеет решающее значение для создания увлекательного игрового процесса, в котором участвует несколько команд.
По завершении этого урока вы научитесь динамически распределять игроков по командам в среде выполнения и всякий раз, когда к игре присоединяется новый игрок. В конце этого урока для справки приведён полный сценарий.
Используемые возможности языка Verse
массив: это устройство использует массивы для хранения ссылок на все команды.
option: это устройство использует переменные типа option, чтобы определить, есть ли команда с меньшим числом игроков, чем та, участником которой является игрок.
for: с помощью выражения for вы можете перебирать массивы, которые использует устройство.
if: выражение if используется для проверки необходимости перевода игрока в другую команду исходя из текущего числа игроков в командах.
неоднозначность: контексты, допускающие неоднозначность, используются для доступа к массивам, а также для управления ходом выполнения программы.
Используемые API Verse
С возможностью подписаться: вам нужно будет выполнить подписку на событие
PlayerAddedEvent(), чтобы динамически перераспределять новых игроков по командам во время игры.Команды: класс team позволяет добавлять, удалять и выбирать игроков в командах. В данном уроке мы используем класс команды для непосредственного управления игроками и их назначения в команды.
Игровое пространство: игровое пространство отслеживает события с возможностью подписки, связанные с игроками, которые присоединяются к игре и покидают её. Оно также отвечает за получение списков игроков и команд и помогает подобрать команду для конкретного игрока. В данном уроке вы подпишетесь на несколько событий игрового пространства и будете получать игроков и команды с помощью методов игрового пространства, чтобы работать с ними напрямую.
Подготовка уровня
В данном примере используется следующее устройство.
4 устройства «Точка появления игрока»: это устройство определяет, где должен появляться игрок в начале игры.
Выполните следующие действия, чтобы подготовить уровень:
Добавьте Точку появления игрока на уровень.
Выберите точку появления на панели Структура, чтобы открыть её панель Сведения.
На панели Сведения, в разделе Пользовательские настройки:
Для параметра Команда игрока в поле Индекс команды задайте значение 1
Включите параметр Отображать в игре
Нажмите на изображение, чтобы увеличить его.
Скопируйте точку появления и разместите копию рядом с оригиналом.
Скопируйте обе точки появления и разместите копии на удалении от оригиналов. Здесь будут появляться игроки команды 2.
Выберите скопированные точки появления и на панели Сведения в Пользовательских настройках измените значение Индекса команды на 2 для этих двух точек появления.
На панели Структура выберите устройство Настройки острова, чтобы открыть панель Сведения. В разделе Пользовательские настройки — Правила игры:
Для параметра Команды в поле Индекс команды задайте значение 2. В этом примере используются две команды, но у вас может быть больше команд.
Для параметра Размер команды задайте значение Динамическое распределение. Это означает, что код Verse сможет распределять игроков по командам.
В поле Присоединение во время игры выберите Создать, чтобы новые игроки могли присоединиться прямо во время игры.
Нажмите на изображение, чтобы увеличить его.
Создайте новое устройство Verse под названием team_multiplayer_balancing с помощью Проводника Verse и перетащите его на уровень. (Узнать, как создать новое устройство в Verse, вы можете в разделе «Создание собственного устройства с помощью Verse».)
Уровень должен выглядеть приблизительно так:
Равномерное распределение по командам
Выравнивание состава команд в начале игры
В этом разделе показано, как равномерно распределять игроков по командам в начале игры и при присоединении нового игрока.
Откройте проводник Verse и дважды нажмите на файл team_multiplayer_balancing.verse, чтобы открыть сценарий в Visual Studio Code.
Добавьте в определение класса
team_multiplayer_balancingпеременнуюteam, которая будет выступать массивом и где будут храниться ссылки на все команды игроков, и назовите еёTeams.Verseteam_multiplayer_balance := class(creative_device): # Holds the teams found with GetTeams() var Teams : []team = array{}В функции
OnBegin()обновите массивTeams, чтобы данные в массиве соответствовали командам, заданным ранее в Параметрах острова. Вызовите функциюGetTeams()из APIfort_team_collection, чтобы получить все команды в игровом пространстве.VerseOnBegin<override>()<suspends>:void= Print("Verse Device Started!") set Teams = Self.GetPlayspace().GetTeamCollection().GetTeams()Найдите всех игроков в игре, вызвав функцию
GetPlayers(), и сохраните их в массиве игроков с названиемAllPlayers.VerseOnBegin<override>()<suspends>:void= Print("Verse Device Started!") set Teams = Self.GetPlayspace().GetTeamCollection().GetTeams() AllPlayers := GetPlayspace().GetPlayers()Переберите всех игроков в списке и создайте команды с равным количеством игроков. Для этого нужно сравнить команду, в которой в данный момент находится игрок, со всеми прочими командами и определить, оптимальна ли команда для данного игрока. В данном случае вы можете использовать команду, автоматически заданную для игрока при запуске игры, получив её с помощью
GetTeam[](поскольку в многопользовательских режимах игроки должны быть в одной из команд). Обратите внимание, что для использованияGetTeam[]нужен параметр типа agent, но поскольку игрок — это подкласс agent, вы можете передавать игрока и без приведения типов.VerseAllPlayers := GetPlayspace().GetPlayers() for (TeamPlayer : AllPlayers, CurrentTeam := GetPlayspace().GetTeamCollection().GetTeam[TeamPlayer]): # Assign Players to a new team if teams are unbalancedПоскольку `team` — это внутренний класс, его нельзя инициализировать, а можно использовать только как ссылку на существующий объект команды.
Следует распределять игроков в команду с наименьшим количеством игроков до тех пор, пока все команды не будут сбалансированы. Для этого необходимо проверить каждого игрока, а затем каждую команду с помощью цикла
for. Можно использовать два цикла `for`: один для перебора игроков, а другой для перебора команд. В данном примере цикл for для команд выделен в отдельный метод. Настройте цикл for для игроков, получив ссылку на каждого игрока, а затем ссылку на каждую команду, в которой он находится, в константеCurrentTeam.Создайте новую целочисленную переменную
TeamSizeи инициализируйте её значением0, а затем запишите в неё размер команды, в которой на данный момент находится игрок. ПосколькуGetAgents[]— это выражение с неоднозначным результатом, необходимо включить следующие строки в оператор if.Versefor (TeamPlayer : AllPlayers, CurrentTeam := GetPlayspace().GetTeamCollection().GetTeam[TeamPlayer]): # Assign Players to a new team if teams are unbalanced var TeamSize : int = 0 if(set TeamSize = GetPlayspace().GetTeamCollection().GetAgents[CurrentTeam].Length): Print("Size of this player's starting team is {TeamSize}")Создайте метод
FindSmallestTeam(). Он возвращает команду типа option (?team) при передачеTeamSizeв качестве аргумента и выполняет поиск и возврат команды с наименьшим количеством игроков. Инициализируйте новую переменную команды типа option с названиемSmallestTeamвFindSmallestTeam(). Здесь необходима переменная типа option, потому что игрок может уже быть в самой маленькой команде в момент вызоваFindSmallestTeam().Поскольку для переменной типа option
SmallestTeamпо умолчанию задано значениеfalse, оно таковым и останется, если команда меньшего размера не будет найдена. ЕслиFindSmallestTeam()вернёт значениеfalse, вы будете точно знать, что указанный игрок уже и так в самой маленькой команде. Вам также нужно инициализировать переменную типа intCurrentTeamSizeзначениемTeamSize.CurrentTeamSizeдолжна быть именно переменной, чтобы можно было записывать в неё размер любой другой найденной команды с меньшим числом игроков.VerseFindSmallestTeam(CurrentTeamSize : int) : ?team= var SmallestTeam : ?team = false var TeamSize : int = CurrentTeamSizeПоскольку
TeamSizeотслеживает размерSmallestTeam, нужно сравнить значение этой переменной с размером каждой команды. Выполните перебор всех команд, получая их размер в локальной переменной типа intCandidateTeamSize. ЕслиCandidateTeamSizeменьше, чемTeamSize, присвойтеSmallestTeamэту команду, а вTeamSizeзапишите размер данной команды.Условие
TeamSize > CandidateTeamSizeявляется условием фильтрации, поскольку оно проверяется в круглых скобках цикла for. Использование условия фильтрации гарантирует, что код внутри цикла будет выполняться только в том случае, если данное условие истинно. Благодаря этому значениемSmallestTeamбудет команда с наименьшим числом игроков, если такая будет найдена. Если такая команда не будет найдена, дляSmallestTeamсохранится значение false.После проверки всех команд возвращаем значение
SmallestTeam.Versefor(Team : Teams, CandidateTeamSize := GetPlayspace().GetTeamCollection().GetAgents[Team].Length, TeamSize > CandidateTeamSize): set SmallestTeam = option{Team} set TeamSize = CandidateTeamSize Print("Found a team with less players: {CandidateTeamSize}") return SmallestTeamВ
OnBegin()создайте новую переменнуюSmallestTeamбез обязательного значения (option) типа team внутри циклаforи инициализируйте её значениемFindSmallestTeam(), передавTeamSizeв качестве аргумента.VerseSmallestTeam : ?team = FindSmallestTeam(TeamSize)Далее попробуйте получить значение переменной типа option
SmallestTeam. Если значение «false», значит, игрок и так находится в самой маленькой команде и его не нужно переводить в другую команду. В противном случае необходимо перевести игрока в новую команду. Поскольку многие методы не позволяют напрямую передавать переменную типа option в качестве аргумента, нужно извлечь значение в локальную переменнуюTeamToAssign. Вы можете попробовать назначить игрока в эту команду, используя методAddToTeam[player, team], при этом важно помнить, что назначение может привести к ошибке, если вы попытаетесь назначить игрока в команду, в которой он уже состоит. Это не будет иметь никаких негативных последствий, поскольку циклforпросто перейдёт к следующему игроку и оставит первого игрока в первоначальной команде.Verseif (TeamToAssign := SmallestTeam?, GetPlayspace().GetTeamCollection().AddToTeam[TeamPlayer, TeamToAssign]): Print("Attempting to assign player to a new team")
Блок
OnBegin()теперь должен выглядеть следующим образом.VerseOnBegin<override>()<suspends> : void = Print("Verse Device Started!") set Teams = Self.GetPlayspace().GetTeamCollection().GetTeams() Print("Beginning to Assign Players") Playspace := GetPlayspace() AllPlayers := Playspace.GetPlayers() for (TeamPlayer : AllPlayers, CurrentTeam := Playspace.GetTeamCollection().GetTeam[TeamPlayer]): var TeamSize : int = 0 if(set TeamSize = Playspace.GetTeamCollection().GetAgents[CurrentTeam].Length): Print("Size of this player's starting team is {TeamSize}")
Присоединение игрока по ходу игры
Нам необходимо автоматически распределять игроков по командам во время игры, поэтому нужно подписаться на событие, которое возникает при присоединении к игре нового игрока. Нет смысла повторять весь написанный код, поэтому просто реорганизуем его в общий метод.
Создайте метод
BalanceTeams()и переместите в него весь код после задания переменнойTeamsс помощьюGetTeams(). Он будет вызываться в методеOnBegin()для распределения по командам в начале игры.BalanceTeams()будет выглядеть примерно так.VerseBalanceTeams() : void = AllPlayers := GetPlayspace().GetPlayers() for (TeamPlayer : AllPlayers, CurrentTeam := GetPlayspace().GetTeamCollection().GetTeam[TeamPlayer]): var TeamSize : int = 0 if(set TeamSize = GetPlayspace().GetTeamCollection().GetAgents[CurrentTeam].Length): Print("Size of this player's starting team is {TeamSize}") SmallestTeam : ?team = FindSmallestTeam(TeamSize) if (TeamToAssign := SmallestTeam?, GetPlayspace().GetTeamCollection().AddToTeam[TeamPlayer, TeamToAssign]): Print("Attempting to assign player to a new team")Создайте ещё один метод
OnPlayerAdded(), который будет вызыватьBalanceTeams(). Несмотря на то что вы не будете использовать переменнуюplayer, определение метода требует её наличия, поскольку вы подписываетесь на событиеPlayerAddedEvent(), используя этот метод. Более подробно о событиях с возможностью подписки рассказано в разделе «Реализация взаимодействия с устройствами».VerseOnPlayerAdded(InPlayer : player) : void = Print("A new Player joined, assigning them to a team!") BalanceTeams()В
OnBegin()подпишитесь на игровое пространствоPlayerAddedEvent()при помощиOnPlayerAdded. После присоединения игрока к игреOnPlayerAddedбудет вызыватьBalanceTeams()для автоматического распределения по командам.VerseOnBegin<override>()<suspends> : void = GetPlayspace().PlayerAddedEvent().Subscribe(OnPlayerAdded) Print("Beginning to balance teams") BalanceTeams()Сохраните сценарий в Visual Studio Code и нажмите Собрать код Verse, чтобы скомпилировать сценарий.
Нажмите Запустить сеанс на панели инструментов UEFN, чтобы выполнить игровой тест уровня.
Во время игрового теста уровня вы должны увидеть размер каждой команды, а также все команды меньшего размера, найденные сценарием, в журнале выходных данных. Игроки должны быть распределены по командам равномерно, а новый игрок, присоединившийся к игре, не должен нарушать баланс.
Полный сценарий
Следующий код представляет собой полный сценарий для создания устройства, которое будет автоматически выравнивать составы команд.
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
team_multiplayer_balance := class(creative_device):
# Holds the teams found with GetTeams()
var Teams : []team = array{}
OnBegin<override>()<suspends> : void =
Print("Verse Device Started!")
Самостоятельная работа
По завершении данного урока вы сможете создать устройство Verse, которое будет автоматически выравнивать состав команд.
Опираясь на полученные знания, попробуйте намеренно создать команды с неравным количеством игроков для ассиметричных игровых режимов, к примеру «один против четырёх».