Към съдържанието
PN Scripts

Игри

Слотове за запис в браузърна игра с localStorage: данни с версия, сигурно зареждане и запис при затваряне

  • PN Scripts
  • 8 мин четене

Играч, който загуби напредъка си в браузърна игра, рядко се връща. А това се случва по-често, отколкото би трябвало: обновление преименува поле и старите записи спират да се зареждат, хранилището се пълни и записът тихо се проваля, играчът затваря раздела по средата на рунд или браузърът изчиства данните на сайта след седмица. Това ръководство показва как нашата игра с балон Billowpost пази три слота за запис и настройките в localStorage, защо е там всяка част и какво бихме добавили за игра, която трябва да пази записи години наред. Кодът е съкратен от собствения модул за записи на играта.

#Един ключ, един запис с версия

Всичко, което играта помни, живее под един ключ като един JSON документ: номер на версията, последният ползван слот, три слота и настройките, които са общи за всички слотове.

export const SAVE_KEY = 'pn-billowpost';
export const SAVE_VERSION = 1;

export interface SaveData {
  v: number;
  lastSlot: number;
  slots: (Slot | null)[]; // три слота, null = празен
  settings: Settings;     // сила на звука, достъпност, управление, графика
}

Един ключ прави записа неделим: цялото състояние е или старата версия, или новата, никога половината от всяка. Сложете в началото на ключа името на студиото или на играта, защото всички игри на един и същ произход делят една област за съхранение, а сайт за игри, който пуска много заглавия от един домейн, е точно такъв случай. Номерът на версията не струва нищо днес и е единственият начин да разпознаете стар запис след промяна в структурата.

#Зареждайте предпазливо

Зареждането е мястото, където повечето системи за записи се чупят. Четенето от localStorage може да хвърли грешка: някои браузъри го блокират в частни прозорци или във вградени рамки със строги настройки. Запазеният текст може да е повреден или писан от по-стара версия. А запис от миналия месец няма нито едно от полетата, добавени оттогава. Зареждащата функция се справя и с трите:

export function loadSave(): SaveData {
  let raw: string | null = null;
  try {
    raw = localStorage.getItem(SAVE_KEY);
  } catch {
    raw = null; // хранилището е блокирано: играе се без запис
  }
  if (!raw) return fresh();
  try {
    const d = JSON.parse(raw);
    const out = fresh();
    out.lastSlot = typeof d.lastSlot === 'number' ? d.lastSlot : -1;
    if (Array.isArray(d.slots))
      out.slots = [0, 1, 2].map((i) => (d.slots[i] ? { ...newSlot(), ...d.slots[i] } : null));
    if (d.settings) {
      out.settings = { ...out.settings, ...d.settings };
      const def = defaultBindings();
      const b = d.settings.bindings;
      out.settings.bindings = { keys: { ...def.keys, ...(b?.keys ?? {}) }, pad: { ...def.pad, ...(b?.pad ?? {}) } };
    }
    return out;
  } catch {
    return fresh();
  }
}

Ключовият похват е { ...newSlot(), ...stored }. Новият слот има всяко поле с разумна стойност по подразбиране; запазеният слот презаписва каквото има. Когато по-късна версия добави към слота например поредица от ежедневни игри, старият слот се зарежда с празна поредица, вместо да се срине заради липсващо поле. Свързванията на управлението минават през същото едно ниво по-навътре, така че действие, добавено с обновление, получава своя клавиш, а пренастроените от играча клавиши остават. Статията ни за пренастройка на клавиатура и геймпад описва тази страна.

#Когато допълването не стига: преобразувания

Допълването се справя с добавени полета. Не се справя с преименувано поле, сменена мерна единица или разделена структура. За тях номерът на версията се отплаща. Преди допълването прекарайте запазените данни през верига от малки функции, по една за всяка стъпка на версията:

const MIGRATIONS = {
  1: (d) => ({ ...d, v: 2, slots: d.slots.map((s) => s && { ...s, playTime: Math.round((s.playMs ?? 0) / 1000) }) }),
  // 2: (d) => ...,
};

function migrate(d) {
  while (d.v < SAVE_VERSION && MIGRATIONS[d.v]) d = MIGRATIONS[d.v](d);
  return d;
}

Всяка стъпка е мъничка и се тества с истински запис от съответната версия, пазен като образец в папката с тестове. Никога не променяйте стара стъпка след пускането; добавете нова. Billowpost още е на версия 1, така че веригата ѝ е празна, но номерът вече е във всеки запис, който тя прави.

#Записвайте с резервен вариант

Браузърите дават на всеки произход ограничено място в localStorage, около 5 MiB в съвременните браузъри, а запис, който не се побира, хвърля грешка. В Billowpost големите неща са пощенските картички: малки JPEG снимки, които играчът прави при забележителности. Когато запис се провали, играта маха снимките и опитва отново, защото напредъкът е по-важен от сувенирите:

export function writeSave(d: SaveData): boolean {
  try {
    localStorage.setItem(SAVE_KEY, JSON.stringify(d));
    return true;
  } catch {
    try {
      for (const s of d.slots) if (s) for (const k of Object.keys(s.postcards)) s.postcards[k] = '';
      localStorage.setItem(SAVE_KEY, JSON.stringify(d));
      return true;
    } catch {
      return false;
    }
  }
}

