Во многих играх используются показатели игрока для отслеживания его игровых данных с течением времени. Такие метрики, как счёт, количество выигранных игр, общее время в игре и собранные предметы, дают игрокам ощущение прогресса и являются отличным способом побудить игроков возвращаться в ваш мир снова и снова.
Verse Persistence — это мощный инструмент, позволяющий добавлять сохраняемые данные в ваш сценарий Verse. Данные могут сохраняться как для каждого отдельного игрока, так и для всего острова после выхода из одного сеанса игры и до входа в другой. Сохраняемые данные позволяют отслеживать прогресс игрока от сеанса к сеансу и открывают возможность реализовать множество уникальных и интересных жанров, недоступных ранее в UEFN.
В этом уроке вы узнаете, как создать пользовательскую таблицу статистики игрока на Verse и настроить её так, чтобы она сохранялась на протяжении нескольких игр. После завершения этого урока загляните в раздел Создание внутриигровой таблицы лидеров на Verse, чтобы узнать, как использовать сохраняемые данные для создания внутриигровой таблицы лидеров!
Используемые возможности языка Verse
Класс: в этом примере мы создадим класс Verse, который будет управлять отдельным показателем, а также сохраняемый класс, отслеживающий группу показателей одного игрока.
Конструктор: специальная функция, которая создаёт экземпляр связанного с ней класса.
Weak_map: простой ассоциативный массив без возможности итерации по нему. Сохраняемые данные Verse должны храниться в weak_map.
Подготовка уровня
В этом примере используются следующие объекты окружения и устройства.
2 устройства Кнопка: когда игрок взаимодействует с устройством, к его счёту прибавляется одно очко. С помощью другого устройства кнопки вы будете имитировать окончание игры, добавляя выигрыш или проигрыш игрока в зависимости от его текущего счета.
1 устройство Рекламный щит: часто бывает нужно показать сохраняемые данные игроку. Иногда это делается в рамках тестирования, а иногда — чтобы сильнее вовлечь игрока или показать его прогресс. Хотя требования к тому, когда и какие данные показывать, варьируются в зависимости от игры, в данном примере на рекламном щите будут показаны данные о счёте, рекорде, победах и поражениях.
Отслеживание сохраняемой статистики игрока
Сначала важно определить, какие показатели нужно отслеживать для каждого игрока. К примеру, может потребоваться отслеживать количество очков игрока за все время игры, его текущий рейтинг или его лучшее время на круге. В этом примере мы будем отслеживать счёт, победы и поражения в таблице значений показателей для каждого игрока. Всё это будет в новом классе player_stats_table, который мы будем использовать в качестве основного сохраняемого класса.
Чтобы создать класс player_stats_table, выполните следующие шаги:
Создайте новый файл Verse с помощью проводника Verse с названием
player_stats_table.verse.Создайте в этом файле Verse новый класс
player_stats_table. Добавьте к классу модификаторы<persistable>и<final>. Модификатор<persistable>позволяет сделать данные в классе сохраняемыми и должен дополняться модификатором<final>, поскольку сохраняемые данные нельзя переопределить или создать из них подкласс.Verseusing { /Fortnite.com/Devices } using { /Verse.org/Simulation } using { /UnrealEngine.com/Temporary/Diagnostics } # Tracks different persistable stats for each player. player_stats_table := class<final><persistable>:В
player_stats_tableдобавьте трицелочисленныхзначения:Score,WinsиLosses. С их помощью мы отслеживаем счёт за всё время, а также количество побед и поражений для каждого отдельного игрока. Также добавьтецелочисленноезначениеVersionдля отслеживания текущей версииplayer_stats_table.Verse# Tracks different persistable stats for each player. player_stats_table := class<final><persistable>: # The version of the current stats table. Version<public>:int = 0 # The score of a player. Score<public>:int = 0 # The number of wins for a player. Wins<public>:int = 0Для создания экземпляра класса
класса player_stats_tableмы будем использовать функцию<constructor>. Этот конструктор необходим, поскольку система сохранения данных в Verse не допускает сохранение классов с полями переменных. Использование конструктора позволит вам обновлять значения сохраняемого класса за счёт создания копии существующего сохраняемого показателя в виде переменной, обновлять копию, а затем заменять исходный экземпляр класса изменёнными значениями. Добавьте в свой файл новую функцию-конструкторMakePlayerStatsTable(). Этот конструктор будет принимать оригинальный (предыдущий) экземпляр классаplayer_stats_table, после чего будет создавать новый на основе оригинальных значений.Verse# Creates a new player_stats_table with the same values as the previous player_stats_table. MakePlayerStatsTable<constructor>(OldTable:player_stats_table)<transacts> := player_stats_table: Version := OldTable.Version Score := OldTable.Score Wins := OldTable.Wins Losses := OldTable.LossesДля отслеживания всех
player_stats_tables, мы будем использовать сохраняемый ассоциативный массивweak_map, состоящий из экземпляровplayer, соотнесённых сplayer_stats_table. Добавьте этот слабый ассоциативный массив в свой файл.Verse# Maps players to a table of their player stats. var PlayerStatsMap:weak_map(player, player_stats_table) = map{}Готовый класс
player_stats_tableдолжен выглядеть следующим образом:Verseusing { /Fortnite.com/Devices } using { /Verse.org/Simulation } using { /UnrealEngine.com/Temporary/Diagnostics } # Tracks different persistable stats for each player. player_stats_table := class<final><persistable>: # The version of the current stats table. Version<public>:int = 0 # The score of a player.
Управление показателями для каждого игрока
Касс player_stats_table позволяет отслеживать показатели отдельного игрока, но пока мы не можем управлять ими. Нужно обновлять таблицы статистики для каждого игрока всякий раз, когда он получает очки, а в зависимости от дизайна вашей игры может одновременно отображаться много игроков.
Для этого вы будете использовать другой класс, который управляет статистикой всех игроков и записывает изменения показателей каждый раз, когда игрок одерживает победу, проигрывает или набирает очки. Чтобы настроить управляющий класс, выполните следующие действия:
Создайте новый файл Verse с названием
player_stats_managerс помощью проводника Verse. В этом файле создайте новый классplayer_stats_manager.Verseusing { /Fortnite.com/Devices } using { /Verse.org/Simulation } using { /UnrealEngine.com/Temporary/Diagnostics } # Manages and updates player_stat_tables for each player. player_stats_manager := class():Класс
player_stats_managerдолжен выполнять несколько функций. Нужно настроитьplayer_stats_tableдля игрока, обновить значенияScore,WinsиLossesкаждого игрока и возвращать игрокуplayer_stats_table. Для каждого из этих действий прописывается отдельная функция. Добавьте новую функциюInitializePlayer()к определению классаplayer_stats_manager. Эта функция будет инициализировать показатели заданного игрока.Verse# Initialize stats for the given player. InitializePlayer(Player:player):void=В
InitializePlayer()проверьте, существует ли уже заданный игрок вPlayerStatsMap. Если нет, задайте значение этого игрока в массиве для новой таблицыplayer_stats_table. В конечном итоге функцияInitializePlayer()должна выглядеть следующим образом:Verse# Initialize stats for the given player. InitializePlayer(Player:player):void= if: not PlayerStatsMap[Player] set PlayerStatsMap[Player] = player_stats_table{} else: Print("Unable to initialize player stats")Добавьте новую функцию
InitializeAllPlayers()к определению вашего классаplayer_stats_manager. Эта функция принимает массив игроков и вызывает функциюInitializePlayer()для каждого из них. Готовая функцияInitializeAllPlayers()должна выглядеть следующим образом:Verse# Initialize stats for all current players. InitializeAllPlayers(Players:[]player):void = for (Player : Players): InitializePlayer(Player)Чтобы вернуть показатели определённого игрока, вам нужна функция, которая возвращает таблицу
player_stats_tableэтого игрока. Добавьте новую функциюGetPlayerStats()к определению классаplayer_stats_manager, принимающего агента. Добавьте модификатор<decides><transacts>позволяющий этой функции возвращать неоднозначный результат и откатываться назад в случае, если таблицы статистики игрока не существует. В функцииGetPlayerStats()создайте новую переменнуюPlayerStatsв таблицеplayer_stats_table.Verse# Return the player_stats_table for the provided Agent. GetPlayerStats(Agent:agent)<decides><transacts>:player_stats_table= var PlayerStats:player_stats_table = player_stats_table{}В выражении
ifприведитеAgent, переданный этой функции, кPlayer, после чего получитеplayer_stats_tableэтого игрока изPlayerStatsMap. Затем задайтеPlayerStatsдля этой таблицы путём вызоваMakePlayerStatsTable(). В конечном итоге мы будет возвращатьPlayerStats. В конечном итоге функцияGetPlayerStats()должна выглядеть следующим образом:Verse# Return the player_stats_table for the provided Agent. GetPlayerStats(Agent:agent)<decides><transacts>:player_stats_table= var PlayerStats:player_stats_table = player_stats_table{} if: Player := player[Agent] PlayerStatsTable := PlayerStatsMap[Player] set PlayerStats = MakePlayerStatsTable(PlayerStatsTable) PlayerStatsДля обновления показателей счёта, побед и поражений мы создадим отдельные функции для каждого из них. Добавьте новую функцию с названием
AddScore()в ваш файлplayer_stats_manager. Эта функция будет принимать агент для начисления очков и количество начисляемых очков в видецелочисленного значения.Verse# Adds to the given Agent's score and updates both their stats table # in PlayerStatsManager and the billboard in the level. AddScore<public>(Agent:agent, NewScore:int):void=Данные обновляются следующим образом: сначала выполняется проверка, что данные в сохраняемом массиве
weak_mapявляются допустимыми, после чего эти данные заменяются на обновлённую копию класса. Чтобы выполнить эту процедуру для счёта, мы получаем счёт игрока изPlayerStatsTable, затем для таблицы вPlayerStatsMapзадаём результат создания новой таблицыplayer_stats_tableс помощьюMakePlayerStatsTable(), передавая значение текущего счёта с добавлением нового счёта. При работе с классом, содержащим несколько полей, конструктор класса позволяет легко обновить отдельное поле без необходимости каждый раз копировать все поля, когда необходимо выполнить обновление. ФункцияAddScore()должна выглядеть следующим образом:Verse# Adds to the given Agent's score and updates both their stats table # in PlayerStatsManager and the billboard in the level. AddScore<public>(Agent:agent, NewScore:int):void= if: Player := player[Agent] PlayerStatsTable := PlayerStatsMap[Player] CurrentScore := PlayerStatsTable.Score set PlayerStatsMap[Player] = player_stats_table: MakePlayerStatsTable<constructor>(PlayerStatsTable) Score := CurrentScore + NewScoreПовторите те же шаги для показателей побед и поражений добавив
NewWinsиNewLossesк победам или поражениям игрока соответственно при вызовеMakePlayerStatsTable().Verse# Adds to the given Agent's wins and updates both their stats table # in PlayerStatsManager and the billboard in the level. AddWin<public>(Agent:agent, NewWins:int):void= if: Player := player[Agent] PlayerStatsTable := PlayerStatsMap[Player] CurrentWins := PlayerStatsTable.Wins set PlayerStatsMap[Player] = player_stats_table: MakePlayerStatsTable<constructor>(PlayerStatsTable) Wins := CurrentWins + NewWinsОкончательный файл
player_stats_managerдолжен выглядеть следующим образом.Verseusing { /Fortnite.com/Devices } using { /Verse.org/Simulation } using { /UnrealEngine.com/Temporary/Diagnostics } # Manages and updates player_stat_tables for each player. player_stats_manager := class(): # Return the player_stats_table for the provided Agent. GetPlayerStats(Agent:agent)<decides><transacts>:player_stats_table= var PlayerStats:player_stats_table = player_stats_table{}
Тестирование сохранения показателей с использованием устройств
Теперь, когда вы настроили сохраняемые классы, можно протестировать их на вашем уровне.
Создайте новое устройство Verse с названием player_stats_example. Порядок действий см. в разделе «Создание собственного устройства с помощью Verse».
В начале определения класса
player_stats_exampleдобавьте следующие поля:Редактируемое устройство
button_deviceс названиемScorePointsButton. Это кнопка при активации добавляет счёт игроку.Verse# Adds to the activating player's score. @editable ScorePointsButton:button_device = button_device{}Редактируемое устройство
billboard_deviceс названиемStatsBillboard. Оно показывает счёт, рекорд, количество побед и поражений игрока.Verse# Displays the player's Score, High Score, Wins, and Losses @editable StatsBillboard:billboard_device = billboard_device{}Редактируемое устройство button_device с названием
CheckWinButton. Эта кнопка сбрасывает счёт каждого игрока и назначает ему победу или поражение в зависимости от счёта игрока.Verse# Resets the player's score and award them a win or a loss # depending if their current score is greater than WinScore. @editable CheckWinButton:button_device = button_device{}Редактируемое
целочисленноезначение с названиемWinScore. Это счёт, которого должны достичь игроки, чтобы им была засчитана победа после активацииCheckWinButton.Verse# The score players need to reach to be awarded a win after # the CheckWinButton is activated. @editable WinScore:int = 10Редактируемая
целочисленнаяпеременная с названиемAwardScore. Это количество очков, которое начисляется игрокам при взаимодействии с кнопкой.Verse# The amount of score to award per button press. @editable AwardScore:int = 1Диспетчер
player_stats_managerс названиемPlayerStatsManager. Он будет управлять показателями для каждого игрока и обновлять их.Verse# Manages and updates stats for each player. PlayerStatsManager:player_stats_manager = player_stats_manager{}Сообщение StatsMessage, которое принимает от агента четыре целочисленных значения: Score, MaxScore, Wins и Losses. Это сообщение будет показывать игроку его статистику на рекламном щите.
Verse# Displays a player's stats on a billboard. StatsMessage<localizes>(Player:agent, Score:int, Wins:int, Losses:int):message = "{Player}, Stats:\n Score: {Score}\n Wins: {Wins}\n Losses: {Losses}"
Скомпилируйте код и перетащите созданное в Verse устройство на свой остров. Порядок действий см. в разделе «Добавление устройства Verse на ваш уровень».
На панели Сведения устройства на вашем уровне назначьте устройству «Кнопка» значение ScorePointsButton, а устройству «Рекламный щит» — значение StatsBillboard.
Чтобы отобразить статистику игрока на StatsBillboard, добавьте новую функцию
UpdateStatsBillboard()в определение классаplayer_stats_example. Эта функция содержит агент, показатели которого нужно отобразить.Verse# Retrieves the stats of the given player and displays their stats # on the StatsBillboard. UpdateStatsBillboard(Agent:agent):void=Получите текущие показатели заданного агента в
UpdateStatsBillboard(), вызвав функцию диспетчера статистикиGetPlayerStats[]. Затем вызовитеSetText()для StatsBillboard и передайте новое сообщениеStatsMessage(). Для компоновки этого сообщенияStatsMessage()нужно получить значения Score, Wins и Losses из текущих показателей агента. Готовая функцияUpdateStatsBillboard()должна выглядеть следующим образом:Verse# Retrieves the stats of the given player and displays their stats # on the StatsBillboard. UpdateStatsBillboard(Agent:agent):void= if: # Get the current stats of the given agent. CurrentPlayerStats := PlayerStatsManager.GetPlayerStats[Agent] then: StatsBillboard.SetText( StatsMessage( Player := Agent,Добавьте новую функцию
AddScore()к определению классаplayer_stats_example. Эта функция содержит агент и добавляет ему очки при каждом взаимодействии с кнопкой ScorePointsButton.Verse# Adds to the given player's score and updates both their stats table # in PlayerStatsManager and the billboard in the level. AddScore(Agent:agent):void=В
AddScore()получаем текущую статистику заданного агента, а также его текущий счёт. Затем вызываемAddScore()изPlayerStatsManagerи передаём агент иAwardScore, чтобы присвоить ему новый счёт. И наконец, вызываемUpdateStatsBillboard()и передаём заданный агент. Готовая функцияAddScore()должна выглядеть следующим образом:Verse# Adds to the given player's score and updates both their stats table # in PlayerStatsManager and the billboard in the level. AddScore(Agent:agent):void= if: CurrentPlayerStats := PlayerStatsManager.GetPlayerStats[Agent] CurrentScore := CurrentPlayerStats.Score then: Print("Current Score is: {CurrentScore}") PlayerStatsManager.AddScore(Agent, AwardScore) UpdateStatsBillboard(Agent)Чтобы присуждать игроку выигрыш или проигрыш при нажатии на кнопку CheckWin, добавьте новую функцию
CheckWin()в определение класса player_stats_manager.Verse# Awards a player a win or a loss when they interact # with the CheckWinButton. CheckWin(Agent:agent):void=Сначала задайте переменную
CurrentScoreдля отслеживания текущего счёта агента. Затем, как и в случае с функциейAddScore(), получите текущий счёт из таблицы статистики игрока.Verse# Awards a player a win or a loss when they interact # with the CheckWinButton. CheckWin(Agent:agent):void= var CurrentScore:int = 0 if: PlayerStats := PlayerStatsManager.GetPlayerStats[Agent] set CurrentScore = PlayerStats.ScoreЕсли текущий счёт агента превышает значение
WinScore, нужно записать победу вPlayerStatsManager. В противном случае записывается поражение. Наконец, сбросьте счёт агента, вызвавAddScore()с отрицательнымCurrentScore, а затем отобразите его статистику на щите со статистикой. В конечном итоге функцияCheckWin()должна выглядеть следующим образом:Verse# Awards a player a win or a loss when they interact # with the CheckWinButton. CheckWin(Agent:agent):void= var CurrentScore:int = 0 if: PlayerStats := PlayerStatsManager.GetPlayerStats[Agent] set CurrentScore = PlayerStats.Score then: Print("Current Score is: {CurrentScore}") if:В
OnBegin()мы подписываемScorePointsButton.InteractedWithEventнаAddScore(), аCheckWinButton.InteractedWithEventнаCheckWin(). Затем получаем массив каждого игрока в игре, вызвавGetPlayers(), и инициализируем их всех с помощью функции диспетчера статистикиInitializeAllPlayers().Verse# Runs when the device is started in a running game OnBegin<override>()<suspends>:void= # Register Button Events ScorePointsButton.InteractedWithEvent.Subscribe(AddScore) CheckWinButton.InteractedWithEvent.Subscribe(CheckWin) Players := GetPlayspace().GetPlayers() # Initialize player stats PlayerStatsManager.InitializeAllPlayers(Players)Сохраните код и скомпилируйте его.
Тестирование сохраняемых данных в игре
Вы можете протестировать сохраняемые данные в сеансе редактирования, но они будут сброшены при выходе из сеанса и его перезапуске. Чтобы данные сохранялись от сеанса к сеансу, нужно запустить сеанс игрового теста и изменить некоторые настройки в разделе Настройки острова. Более подробную информацию о том, как тестировать сохраняемые данные как в редакторе, так и в игре см. в разделе «Тестирование с сохраняемыми данными» на странице о сохраняемых данных.
После настройки сеанса нажатие кнопки **ScorePoints** во время игрового теста должно добавлять игроку очки и отображать это обновление на рекламном щите. Взаимодействие с кнопкой **CheckWin** должно увеличивать значение побед или поражений игрока, в зависимости от счёта игрока. После возвращения в лобби и повторного входа на остров статистика игрока должна сохраниться, а значения его побед/поражений и рекорд будут отображаться на рекламном щите при каждом его обновлении.
Самостоятельная работа
В этом уроке вы узнали, как использовать Verse для создания сохраняемых данных, отслеживаемых для каждого игрока от сеанса к сеансу. А теперь попробуйте придумать, как можно использовать сохраняемые данные, чтобы улучшить свою игру.
Получится ли у вас создать систему сохранения, которая запоминала бы последнюю контрольную точку, до которой добрался игрок?
Как насчёт системы, которая запоминает персонажей, с которыми вы общались, а также ваши отношения с ними?
Как насчёт создать систему, которая будет давать игрокам ограниченное количество времени на достижение целей, а в случае неудачи будет сбрасывать их прогресс?
Полный код
Ниже приведён полный код, написанный в этом разделе обучения.
player_stats_table.verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Tracks different persistable stats for each player.
player_stats_table := class<final><persistable>:
# The version of the current stats table.
Version<public>:int = 0
# The score of a player.
player_stats_manager.verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# Manages and updates player_stat_tables for each player.
player_stats_manager := class():
# Return the player_stats_table for the provided Agent.
GetPlayerStats(Agent:agent)<decides><transacts>:player_stats_table=
var PlayerStats:player_stats_table = player_stats_table{}
player_stats_example.verse
using { /Fortnite.com/Devices }
using { /Fortnite.com/Game }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
# A Verse-authored creative device that can be placed in a level
player_stats_example := class(creative_device):
# Adds to the activating player's score.
@editable