Zapisy gry
ProjectRepository z atomowym zapisem, PlayerSaveRepository i sześć backendów SaveStore — od localStorage po SQL.
Dwa repozytoria#
| Repozytorium | Co zapisuje | Jak |
|---|---|---|
ProjectRepository | Projekt gry (edytor) | Atomowo: tmp → odczyt zwrotny → backup → commit |
PlayerSaveRepository | Stan gracza (sloty) | Klucz playerId + slot |
ts
import {
ProjectRepository,
PlayerSaveRepository,
LocalStorageStore,
IndexedDbStore
} from '@engine'
const projectRepo = new ProjectRepository(new IndexedDbStore({ database: 'mygame' }))
await projectRepo.save(project)
const loaded = await projectRepo.load()
const backup = await projectRepo.restoreBackup()
const saves = new PlayerSaveRepository(new LocalStorageStore('mygame:'))
await saves.save('player-1', 'slot-1', { level: 4, score: 1280, collected: ['c1', 'c3'] })
const state = await saves.load('player-1', 'slot-1')
await saves.remove('player-1', 'slot-1')
Atomowy zapis projektu#
text
tmp → odczyt zwrotny → backup → commit
Zapis najpierw trafia do pliku tymczasowego, jest odczytywany z powrotem dla weryfikacji, poprzednia wersja staje się backupem, a dopiero potem następuje commit. Przerwanie w dowolnym momencie nie zostawia uszkodzonego projektu.
Backendy SaveStore#
SaveStore to kontrakt pięciu metod: read, write, remove, keys, clear.
| Backend | Zastosowanie |
|---|---|
MemoryStore | Testy, tryb incognito |
LocalStorageStore(prefix = "b2d:") | Proste zapisy w przeglądarce |
IndexedDbStore({ database?, store? }) | Duże zapisy, projekt edytora |
FileSystemStore(baseDir) | Node / Electron |
HttpStore({ endpoint, headers? }) | Zapisy na serwerze (/v1/entries) |
SqlSaveStore(client, { dialect, table? }) | PostgreSQL / MySQL |
ts
import { MemoryStore, FileSystemStore, HttpStore, SqlSaveStore, sqlSchema } from '@engine'
const memory = new MemoryStore()
const fs = new FileSystemStore('./saves')
const http = new HttpStore({ endpoint: 'https://api.example.com/v1/entries' })
const sql = new SqlSaveStore(pgClient, { dialect: 'postgres', table: 'player_saves' })
const ddl = sqlSchema({ dialect: 'postgres', table: 'player_saves' })
Zero sterowników w bundlu
SqlSaveStore przyjmuje dowolny obiekt z query(sql, params). Dzięki wstrzykiwanemu klientowi w bundlu gry nie ma pg ani mysql2 — sterownik żyje po stronie serwera.
Przykład: system slotów zapisu#
ts
class GameScene extends Scene {
private saves = new PlayerSaveRepository(new IndexedDbStore({ database: 'mygame' }))
private playerId = 'player-1'
async saveGame(slot: string) {
await this.saves.save(this.playerId, slot, {
level: this.currentLevel,
score: this.score,
collected: this.collected,
position: { x: this.player.x, y: this.player.y },
savedAt: Date.now()
})
}
async loadGame(slot: string) {
const state = await this.saves.load<PlayerState>(this.playerId, slot)
if (!state) return false
this.currentLevel = state.level
this.score = state.score
this.collected = state.collected
this.player.setPosition(state.position.x, state.position.y)
return true
}
shutdown() {
void this.saveGame('autosave')
}
}
Przykład: zapisy serwerowe#
ts
const store = new HttpStore({
endpoint: 'https://api.example.com/v1/entries',
headers: { Authorization: `Bearer ${token}` }
})
const saves = new PlayerSaveRepository(store)
await saves.save(userId, 'cloud', playerState)
Ten sam kod działa lokalnie i w chmurze — zmienia się tylko backend.