Збережені дані — це вбудоване рішення Minecraft для збереження даних під час сеансів.
Дані зберігаються на носію даних та перезавантажуються, коли гру закривають і відкривають знову. Ці дані зазвичай обмежені (наприклад, рівень). Дані записуються на носій, оскільки NBT і кодеки використовуються для серіалізації/десеріалізації цих даних.
Розгляньмо простий сценарій, коли нам потрібно зберегти кількість блоків, зламаних гравцем. Ми можемо зберегти цей підрахунок на логічному сервері.
Ми можемо використовувати подію PlayerBlockBreakEvents.AFTER із простим статичним цілим полем, щоб зберегти це значення та опублікувати його як повідомлення чату.
java
private static int blocksBroken = 0; // keeps track of the number of blocks broken
PlayerBlockBreakEvents.AFTER.register((level, player, pos, state, blockEntity) -> {
blocksBroken++; // increment the counter each time a block is broken
player.displayClientMessage(Component.literal("Blocks broken: " + blocksBroken), false);
});1
2
3
4
5
6
2
3
4
5
6
Тепер, коли ви ламаєте блок, ви побачите повідомлення з підрахунком.

Якщо ви перезапустите Minecraft, завантажите світ і почнете ламати блоки, ви помітите, що підрахунок скинуто. Ось де нам потрібні збережені дані. Потім ми можемо зберегти цей підрахунок, щоб наступного разу, коли ви завантажуватимете світ, ми могли отримати збережений підрахунок і почати збільшувати її з цього моменту.
Збереження даних
SavedData — основний клас, який відповідає за керування збереженням/завантаженням даних. Оскільки це абстрактний клас, ви повинні надати реалізацію.
Налаштування класу даних
Назвімо наш клас даних SavedBlockData і розширимо SavedData.
Цей клас міститиме поле для відстеження кількості зламаних блоків, а також метод отримання та метод збільшення цього числа.
java
public class SavedBlockData extends SavedData {
private int blocksBroken = 0;
public SavedBlockData() {
}
public int getBlocksBroken() {
return blocksBroken;
}
// :::set_dirty
public void incrementBlocksBroken() {
blocksBroken++;
// If saved data is not marked dirty, nothing will be saved when Minecraft closes.
setDirty();
}
// :::set_dirty
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Для серіалізації та десеріалізації цих даних нам потрібно визначити кодек. Ми можемо створити кодек, використовуючи різні примітивні кодеки, надані Minecraft.
Для ініціалізації класу вам знадобиться конструктор з аргументом int.
java
public SavedBlockData(int count) {
blocksBroken = count;
}1
2
3
2
3
Тоді ми можемо створити кодек.
java
private static final Codec<SavedBlockData> CODEC = Codec.INT.xmap(
SavedBlockData::new, // Create a new 'SavedBlockData' from the stored number.
SavedBlockData::getBlocksBroken // Return the number from the 'SavedBlockData' to be saved/
);1
2
3
4
2
3
4
Ми повинні викликати setDirty(), коли дані фактично змінюються, щоб Minecraft знав, що їх потрібно зберегти на носій.
java
public void incrementBlocksBroken() {
blocksBroken++;
// If saved data is not marked dirty, nothing will be saved when Minecraft closes.
setDirty();
}1
2
3
4
5
6
2
3
4
5
6
Нарешті, ми повинні мати SavedDataType, який описує наші збережені дані. Перший аргумент відповідає імені файлу, який буде створено в каталозі data світу.
java
private static final SavedDataType<SavedBlockData> TYPE = new SavedDataType<>(
"saved_block_data", // The unique name for this saved data.
SavedBlockData::new, // If there's no 'SavedBlockData', yet create one and refresh fields.
CODEC, // The codec used for serialization/deserialization.
null // A data fixer, which is not needed here.
);1
2
3
4
5
6
2
3
4
5
6
Доступ до збережених даних
Як згадувалося раніше, збережені дані можуть бути пов’язані з таким обсягом, як поточний рівень. У цьому випадку наші дані будуть частиною даних рівня. Ми можемо отримати DimensionDataStorage рівня, щоб додавати та змінювати наші дані.
Ми розмістимо цю логіку в методі утиліти.
java
public static SavedBlockData getSavedBlockData(MinecraftServer server) {
// This could be either the overworld or another dimension.
ServerLevel level = server.getLevel(ServerLevel.OVERWORLD);
if (level == null) {
return new SavedBlockData(); // Return a new instance if the level is null.
}
// The first time the following 'computeIfAbsent' function is called, it creates a new 'SavedBlockData'
// instance and stores it inside the 'DimensionDataStorage'.
// Subsequent calls to 'computeIfAbsent' returns the saved 'SavedBlockData' NBT on disk to the Codec in our type,
// using the Codec to decode the NBT into our saved data.
return level.getDataStorage().computeIfAbsent(TYPE);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
Використання збережених даних
Тепер, коли ми все налаштували, збережімо деякі дані.
Ми можемо повторно використати перший сценарій і замість того, щоб збільшувати поле, ми можемо викликати наш incrementBlocksBroken з нашого SavedBlockData.
java
PlayerBlockBreakEvents.AFTER.register((level, player, pos, state, blockEntity) -> {
MinecraftServer server = level.getServer();
if (server == null) {
return;
}
// Retrieve the saved block data from the server.
SavedBlockData savedData = SavedBlockData.getSavedBlockData(server);
savedData.incrementBlocksBroken(); // Increment the counter each time a block is broken.
player.displayClientMessage(Component.literal("Blocks broken: " + savedData.getBlocksBroken()), false);
});1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
Це повинно збільшити значення та зберегти його на носію.
Якщо ви перезапустите Minecraft, завантажите світ і зламаєте блок, ви побачите, що раніше збережений підрахунок тепер збільшився.
Якщо ви зайдете в каталог data світу, ви побачите файл .dat з назвою saved_block_data.dat. Відкриття цього файлу в програмі читання NBT покаже, як зберігаються наші дані.

