Skip to content

Core Livelli JSON

Alessandro Bria edited this page Sep 30, 2026 · 4 revisions

LevelData e livelli JSON

I livelli usano il formato del level editor: categories contiene i nomi delle categorie e objects le regioni da convertire in oggetti di gioco. LevelData legge e valida il documento, converte le geometrie e applica le impostazioni della scena. Nei quattro prototipi LevelLoader::load() legge il documento e crea la scena; loadJson() riceve la scena e il LevelData già letto, poi crea gli oggetti specifici del gioco.

I livelli inclusi sono:

  • CustomPlatformer/levels/overworld.json
  • CustomPlatformerSlopes/levels/1-1.json
  • Box2DPlatformer/levels/level0.json
  • ActionRPG/levels/overworld.json

Tutti e quattro i prototipi caricano levels/<name>.json dalla cartella dell'eseguibile e usano lo stesso formato: scene, categories e objects. Nuovi file possono essere caricati passando il loro nome a LevelLoader::load(). Cambiano le categorie e le proprietà specifiche del gioco, non la struttura del documento.

Responsabilità e caricamento

LevelData è un modulo del core condiviso da loader ed editor. Conserva il documento come nlohmann::ordered_json, controlla il formato comune e converte le geometrie. Ogni LevelLoader resta nel proprio progetto e decide quali classi creare e come interpretare le proprietà di gioco.

file JSON -> LevelData -> LevelLoader::loadJson() -> oggetti della scena
                ^
                |
             editor (lettura e salvataggio)
Componente Responsabilità
LevelData Lettura, validazione del formato, conversione di rettangoli e polilinee, accesso ai dati della scena.
LevelLoader::load(name) Scelta del file, creazione di LevelData e della scena, chiamata a loadJson(), restituzione della Scene*.
LevelLoader::loadJson(world, level) Applicazione della configurazione, creazione delle entità secondo categoria e proprietà, collegamenti di gameplay.
Costruttori delle entità Sprite e comportamento propri della classe: per esempio Lift richiede platform e Gear richiede gear alla factory.
EditorScene e EditableObject Modifica di geometrie, categorie e nomi opzionali; conservazione delle altre proprietà durante il salvataggio.

Il core non istanzia Mario, Link o un body Box2D. Non serve aggiungere un metodo di caricamento JSON a ogni classe di gioco: l'associazione tra categoria e costruttore rimane leggibile nel loader.

API essenziale di LevelData

Metodo Uso
LevelData(path) Legge e valida il documento; segnala gli errori con il percorso del file.
json(), objects() Accedono al documento completo e alle regioni.
category(object) Risolve l'indice category nel corrispondente nome in categories.
sceneRect(), pixelUnitSize(), timeStep() Forniscono i parametri per costruire la scena. Richiedono il blocco scene.
configure(world) Registra il percorso JSON nella scena e applica view e backgroundColor, se presenti. Non ricostruisce la scena né crea oggetti.
rect(json) Converte il contenuto di un campo rect in RectF.
rotRect(object) Legge rect oppure rotRect da un oggetto e restituisce RotatedRectF.
multiline(json) Converte il contenuto del campo multiline in punti.
save(path, json) Valida il documento e lo scrive su disco.

I quattro prototipi usano questo schema in load() (ActionRPG istanzia RPGGameScene al posto di PlatformerGameScene):

Scene* LevelLoader::load(const std::string& name)
{
    std::string path = std::string(SDL_GetBasePath()) + "levels/" + name + ".json";
    LevelData level(path);
    PlatformerGameScene* world = new PlatformerGameScene(
        level.sceneRect(), level.pixelUnitSize(), level.timeStep());
    try
    {
        loadJson(world, level);
    }
    catch (...)
    {
        delete world;
        throw;
    }
    return world;
}

Anche ActionRPG descrive nel JSON la scena, le immagini di sfondo e gli spawn di NPC, Soldier e Link, oltre a collider e portali. loadJson() crea prima Link, così i portali possono osservarlo indipendentemente dall'ordine delle regioni. Se il caricamento fallisce dopo la creazione della scena, i loader eliminano la scena parziale e propagano l'errore.

Documento e geometrie

{
   "core_version": "3.7.3",
   "categories": ["Static", "Mario"],
   "scene": {
      "rect": {"x": 0, "y": -20, "width": 224, "height": 50, "yUp": false},
      "pixelUnitSize": {"x": 16, "y": 16},
      "dt": 0.01,
      "backgroundColor": [92, 148, 252, 255],
      "view": {"x": 0, "y": -12, "width": 16, "height": 15, "yUp": false}
   },
   "objects": [
      {
         "category": 0,
         "rect": {"x": 0, "y": 1, "width": 68, "height": 2, "yUp": false},
         "sprite": "terrain",
         "layer": 0
      },
      {
         "category": 1,
         "rect": {"x": 2.5625, "y": 0, "width": 1, "height": 1, "yUp": false}
      }
   ]
}

