В гоночных играх игроки обычно занимают разные стартовые позиции в зависимости от их результатов в предыдущем раунде. Это побуждает игроков ехать быстрее, даже если они не на первом месте, поэтому они стартуют впереди других игроков.
Для этого игре должно быть известно, какой раунд игроки проходят на данный момент, и данные о порядке финиширования игроками должны сохраняться во всех раундах, но не во всех игровых сеансах. Переменная слабого ассоциативного массива сеанса в Verse сбрасывает данные после каждого раунда, поэтому эта информация о раунде должна храниться у каждого игрока в переменной слабого ассоциативного массива игрока и сбрасываться после окончания игры.
В настоящее время в проекте может быть не более двух переменных слабого ассоциативного массива игрока. Если в вашем проекте уже есть одна такая переменная, рекомендуем использовать вторую переменную для записи информации о раунде, чтобы отличать данные, которые всегда должны сохраняться, от данных, которые нужно сбрасывать после окончания игры или выхода игрока из сеанса.
Также важно знать, какой раунд игроки проходят в данный момент, чтобы применить логику конкретного раунда и сбросить информацию о последнем раунде. В настоящее время для получения информации о текущем раунде API не настроен, поэтому эта информация также должна быть записана в сохраняемые данные для каждого игрока.
Подводя итог: вам понадобится переменная слабого ассоциативного массива игрока, содержащая, по крайней мере, следующую информацию:
позицию на финише;
последний завершённый раунд.
Далее описывается, как настроить эти данные и логику раундов. В конце раздела приведён полный код.
Запись последнего завершённого раунда
Выполните следующие действия, чтобы настроить сохраняемые данные для каждого игрока и записать последний завершённый раунд.
Создайте сохраняемый класс
player_circuit_infoдля хранения информации об игроке между раундами. В этом классе должны быть поля, представляющие позицию игрока на финише (LastRoundFilishOrder) и последний завершённый им раунд (LastCompletedRound). Эти поля инициализируются со значением–1для отображения недопустимых значений, чтобы было видно, содержат ли эти поля полезную информацию.Verse# Tracks the number of and in what order a player finished the previous round. player_circuit_info<public> := class<final><persistable>: Version:int = 0 LastRoundFinishOrder:int = -1 LastCompletedRound<public>:int = -1Создайте переменную слабого ассоциативного массива игрока, используя класс
player_circuit_info, чтобы сохранить информацию о раунде для игроков.Verse# A persistable map that maps each player to # what order they finished the previous round. var CircuitInfo<public>:weak_map(player, player_circuit_info) = map{}При работе с сохраняемыми данными рекомендуем создавать конструктор для сохраняемого класса, чтобы можно было легко обновлять информацию для каждого игрока. Подробнее см. в разделе Использование конструкторов для частичных обновлений.
Verse# Creates a new player_circuit_info from the given older player_circuit_info. MakePlayerCircuitInfo<constructor>(OldPlayerCircuitInfo:player_circuit_info)<transacts> := player_circuit_info: Version := OldPlayerCircuitInfo.Version LastRoundFinishOrder := OldPlayerCircuitInfo.LastRoundFinishOrder LastCompletedRound := OldPlayerCircuitInfo.LastCompletedRoundВы определили структуру для этих данных, и теперь можно добавить функцию для записи позиции игрока на финише и обновления его сохраняемых данных. Эта функция использует конструктор, созданный на предыдущем этапе, чтобы частично обновлять данные для получения единственной необходимой информации — порядка финиширования.
Verse# Creates a new player_circuit_info for the given player with the order they finished the round in. RecordPlayerFinishOrder<public>(Agent:agent, FinishOrder:int)<decides><transacts>:void= Player := player[Agent] Player.IsActive[] PlayerCircuitInfo:player_circuit_info = if: Info := CircuitInfo[Player] then: Info else: player_circuit_info{}Создайте ещё одну функцию для обновления данных только за последний завершённый игроком раунд.
Verse# Updates a player's player_circuit_info with their last completed round. UpdateRound<public>(Agent:agent, CompletedRound:int)<decides><transacts>:void= Player := player[Agent] Player.IsActive[] PlayerCircuitInfo := CircuitInfo[Player] set CircuitInfo[Player] = player_circuit_info: MakePlayerCircuitInfo<constructor>(PlayerCircuitInfo) LastCompletedRound := CompletedRoundТеперь вы можете записать данные за последний завершённый игроком раунд. Далее создадим функцию для вычисления данных за последний завершённый раунд для игры, проверив, у кого из игроков содержатся актуальные записанные данные за раунд. Вам нужно проверить всех игроков, чтобы учесть тех игроков, которые, возможно, только присоединились к текущему сеансу. Переменная последнего завершено раунда инициализация со значением
–1,что означает «недопустимые данные». Если у каких-либо игроков значение больше–1,раунд уже завершился.Verse# Returns the highest last completed round among all players. GetLastCompletedRound<public>(Players:[]player, TotalRounds:int)<transacts>:int= var LastCompletedRound:int = -1 for: Player : Players Player.IsActive[] PlayerCircuitInfo := CircuitInfo[Player] do: # Update LastCompletedRound if this player has the highest last completed round. else if:Создайте устройство Verse, чтобы проверить, что завершённый раунд и позиция игрока на финише сохраняются должным образом. Убедитесь, что в вашей игре несколько раундов, настроив свойство «Всего раундов» в Настройках острова.
Verse# A Verse-authored creative device that can be placed in a level test_round_info_device := class(creative_device): # Runs when the device is started in a running game OnBegin<override>()<suspends>:void= Players := GetPlayspace().GetPlayers() CurrentRound := GetLastCompletedRound(Players) + 1 Print("Current round is {CurrentRound}") for:
Сброс информации о раунде при выходе игрока
Если игрок покидает игру, его сохраняемые данные, содержащие информацию о раунде, должны быть сброшены. Чтобы отметить момент выхода игрока из игры, можно подписаться на PlayerRemovedEvent игрового пространства.
Выполните следующие действия, чтобы сбросить информацию о раунде, когда игрок покинет игру:
Создайте функцию для сброса сохраняемых данных игрока для получения информации о раунде. То есть вам нужно задать значения
–1или любые другие значения, которые вы решите использовать для обозначения недопустимых данных в этих полях. Эта реализация определяет, какие данные следует сбросить, в случае, если вы позже добавите в сохраняемый класс информацию, которую не следует сбрасывать.Verse# Resets a player's player_circuit_info. ResetCircuitInfo<public>(Agent:agent)<decides><transacts>:void= Player := player[Agent] Player.IsActive[] PlayerCircuitInfo := CircuitInfo[Player] set CircuitInfo[Player] = player_circuit_info: MakePlayerCircuitInfo<constructor>(PlayerCircuitInfo) LastRoundFinishOrder := -1 LastCompletedRound := -1Создайте функцию
OnPlayerRemoved, чтобы сбрасывать информацию о раунде, когда игрок покидает игру.Verse# When a player is removed from the race, reset their circuit to prevent their # stats from showing up on billboards and player references. OnPlayerRemoved(Player:player):void= # Reset circuit info when player leaves the game. if: ResetCircuitInfo[Player] else: Print("Unable to reset circuit info for player")Настройте функцию
OnPlayerRemoved, подписавшись на событиеGetPlayspace().PlayerRemovedEvent(), которая будет вызываться, когда игрок покидает игру.Verse# A Verse-authored creative device that can be placed in a level test_round_info_device := class(creative_device): # Runs when the device is started in a running game OnBegin<override>()<suspends>:void= GetPlayspace().PlayerRemovedEvent().Subscribe(OnPlayerRemoved) Players := GetPlayspace().GetPlayers() CurrentRound := GetLastCompletedRound(Players) + 1Убедитесь, что данные сбрасываются, когда игрок покидает игру.
Сброс информации о раунде в конце игры
Функция OnBegin для устройства Verse запускается в начале каждого раунда. В этот момент можно определить, были ли игроку добавлены непредвиденные сохраняемые данные, например, совпадает ли последний завершённый им раунд с общим количеством раундов. Если такие данные есть, нужно сбросить данные игрока. В настоящее время для определения общего количества раундов в игре API не настроен. Вместо этого нужно будет добавить редактируемое свойство в устройство Verse для определения общего количества раундов в коде Verse и проверить, что оно соответствует свойству «Всего раундов» в Настройках острова.
Выполните следующие действия, чтобы сбросить информацию о раунде после завершения игры:
Добавьте редактируемое свойство в устройство Verse, которое содержит общее количество раундов в игре. Значение TotalRounds не должно быть меньше или равно
0, поэтому ограничьте это свойство значениями, большими или равными1.Verse# A Verse-authored creative device that can be placed in a level test_round_info_device := class(creative_device): # The total number of rounds in the race. @editable TotalRounds:type {Rounds:int where 1 <= Rounds} = 3 # Runs when the device is started in a running game OnBegin<override>()<suspends>:void= GetPlayspace().PlayerRemovedEvent().Subscribe(OnPlayerRemoved)Обновите функцию
GetLastCompletedRound, чтобы сбросить сохраняемые данные игрока за последний завершённый раунд, если количество раундов в этих данных превышает ожидаемое количество раундов в игре.Verse# Returns the highest last completed round among all players. GetLastCompletedRound<public>(Players:[]player, TotalRounds:int)<transacts>:int= var LastCompletedRound:int = -1 for: Player : Players Player.IsActive[] PlayerCircuitInfo := CircuitInfo[Player] do: # If player's recorded info is greater than the total rounds for whatever reason, # then need to reset the player's circuit info because they shouldn't have more than what's allowed.Обновите вызов для
GetLastCompletedRoundдля передачи общего количества раундов в качестве аргумента, чтобы сбросить данные игрока о раунде, если их состояние не является удовлетворительным.Verse# A Verse-authored creative device that can be placed in a level test_round_info_device := class(creative_device): # The total number of rounds in the race. @editable TotalRounds:type {Rounds:int where 1 <= Rounds} = 3 # Runs when the device is started in a running game OnBegin<override>()<suspends>:void= GetPlayspace().PlayerRemovedEvent().Subscribe(OnPlayerRemoved)Выполните тест, чтобы убедиться, что данные раунда для игрока сбрасываются даже после игры и завершения всех раундов.
Добавление логики на основе данных текущего раунда
Теперь вы можете использовать эту информацию для написания пользовательской логики с учётом раунда, который проходят игроки. Например, можно отобразить локальную таблицу лидеров для первого раунда игры.
Вызывать GetLastCompletedRound() каждый раз, когда нужно узнать, какой сейчас раунд, — не лучший вариант. Лучше сделать это один раз за раунд и записать данные раунда в переменную слабого ассоциативного массива сеанса, чтобы весь код Verse смог обращаться к этому значению в любой момент и не нужно было повторно его вычислять.
Вот отличный пример, показывающий различия и обоснование использования переменной слабого ассоциативного массива сеанса и переменной слабого ассоциативного массива игрока в вашем коде:
Слабые ассоциативные массивы сеанса пригодятся для паттернов типа «одиночка» и хранения данных для текущего раунда, если вы не хотите совершать повторные вычисления.
Слабые ассоциативные массивы игрока предназначены для показателей, которые должны сохраняться между множеством раундов и сеансов, но при этом должны быть связаны с отдельными игроками.
Выполните следующие действия, чтобы задать переменную слабого ассоциативного массива сеанса для хранения данных текущего раунда.
Создайте класс для хранения данных раунда. Вам как минимум понадобится поле
CurrentRound, но можно также включить прочую информацию о раунде, которую вы хотите сохранить во всём коде Verse, например стартовые позиции и транспортные средства игроков. ИнициализируйтеCurrentRoundсо значением–1, чтобы обозначить недопустимые данные.Verseround_info := class: CurrentRound:int = -1Создайте переменную слабого ассоциативного массива сеанса с помощью класса
round_infoдля хранения информации о раунде в сеансе.Verse# Maps the current session to its associated round info. var RoundInfo:weak_map(session, round_info) = map{}Добавьте функцию получения для получения данных текущего раунда из переменной слабого ассоциативного массива сеанса.
VerseGetRound<public>()<decides><transacts>:int= RoundInfo[GetSession()].CurrentRoundДобавьте функцию для получения данных текущего раунда и их сохранения в переменной слабого ассоциативного массива сеанса.
VerseRecordCurrentRound<public>(Players:[]player, TotalRounds:int):void= var CurrentRoundInfo:round_info = if: Info := RoundInfo[GetSession()] then: Info else: LastCompletedRound := GetLastCompletedRound(Players, TotalRounds) round_info: CurrentRound := LastCompletedRound + 1Обновите устройство Verse, чтобы можно было использовать новую функцию
RecordCurrentRoundи вызыватьGetRound, когда нужно будет узнать, какой раунд сейчас проходят игроки.Verse# A Verse-authored creative device that can be placed in a level test_round_info_device := class(creative_device): # The total number of rounds in the race. @editable TotalRounds:type {Rounds:int where 1 <= Rounds} = 3 # Runs when the device is started in a running game OnBegin<override>()<suspends>:void= GetPlayspace().PlayerRemovedEvent().Subscribe(OnPlayerRemoved)
Теперь эти данные хранятся в переменной слабого ассоциативного массива сеанса и можно легко добавить пользовательскую логику для раундов. Например, вы можете проверить, первый ли это раунд, и настроить лобби и зону просмотра таблицы лидеров для игроков.
# Returns true if this is the first round of the game.
IsFirstRound<public>(RoundToCheck:int)<decides><transacts>:void=
RoundToCheck <= 0Самостоятельная работа
Ознакомьтесь с разделом Гоночная трасса с сохранением данных с помощью Verse, чтобы узнать, как использовать этот код в гоночной игре для определения стартовых позиций игроков.
После ознакомления с шаблоном попробуйте сделать следующее:
Добавьте дополнительную информацию о раунде, например, укажите, какое транспортное средство закреплено за игроком.
Телепортируйте игроков в разные области карты в начале каждого раунда.
Как вы думаете, в каких ещё играх используется логика, характерная для игры с раундами?
Полный код
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# A persistable map that maps each player to
# what order they finished the previous round.
var CircuitInfo<public>:weak_map(player, player_circuit_info) = map{}
# Maps the current session to its associated round info.
var RoundInfo:weak_map(session, round_info) = map{}