Het sluiten van een browsertabblad zou niet vier uur aan voortgang mogen wissen. Dat lijkt vanzelfsprekend, maar veel browsergames behandelen local storage als een bijzaak. Een speler ontgrendelt een high score, past de instellingen aan, komt de volgende dag terug en vindt niets. Erger nog: ze komen terug na een patch en de game geeft een foutmelding omdat het savefile op hun machine niet meer overeenkomt met de code die je zojuist hebt uitgebracht. Het bouwen van een survivor-style shooter in Phaser 4 betekent dat je te maken krijgt met constante golven vijanden, maar de echte langetermijnbedreiging zijn je eigen toekomstige updates.

De meeste ontwikkelaars bouwen hun eerste save-systeem door een object te pakken, dit door JSON.stringify te halen en het in localStorage te dumpen. Bij het laden parsen ze het en geven het onbewerkt terug aan de game. Dat werkt op de eerste dag. Maar het gaat kapot op het moment dat je een nieuwe instelling, een nieuwe unlock-flag of een derde laag geneste configuratie toevoegt. Als een terugkerende speler een oud savefile heeft dat de vignette-property mist, en je nieuwe code verwacht dat deze bestaat, krijg je undefined waar je een boolean verwachtte. Vermenigvuldig dat met een dozijn nieuwe functies en je hebt een debugging-nachtmerrie die eerst je meest loyale spelers raakt.

Begin met een contract, niet met een rauw object

Voordat je ook maar iets met localStorage doet, moet je een standaard save-schema definiëren in je codebase. Zie het als een contract waar elk savefile aan moet voldoen, of het nu vijf minuten of vijf maanden geleden is gemaakt. Een duidelijk startpunt zou er zo uit kunnen zien:

const defaultSave = {
  highScore: 0,
  settings: {
    screenShake: true,
    vignette: true
  }
};

Dit object leeft in je broncode. Wanneer de game opstart, heb je deze vorm altijd beschikbaar. Het geeft je een basislijn. Het dwingt je ook om over de structuur na te denken voordat je iets serialiseert. Als je deze stap overslaat en gewoon het state-object opslaat dat op dat moment handig is, eindig je met inconsistente keys, ontbrekende velden en stille fouten wanneer oudere saves uit de pas lopen met je verwachtingen.

Defensief laden met Try/Catch

Local storage is geen database. Het is een 'string-kastje' in de browser, en alles kan erin terechtkomen. De gebruiker kan handmatig een waarde hebben bewerkt, een halfgeschreven schrijfactie kan zijn onderbroken, of een browser-extensie heeft rommel in de key gedumpt die jij gebruikt. Wanneer je die string weer ophaalt en aan JSON.parse voert, zorgt één enkel gecorrumpeerd karakter voor een harde exception. In een Phaser-game kan die niet-afgevangen fout je opstartsequentie bevriezen of de speler terugwerpen naar een leeg scherm.

Wikkel je lees- en parse-logica altijd in een try/catch-blok. Bij een fout val je terug op je standaard schema. Het doel is simpel: als het savefile onleesbaar is, behandel de speler dan als een nieuwe gebruiker in plaats van de hele sessie te laten crashen. Deze ene gewoonte onderscheidt hobbyprojecten van builds van professionele kwaliteit. Het kost bijna niets om te implementeren en het bespaart je mysterieuze bugrapporten die onmogelijk te reproduceren zijn.

Combineer oude data met de standaardwaarden

Een succesvolle parse betekent niet dat je veilig bent. Vervang je standaard object nooit volledig door het geparseerde resultaat. Dat oude savefile bevat misschien niet je nieuwste instellingen. Het slaat misschien screenShake op, maar niet vignette. Als je gamelogica ervan uitgaat dat vignette bestaat omdat het met de laatste update is meegeleverd, ben je weer bezig met het opsporen van undefined-fouten.

Combineer in plaats daarvan de geladen data met je standaardwaarden. Gebruik Object.assign om de opgeslagen waarden over het basis-schema heen te leggen. De standaardwaarden vullen automatisch elk ontbrekend gat op. Nieuwe properties die je in versie twee hebt toegevoegd, krijgen hun beginwaarden uit het standaard object. Bestaande properties die de speler daadwerkelijk heeft gewijzigd, worden overschreven met hun opgeslagen voorkeuren. Iedereen wint. De terugkerende speler behoudt zijn high score, en de game krijgt toegang tot de nieuwe toggle die je gisteren hebt toegevoegd zonder dat de boel ontploft.