scene è obbligatorio in tutti e quattro i prototipi e contiene rect, pixelUnitSize e dt. backgroundColor (RGBA) e view sono opzionali. dt è il passo fisico in secondi: 0.01 corrisponde a 100 passi al secondo di tempo simulato, indipendentemente dal refresh del monitor. core_version è un metadato scritto dall'editor, non un selettore di formato.

Ogni oggetto ha category (indice in categories) e una sola geometria:

  • rect: x, y, width, height, yUp; posizione e dimensioni del rettangolo.
  • rotRect: cx, cy, width, height, angle, yUp; centro, dimensioni e angolo in gradi. LevelData::rotRect() converte l'angolo in radianti per il gioco.
  • multiline: array di punti {"x": ..., "y": ...}, almeno due. I segmenti di lunghezza nulla vengono saltati dai loader.

name è opzionale: normalmente basta la categoria. Si usa quando serve identificare una regione specifica, come i due portali "A" di ActionRPG, collegati dal loader tramite il nome. Gli attributi come sprite, layer e range sono proprietà autonome e non richiedono un nome. L'editor mostra l'etichetta solo se presente e omette name dal salvataggio quando è vuoto.

Oggetti e proprietà

Gioco / categoria Geometria Proprietà aggiuntive
CustomPlatformer / Static Rettangolo allineato agli assi sprite opzionale, layer (default 0)
CustomPlatformer / HammerBrother Rettangolo del personaggio layer; la posizione viene convertita nello spawn atteso dal costruttore
CustomPlatformer / Lift Rettangolo allineato agli assi vertical (default true), range (default 3), layer; il costruttore richiede lo sprite platform
CustomPlatformer / Mario Rettangolo del personaggio layer; deve essere presente esattamente un Mario
CustomPlatformerSlopes / Slope Rettangolo allineato agli assi, yUp: false risesRight (default true), color RGBA, layer (default 1); triangolo statico senza sprite
CustomPlatformerSlopes / Platform multiline con segmenti orizzontali layer; piattaforma attraversabile dai lati e dal basso
CustomPlatformerSlopes / Block, Brick, Wood Rettangolo dello spawn content: stringa vuota, coin, powerup, life o starman; layer
CustomPlatformerSlopes / Static, Pipe, Lift Rettangolo allineato agli assi, yUp: false sprite, color, layer; per Lift anche vertical e range positivo
CustomPlatformerSlopes / Mario, Goomba, Coin Rettangolo del personaggio o oggetto Posizione di spawn e layer; esattamente un Mario
Box2DPlatformer / Renderable rect o rotRect sprite opzionale, layer; decorazione senza fisica
Box2DPlatformer / Terrain multiline layer; un collider per segmento, la grafica del terreno è in backgroundImages
Box2DPlatformer / Static rect o rotRect sprite opzionale, layer
Box2DPlatformer / Gear rect o rotRect layer; il costruttore richiede lo sprite gear
Box2DPlatformer / Box rect o rotRect layer; mantiene lo sprite e l'impulso iniziale del costruttore
Box2DPlatformer / Slime rect o rotRect layer; il centro indica lo spawn
Box2DPlatformer / Player rect o rotRect layer; il centro indica lo spawn, deve essere presente esattamente un player
ActionRPG / Static, Bush rect o rotRect Collider statico, senza sprite
ActionRPG / Static, Cliff multiline Un collider statico per segmento valido
ActionRPG / Portal rect o rotRect name non vuoto; collega la coppia con lo stesso nome
ActionRPG / Clipper rect o rotRect Regione di clipping del rendering
ActionRPG / NPC rect o rotRect La posizione del rettangolo indica lo spawn
ActionRPG / Soldier rect o rotRect Posizione di spawn e patrolRect, con gli stessi campi di rect
ActionRPG / Link rect o rotRect La posizione del rettangolo indica lo spawn; deve essere presente esattamente un Link

I personaggi mantengono dimensioni e collider definiti dalle rispettive classi; il rettangolo nell'editor ne determina la posizione. CustomPlatformer supporta rettangoli allineati agli assi: rotazioni e polilinee richiederebbero un supporto fisico aggiuntivo e vengono segnalate come errori.

In CustomPlatformerSlopes le rampe sono descritte dal rettangolo che le delimita e da risesRight; il loader costruisce la classe Slope. I segmenti di Platform devono essere orizzontali. La scena e i rettangoli usano yUp: false; non sono supportate rampe mobili o ruotate. L’editor conserva risesRight, color e content, che si impostano nel JSON. Vedi Scena e risorse di CustomPlatformerSlopes.

