Сохраняемость данных позволяет отслеживать и сохранять данные каждого игрока от одного игрового сеанса к другому. Сохраняемые данные работают путём хранения данных для каждого отдельного игрока, таких как его профиль или статистика, в Verse. Эти данные затем можно обновлять столько раз, сколько изменяется значение данных. Поскольку эти данные сохраняются, они будут сохраняться во время игровых сеансов и будут доступны в любое время, когда игрок находится в игре онлайн. Дополнительную информацию см. в разделе Использование сохраняемых данных в Verse.
На этой странице приведены некоторые рекомендации по работе с сохраняемыми данными в Verse.
Использование классов для добавления новых полей в дальнейшем
В настоящее время единственный тип сохраняемых данных, которые можно изменить после публикации острова, — это тип class, если новые поля имеют значения по умолчанию. Это означает, что загрузка сохранённых данных из предыдущей версии будет включать новые поля и их значения по умолчанию.
Давайте рассмотрим пример публикации проекта со следующими сохраняемыми данными.
player_profile_data := class<final><persistable>:
Class:player_class = player_class.Villager
XP:int = 0
Rank:int = 0Поскольку проект опубликован и запущен, эти сохраняемые данные будут связаны с игроками, которые играли в эту игру. Если бы мы добавили больше полей в данные профиля игрока, например количество заданий и история, сохраняемые данные в обновлённом проекте могли бы выглядеть следующим образом.
player_profile_data := class<final><persistable>:
Class:player_class = player_class.Villager
XP:int = 0
Rank:int = 0
CompletedQuestCount:int = 0
QuestHistory:[]string = array{}Сохраняемые данные для всех игроков, которые играли с первой версией класса player_profile_data, теперь будут содержать новые поля:
CompletedQuestCountсо значением0, которое является заданным значением по умолчанию.QuestHistoryс пустым массивом строк, который является указанным значением по умолчанию.
Этот подход работает, поскольку для новых полей было указано значение по умолчанию, позволяющее обновлять старую версию данных.
Поскольку после публикации проекта можно обновлять только классы, мы настоятельно рекомендуем использовать класс в качестве типа значения любой переменной weak_map в области видимости модуля.
Подробнее о том, как создать сохраняемый класс, см. в разделе «Сохраняемые типы».
Использование конструкторов для частичных обновлений
Если вы используете классы, мы рекомендуем использовать конструктор для создания нового экземпляра вашего класса, содержащего обновлённое состояние, поскольку конструкторы позволяют выполнять частичные обновления классов.
В следующем примере показано, как обновить PlayerProfileDataMap. Функция GrantXP() получает текущие данные игрока, а затем вызывает конструктор MakePlayerProfileData(), чтобы создать новую версию данных его профиля. Поскольку исходные данные игрока передаются конструктору вместе с новым значением ОП, обновится только значение ОП, а все остальные данные игрока останутся прежними.
MakePlayerProfileData<constructor>(Src:player_profile_data)<transacts> := player_profile_data:
Version := Src.Version
Class := Src.Class
XP := Src.XP
Rank := Src.Rank
CompletedQuestCount := Src.CompletedQuestCount
QuestHistory := Src.QuestHistory
GrantXP(Agent:agent, GrantedXP:int):void=
if:
В предыдущем примере показано, как обновить одно поле, но таким образом можно обновить и любое нужное количество:
set PlayerProfileDataMap[Player] = player_profile_data:
QuestHistory := UpdatedSaveData.QuestHistory
CompletedQuestCount := OldData.CompletedQuestCount + 1
MakePlayerProfileData<constructor>(OldData)Управление версиями сохраняемых данных
Мы рекомендуем использовать в сохраняемых классах управление версиями, чтобы определять версию экземпляра данных, которые были ранее сохранены для игрока. Используя версии, вы можете находить и применять переносы, если определение вашего сохраняемого класса или логика игрового процесса в определённый момент времени изменятся.
Несмотря на то, что для обозначения версии вашего сохраняемого класса вы можете использовать значения целого числа или строки, мы рекомендуем использовать значения option для хранения ссылок на текущие и прошлые версии ваших данных. Рассмотрим следующую настройку:
var SavedPlayerData:weak_map(player, player_data) = map{}
# A player data class containing optional fields of versioned player data. Only one of these
# optional values should contain a real value at any given time.
player_data := class<final><persistable>:
V1:?v1_player_data = false
V2:?v2_player_data = false
# Original version of player data.
В данном примере класс player_data содержит значения option как для первой, так и для второй версии соответствующего класса данных, которые представлены классами v1_player_data и v2_player_data. Только одна из версий V1 или V2 должна быть настроена, чтобы у игроков не было нескольких версий связанных с ними данных.
Исходные данные игрока в V1 содержат три поля int. В версии данных версии V2 поле «Время игры» изменено на значение с плавающей запятой, а также добавлены два новых поля. Поскольку тип поля «Время игры» в версии V2 был изменён, его нужно преобразовать для всех игроков, у которых всё ещё имеются старые данные версии V1. Когда игрок со старыми данными V1 присоединится к игре, вы сможете использовать вспомогательные функции конструктора для создания нового класса данных V2 на основе его старых данных, например:
# Create v1_player_data using existing v1_player_data.
MakeV1PlayerData<constructor>(SourceData:v1_player_data)<transacts> := v1_player_data:
XP := SourceData.XP
Rank := SourceData.Rank
Playtime := SourceData.Playtime
# Create v2_player_data using existing v2_player_data.
MakeV2PlayerData<constructor>(SourceData:v2_player_data)<transacts> := v2_player_data:
XP := SourceData.XP
Rank := SourceData.Rank
Иногда вы можете захотеть принудительно сбросить данные для игроков, присоединяющихся к вашему острову. Для этого можно переназначить значение по умолчанию для сохраняемых данных в 'weak_map' для всех игроков и изменить поле Version класса. Если вы используете необязательные разные версии данных, вы можете сбросить данные, задав для этих полей значение false.
Чтобы узнать, были ли данные игрока уже сброшены, вы можете проверить значение Version в сохраняемых данных игрока, чтобы узнать, является ли оно актуальным.
Проверка допустимых пределов объёма сохраняемых данных
Если обновление может повлиять на общий размер сохраняемых данных, вам следует убедиться, что сохраняемые данные по-прежнему соответствуют ограничениям системы сохраняемости Verse. Если вы попытаетесь обновить сохраняемые данные, и их размер превысит ограничения, вы получите ошибку среды выполнения Verse. Подробности см. в разделе «Максимальный размер сохраняемого объекта».
Вы можете проверить, как ваши обновления влияют на общий размер, используя функцию FitsInPlayerMap().
В следующем примере сохраняемые данные содержат массив строк. Если этот массив когда-либо станет слишком большим для хранения в weak_map, что происходит при ошибке FitsInPlayerMap(), в этом примере массив будет очищен, после чего будет добавлен только самый последний сохранённый элемент.
# Construct and return a new player_profile_data with updated quest history.
SetQuestHistory(Src:player_profile_data, NewQuestHistory:[]string)<transacts>:player_profile_data =
NewData:player_profile_data = player_profile_data:
MakePlayerProfileData<constructor>(Src)
QuestHistory := NewQuestHistory
# Set a player's quest history in the PlayerProfileDataMap.
RecordQuestHistory(Agent:agent, QuestHistory:string):void=
if:
CheckSaveDataForPlayer[Agent]
Реакция на присоединение игрока к острову
Как только новый игрок присоединится к вашему острову, для него не будет автоматически создана запись в сохраняемом weak_map. Вам необходимо добавить эту запись самостоятельно в Verse.
Для этого вы можете либо проверять присутствие игрока в weak_map при каждом обращении к нему, либо добавлять данные по умолчанию в weak_map при каждом подключении игрока, о чём можно узнать, если подписаться на игровое событие PlayerAddedEvent().
GetPlayspace().PlayerAddedEvent().Subscribe(OnPlayerAdded)
# Later in your file
OnPlayerAdded(Player:player):void=
if:
not PlayerProfileDataMap[Player]
set PlayerProfileDataMap[Player] = player_profile_data{}