Developing a Browser Game: Managing Persistent Data
After five years as a full-stack developer, I decided a few months ago to embark on a browser game project, an opportunity to deepen my understanding of web development. I share one article per week on the technical challenges I encounter, which enhance my understanding and knowledge of web development. The first topic of interest: data storage.
The Problem
As a browser game developer, data persistence is a problem that arises very quickly. A simple example is needing to keep track of the player's current score at all times and being able to update it. Working on a React application, data stored in the game state is by default erased every time the page is refreshed or the site is exited, making development impossible beyond a few minutes of gameplay.
Constraints and Web Standards
Here are the constraints I face:
- - As a player, when I perform a significant action, that action is stored in the game's memory.
- - As a player, if I refresh the page, the data remains accessible after the refresh.
- - As a player, if I exit the browser and return later, the game retains my progress.
In a typical web project, this problem is common and is often solved by adding a database and a server with an API to access it. A database allows data to be stored in a structured way, and adding a server layer protects the data by permitting only specific operations necessary for the product. Additionally, the server enables data sharing among multiple users, which is a basic feature of most websites.
In a typical web application, data is stored in a database, and a backend layer must be developed to serve this data to the frontend.
The main difficulty with this approach is the increased complexity of the project, as it requires maintaining two software components: frontend code + backend server, along with a database. A database also introduces significant rigidity in development due to the need to define the database schema (tables and columns). Revising database models often necessitates refactoring the code at multiple levels, which is very time-consuming.
Specifics of Video Game Development and Benchmarking
In the context of a video game, some constraints of a typical web application no longer apply:
- - Data sharing among players (for a single-player game, which is my case).
- - Preventing malicious users from tampering with data. If a player only has access to their own data, it doesn't matter if they break the game as long as it doesn't impact others.
An analysis of known browser games like Universal Paperclip or Cookie Clicker shows that data is stored in local storage. For Cookie Clicker, it is moderately obfuscated with Base64 encoding, while for Universal Paperclip, it is stored in plain text. Allowing players to manipulate data at will—even cheat—did not hinder the success of these two projects because security is not a priority for this type of project.
In Universal Paperclip, all data is stored unencrypted in local storage.
Chosen Solution
Given the specifics of browser game development and existing examples, using browser memory seemed the easiest solution to implement at the start of my game's development. Indeed, it offers several advantages:
- - Synchronous manipulation vs. asynchronous manipulation when using a server and an API. Synchronous code is much simpler and more maintainable than asynchronous code, which must account for data retrieval delays from the server.
- - No need to add a server layer or a database, significantly simplifying development. This also facilitates development tools such as resetting data to restart the game or saving the game's state to test key moments without replaying the entire game during each test.
Simplified version of persistent data management using local storage.
Several additional decisions helped me avoid putting all my eggs in one basket and remain flexible enough to revise this choice if needed:
- - Isolating browser memory manipulation functions in a dedicated service. If necessary, this service can be moved to backend code if unforeseen issues arise.
- - Having an extremely simple interface for manipulating saved data that is agnostic of the technology used (except for being synchronous for now to benefit from frontend memory advantages).
For the choice of frontend memory type, I naturally leaned towards local storage, which remains persistent despite “hard refreshes” or browser closures.
In practice, I made this choice two months ago, and despite increasing the data stored in local storage and the complexity of my game, I have yet to encounter any issues that challenge this solution!