Custom storage#
A Storage implementation reads and writes serialized save text by key. Visual Script includes player-preference and persistent-file implementations; a custom subclass adds another backend to Save settings.
The interface#
Derive from StorageBase and implement reading, writing, existence, and deletion by key. This outline omits the backend operations:
[Title("My Cloud Storage")]
[Serializable]
public class StorageMyCloud : StorageBase
{
public override string Get(string key) { /* … */ }
public override void Set(string key, string data) { /* … */ }
public override bool Exists(string key) { /* … */ }
public override void Delete(string key) { /* … */ }
}Get returns the stored text or null when no valid entry exists. Keys are opaque strings produced by the save system, so treat them as identifiers rather than parsing their contents.
When to write one#
Use custom storage when the save destination has requirements the built-in implementations don't cover:
| Reason | Use when |
|---|---|
| Cloud saves | Steam, console platforms, a service of your own. |
| A specific directory | Alongside the executable, or in a location a launcher expects. |
| Platform requirements | Consoles typically mandate a platform save API. |
| Multiple destinations | Write locally and to the cloud, read from whichever is newer. |
Handle storage failures#
A file backend can fail when the disk is full, the path is read-only, or a file is locked. A network backend also needs to handle disconnections, timeouts, rate limits, and expired authentication.
Never let a storage failure throw into the save system
An exception escaping Set aborts the save. Handle failure inside the storage and report it through the project's own status path.
A failed Get should return null so the caller treats the entry as missing or invalid.
For a networked backend, keep a local copy and report upload status separately:
- Write locally first, so the game retains a copy if the upload fails.
- Attempt the remote write.
- On failure, log it and queue a retry.
- Report status through your own event so the UI can show saving… or offline.
Storage calls are synchronous#
Every StorageBase method completes synchronously on the calling thread. A blocking network request therefore freezes the game during save or load.
For cloud saves, synchronize remote data into a local cache before calling Load. Let Set update that cache immediately, then queue the remote upload through a separate asynchronous service.
Testing#
Test the backend's persistence and failure paths:
- First save on a fresh install, where nothing exists.
- Save, quit the process entirely, relaunch, and load.
- Delete, then check that
Existsreportsfalse. - Corrupt the stored data by hand and confirm loading rejects it without preventing launch.
- For networked synchronization, test offline, mid-write disconnection, and slow connections.
A truncated file simulates an interrupted write. The game must still start when the storage contains one.
Write atomically
Where the backend allows it, write to a temporary location and move it into place on success. A move is atomic on most filesystems, so a crash mid-write leaves the previous save intact rather than a half-written one.
Where to go next#
- Custom serializers — control the format written to storage.
- Custom encryption — transform the text before storage.
- Save settings — select a storage implementation.