Houd er rekening mee dat Object.assign een shallow merge uitvoert. Als je instellingen-object in de loop der tijd diep genest raakt, moet je die interne objecten misschien met iets meer zorg behandelen. Toch blijft het principe hetzelfde: de data van de speler moet je standaardwaarden aanvullen, niet direct vervangen.

Versioneer je keys

Browsers verwijderen oude local storage-items niet automatisch. Als je je datastructuur drastisch verandert, heb je een nette manier nodig om het oude formaat achter te laten. Geef je storage key een versie-achtervoegsel. bitSurvivorsSave_v1 is expliciet. Het vertelt je precies welk schema dat bestand heeft geschreven. Wanneer je later de progressie een overhaul geeft of een volledig inventaris-systeem toevoegt, ga je over naar bitSurvivorsSave_v2.

Dit biedt je twee praktische voordelen. Ten eerste parse je nooit per ongeluk een v1-blob met v2-logica. Ten tweede kun je migratiecode schrijven als je dat wilt. Controleer bij het opstarten op v1. Als deze bestaat en v2 niet, migreer dan de oude gegevens naar de nieuwe structuur, schrijf ze naar de nieuwe key en ga verder. Als je niet wilt migreren, blijft de oude key in ieder geval onschadelijk in de opslag staan terwijl je nieuwe code deze negeert. Hoe dan ook, versiebeheer voorkomt stille corruptie.

Maak opslaan onzichtbaar

Persistentie moet aanvoelen als ademhalen. De speler zou er nooit over na te hoeven denken. Voeg geen 'Apply'-knop toe in je instellingenmenu. 'Apply'-knoppen creëren frictie en trainen gebruikers om zich zorgen te maken of hun keuzes wel echt zijn doorgevoerd. Ze nodigen ook uit tot gegevensverlies wanneer een speler drie opties aanpast, de 'Apply'-knop vergeet te klikken en het tabblad sluit.

Sla op het moment dat de interactie plaatsvindt op. Wanneer de speler een selectievakje aanvinkt om schermschudden (screen shake) uit te schakelen, roep dan onmiddellijk je write-functie aan. Wanneer de run eindigt en de uiteindelijke score wordt berekend, schrijf dan de nieuwe high score voordat het game-over-scherm klaar is met animeren. Event-gestuurd opslaan houdt je architectuur voorspelbaar, omdat de opslag altijd direct naast de actie staat die de gegevens heeft gewijzigd. Je hoeft nooit op zoek naar een centrale batching-functie of je zorgen te maken over een verouderde status.

Deze aanpak vereenvoudigt ook je mentale model. Je weet precies waar de persistentie plaatsvindt: in de callback die de toggle afhandelt, en in de functie die de dood afhandelt. Er zijn geen mysterieuze 'writes' verspreid over de codebase.

Bouw een resetknop voor jezelf

Tijdens de ontwikkeling zul je je eigen saves corrumperen. Je zult foutieve gegevens schrijven, edge cases testen en snel terug moeten keren naar een schone staat. Bouw een resetknop in een debugmenu of een verborgen toetscombinatie. Laat die resetknop twee dingen doen in deze exacte volgorde: reset je in-memory status naar het standaard schema, en roep vervolgens onmiddellijk dezelfde save-functie aan die naar de local storage schrijft.

Als je alleen de lokale variabele wist en de write-stap overslaat, heb je niets bereikt. Bij de volgende paginavernieuwing wordt de oude data weer uit de browser getrokken en tot leven gewekt. Een reset die vergeet de gegevens op te slaan, is het soort bug dat een hele middag verspilt. Als je de volgorde eenmaal goed hebt, blijft je testcyclus snel voor de rest van het project.

De belangrijkste les

Opslaan is geen functie die je aan het einde even toevoegt. Het is infrastructuur die bepaalt of je spel duurzaam aanvoelt en respectvol omgaat met de tijd van de speler. Een Phaser 4 survivor shooter leeft of sterft bij herhaalde runs. Als het browsertabblad een geladen wapen is dat op de voortgang van de speler is gericht, zullen ze uiteindelijk niet meer terugkomen. Schrijf een schema, verdedig je tegen slechte data, merge in plaats van te vervangen, versieer je keys en sla op bij elk betekenisvol event. Je toekomstige zelf, en elke speler die terugkeert na je volgende update, zal je dankbaar zijn.