Решете предварително какво може да изхвърлите и дръжте големите медийни данни извън основния запис, когато можете. Снимки, повторения и дневници са за IndexedDB, която позволява много повече място и пази двоични данни; малкият запис за напредъка остава в localStorage, където е прост и синхронен.

#Записвайте, когато играчът излиза

Автоматичният запис на контролни точки не стига за браузърна игра, защото играчът може да затвори раздела по всяко време, а на телефон браузърът може да изхвърли раздел на заден план без предупреждение. Billowpost записва текущия рунд всеки път, когато страницата се скрие, и при pagehide, последното събитие, което страницата получава надеждно:

document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    if (mode === 'play') pause();
    saveRun();
  }
});
addEventListener('pagehide', () => saveRun());

Не разчитайте на beforeunload или unload: те не се задействат надеждно на мобилни устройства, а unload освен това пречи на страницата да ползва кеша за връщане назад и напред. Едно правило в saveRun лесно се пропуска: то отказва да запише, докато пратка с поща още пада във въздуха. Снимка на състоянието по време на падането би трябвало да пази полета на пратката, а точното ѝ възстановяване е по-крехко от това да се изчака секунда, докато падне. Записвайте в състояния, които можете да възстановите точно; когато рундът свърши, изчистете записания рунд, за да не предлага „Продължи“ никога завършен рунд.

#Три слота и никакво презаписване без питане

Слотовете позволяват на едно семейство да дели браузър и на играча да започне отначало, без да загуби завършена игра. Когато играчът започне нова игра и всички слотове са заети, Billowpost продължава в най-новия, вместо да замени нещо; слот се презаписва само след потвърждение в диалог. Настройките стоят извън слотовете, защото силата на звука, размерът на текста и управлението принадлежат на човека пред клавиатурата, а не на запазената игра.

#Какво все пак може да изтрие запис

Дори съвършената система за записи живее в хранилище, което браузърът може да изчисти. Играчите изчистват данните на сайтовете. Частните прозорци изтриват всичко, когато се затворят. Защитата от проследяване в Safari маха хранилището, в което пишат скриптовете, на сайт, с който човекът не е взаимодействал седем дни на ползване на браузъра, а игрите, вградени в страница на друг сайт, получават хранилище, отделено за този сайт. Три мерки помагат:

  • Поискайте постоянно хранилище с navigator.storage.persist(), след като играчът е постигнал някакъв напредък. Браузърите, които го разрешат, няма да изтрият данните при недостиг на място.
  • Предложете в настройките износ и внос на записа като файл, така че играчът да може да премине в друг браузър или да пази резервно копие.
  • Когато запис не може да се прочете, запазете суровия текст под втори ключ, преди да започнете наново, така че по-късна версия или запитване за помощ още да могат да го възстановят.

За двигатели с файлова система картината е друга; статията ни за система за записи в Godot 4, която оцелява при сривове и обновления описва временни файлове, контролни суми и редуващи се резервни копия, а добавката за Godot Skrinka събира този подход. Billowpost е безплатна и е обявена като „Очаквайте скоро“ в каталога с игри на PN Scripts.

#Често задавани въпроси

Колко може да запише браузърна игра в localStorage?

Около 5 MiB на произход в съвременните браузъри, споделени от всичко на този произход. Пазете записа за напредъка малък и слагайте снимки или повторения в IndexedDB.

Защо играчите ми загубиха записите след обновление?

Обикновено защото новата версия очаква полета, които старият запис няма. Наслагвайте запазените данни върху пълни стойности по подразбиране и добавете номер на версията с малки стъпки за преобразуване на преименувани или променени полета.

Да записва ли браузърната игра при beforeunload?

Не. То е ненадеждно на мобилни устройства. Записвайте, когато страницата се скрие, и при pagehide, а автоматично и на естествени контролни точки.

Може ли браузърът сам да изтрие записа на игра?

Да, при недостиг на място, в частни прозорци и в Safari след седем дни ползване на браузъра без взаимодействие със сайта. Поискайте постоянно хранилище и позволете на играчите да изнасят записа си като файл.

#Източници

Коментари

Коментари

Напишете първия коментар.

Оставете коментар

Следваща стъпка

pnscripts.com/bg/contact

Пишете на екипа

Попитайте за статия или ни разкажете за проект, който искате да изградим. Отговаряме до един работен ден.

Пишете ни

Семейството PN Scripts

Другите сайтове на PN Scripts

Хостингът, игрите и блогът имат свои сайтове, поддържани от същата компания.

  • pnscripts.com

    PN Scripts

    Софтуерно инженерство

    Уеб и мобилни приложения и API по поръчка, плюс нашите продукти и плъгини с отворен код.

  • games.pnscripts.com

    Игри

    Игри и игрови сървъри

    Домът на игрите на PN Scripts: безплатни браузърни игри, които се играят веднага, а игровите сървъри предстоят.

  • hosting.pnscripts.com

    Хостинг

    Хостинг и инфраструктура

    Споделен хостинг, KVM VPS, самостоятелни сървъри и домейни от същата компания, която изработва проекта ви.

  • blog.pnscripts.com

    Блог

    Статии и бележки

    Практични статии за разработка на софтуер, хостинг, отворен код и игри, писани от хората, които ги създават.

    Вие сте тук