2D Game Engine

Format projektu

Wersjonowany JSON projektu (formatVersion 2), walidacja, migracje, import/eksport z sumą kontrolną FNV-1a.

Po co wersjonowany format#

Projekt gry to dane, nie kod. Format JSON ma formatVersion, łańcuch migracji i walidację — dzięki temu stary projekt otworzy się w nowej wersji silnika (ADR 0002).

Struktura#

jsonproject.json
{
  "formatVersion": 2,
  "name": "MyGame",
  "game": {
    "width": 1280,
    "height": 720,
    "scaleMode": "fit",
    "backgroundColor": "#0f1419"
  },
  "assets": [
    { "id": "hero", "type": "image", "src": "/assets/hero.png" },
    { "id": "theme", "type": "audio", "src": "/assets/theme.ogg" }
  ],
  "scenes": [
    { "id": "menu", "objects": [] },
    { "id": "game", "objects": [] }
  ]
}

API#

ts
validateProject(input): ValidationResult
migrateProject(input): { project, migrated, fromVersion }
importProject(text, options?): ImportResult
exportProject(project): string

Walidacja#

ts
import { validateProject } from '@engine'

const result = validateProject(raw)
if (!result.valid) {
  for (const error of result.errors) {
    console.error(error)
  }
}

Walidacja sprawdza typy, wymagane pola, duplikaty id assetów i referencje — błędy są czytelne i wskazują konkretne pole.

Migracje#

ts
import { migrateProject } from '@engine'

const { project, migrated, fromVersion } = migrateProject(oldProject)
if (migrated) {
  console.log(`Zmigrowano z wersji ${fromVersion} do 2`)
}

Migracje są addytywne i idempotentne — projekt w aktualnej wersji przechodzi bez zmian.

Import i eksport z sumą kontrolną#

Koperta eksportu zawiera sumę FNV-1a. importProject weryfikuje ją opcjonalnie:

ts
import { exportProject, importProject } from '@engine'

const text = exportProject(project)

const result = importProject(text, { verifyChecksum: true })
if (!result.valid) throw new Error(result.errors.join('\n'))
ts
async function saveToDisk(project: Project) {
  const blob = new Blob([exportProject(project)], { type: 'application/json' })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = `${project.name}.json`
  a.click()
  URL.revokeObjectURL(url)
}

async function loadFromDisk(file: File) {
  const result = importProject(await file.text(), { verifyChecksum: true })
  if (!result.valid) throw new Error(result.errors.join('\n'))
  return result.project
}

Wskazówka

Suma kontrolna chroni przed uszkodzeniem pliku (ucięty upload, ręczna edycja), nie przed złośliwą modyfikacją — FNV-1a nie jest funkcją kryptograficzną. Do integralności bezpieczeństwa użyj podpisu po stronie serwera.

Powiązane#

  • Zapisy gry — repozytoria i backendy przechowywania.
  • Konfiguracja — jak format projektu mapuje się na EngineConfig.