In ActionRPG, Static e Bush creano collider StaticObject, Clipper crea una maschera di rendering e Portal crea un portale da collegare tramite name. Le polilinee vengono convertite in segmenti statici. Le categorie NPC, Soldier e Link definiscono gli spawn dei personaggi; Soldier legge anche patrolRect. Dimensioni fisiche, sprite, dialoghi e comportamento restano nelle rispettive classi. Vedi Scena e risorse di ActionRPG.

Il trigger di CustomPlatformer è un esempio hardcoded nel loader: dopo aver creato Mario e gli ascensori, viene istanziato in RectF(1, -12, 0.5f, 13) passando il giocatore e una lambda che alterna lo stato freezed degli ascensori. Trigger resta generico e riceve un task std::function<void()>, come negli altri prototipi. In CustomPlatformer il task viene eseguito all'ingresso e all'uscita dell'oggetto osservato; in Box2DPlatformer e ActionRPG soltanto all'ingresso, secondo il comportamento originale. Il trigger di esempio non è una regione del JSON e non richiede ID, azioni o riferimenti da risolvere.

Le classi specifiche scelgono lo sprite nel proprio costruttore. Il campo sprite rimane per gli oggetti generici, come StaticObject e RenderableObject, e per le immagini di sfondo.

In CustomPlatformerSlopes, Box2DPlatformer e ActionRPG le immagini fisse del mondo sono nell’array principale backgroundImages: rispettivamente lo sfondo di 1-1, il terreno del platformer e le mappe di Link. Per esempio:

{
   "rect": {"x": 0, "y": 0, "width": 96, "height": 13, "yUp": true},
   "sprite": "fg_ground",
   "layer": -1
}

Queste immagini non hanno categoria né nome; accettano rect o rotRect nei limiti geometrici del prototipo, uno sprite e un layer (default -1 in Box2DPlatformer e ActionRPG, 0 in CustomPlatformerSlopes). Restano visibili nel gioco e nell'editor, senza generare rettangoli modificabili che coprano i collider. In Box2DPlatformer il profilo fisico del terreno rimane una multiline di categoria Terrain in objects.

In Box2DPlatformer gli sfondi e gli effetti con parallasse sono nell'array principale overlays, nell'ordine di disegno:

{
   "placement": "background",
   "sprite": "bg_houses",
   "parallax": {"x": 0.2, "y": 1},
   "seamless": true
}

placement può essere background o foreground. parallax ha default (0,0) e seamless ha default false; gli sfondi ripetuti richiedono uno sprite compatibile (FilledSprite).

Modifica e salvataggio

Esc disattiva lo strumento o la selezione corrente e annulla le operazioni di trascinamento; durante la rinomina annulla il testo inserito. Non chiude l'applicazione. Ctrl+S salva; Q, in modalità normale, salva ed esce dall'editor.

Il tasto E apre il JSON effettivamente caricato dal livello. L'editor modifica geometrie, categorie ed etichette e conserva le altre proprietà degli oggetti e del documento, comprese impostazioni della scena e sfondi. Quando crea un nuovo documento, l'editor include scene ricavandolo dalla scena di gioco. Le proprietà specifiche del gioco si impostano direttamente nel JSON; non è stato aggiunto un pannello delle proprietà.

CMake copia i JSON accanto all'eseguibile a ogni build, anche senza modifiche C++. L'editor salva quella copia di esecuzione: riportare le modifiche nel file sorgente del progetto prima della build successiva, che ricopia i livelli distribuiti. Per applicare un livello modificato occorre ricaricarlo o riavviare il gioco.

File mancanti, documenti malformati, categorie fuori intervallo e geometrie non valide producono un errore esplicito. Non è previsto un formato alternativo senza scene.

Validazione e proprietà del gioco

LevelData controlla che le categorie siano un array non vuoto di stringhe, gli indici siano validi e ogni oggetto contenga una sola geometria. Verifica coordinate finite, rettangoli validi, polilinee con almeno due punti e il blocco obbligatorio scene (dt positivo, unità positive intere, colore RGBA valido).

Le regole specifiche restano nei loader: categoria supportata, sprite esistente, presenza di un solo giocatore in ciascun prototipo, intervallo positivo di un Lift, nome dei portali. backgroundImages è interpretato dai loader di CustomPlatformerSlopes, Box2DPlatformer e ActionRPG; overlays dal loader di Box2DPlatformer. LevelData conserva questi dati senza creare automaticamente oggetti grafici.

Sorgenti e pagine collegate

Per lavorare sulle geometrie, prosegui con Editor dei livelli. Per seguire la creazione delle entità, leggi le pagine scena e risorse di CustomPlatformer, CustomPlatformerSlopes, Box2DPlatformer e ActionRPG.

Clone this wiki locally