DimensioneX/it/multiarea
Come far diventare il proprio gioco multi-area
Questo articolo scaturisce dall'esperienza maturata nello splitting di Underworld, e si riferisce alla versione 6.0.2a di DimensioneX.
Se avete altre versioni del software potreste incontrare delle inesattezze. Essendo però questo articolo un WIKI potete voi stessi correggerlo dove serve usando la linguetta EDIT o lasciare commenti usando la linguetta DISCUSSION.
Buona lettura.
Cosa vuol dire Multi-Area
Nel maggio del 2005 DimensioneX ha iniziato a supportare la suddivisione in aree. Ciò significa in pratica che più "mondi" (WORLDS) possono essere collegati in gruppo, detto tecnicamente CLUSTER, e in pratica divengono aree di uno stesso grande ambiente.
Questo significa diversi vantaggi, vediamo quali sono.
I vantaggi
- In primo luogo poter sviluppare un grande gioco in aree significa scalabilità, cioè: Una volta che ho due aree, farle diventare tre, quattro o dieci diventa un gioco da ragazzi. Le aree diverse condividono infatti un blocco di codice comune e, se non vogliamo faticare troppo, possono essere prodotte "in serie" con minimo sforzo.
- Le aree diverse sono indipendenti e condividono regole comuni. Questo significa che è possibile mettere tanti sviluppatori a lavorare ognuno sulla sua area senza dover impazzire a coordinare il gruppo. Certo, un minimo di coordinazione serve per le regole comuni, ma comunque ognuno può rimanere gestore della sua porzione e essere indipendente dagli altri.
- Il carico del server di gioco è ottimizzato. A causa della gestione non troppo snella che il motore DimensioneX mette in atto (ricordiamo che è scritto in Java, linguaggio che di suo non brilla per rapidità d'esecuzione) potremmo stimare che il tempo di esecuzione vari con il quadrato del numero degli oggetti. Questo significa che se dimezzo in numero degli oggetti (perchè ho spezzato il mondo in due mondi) la velocità non è un mezzo, ma un quarto (infatti un mezzo elevato al quadrato dà come risultato un quarto).
Che ci vuole
La suddivisione di un gioco in aree non è indolore, ma non è nemmeno una tragedia.
Ci vuole qualche ora di lavoro (diciamo un pomeriggio per starci larghi) per la prima bozza funzionante e poi un periodo - diciamo di una settimana - di aggiustamenti saltuari per mettere una pezza agli inevitabili imprevisti, periodo che può allungarsi tranquillamente fino a un mese come è stato per Underworld. Notare che non si parla di un mese di lavoro ma di qualche minuto per aggiustare qua e là ciò che non va, man mano che i difetti saltano fuori. Se gli utenti sono preparati potrebbe persino essere divertente.
Come si fa
Presenterò qui il metodo usato a mò di ricetta. La procedura va seguita usando buon senso e chiedendo aiuto sul forum se ci si trova nei guai.
La prima bozza
Questo si fa diciamo in un pomeriggio e alla fine avete una bozza del vs gioco multiarea già funzionante.
1. Fate un backup del vostro gioco, intendo il sorgente. Non si sa mai.
da qui in avanti supporremo che il vostro gioco sia chiamato gioco.dxw.
2. Aprite il vostro gioco.dxw, andate subito nella sezione SCRIPTS e selezionate tutti gli eventi/funzioni/sub che qui si trovano. Sposteremo poi tutto questo codice in un nuovo file che chiameremo common.DXL che creiamo subito con il nostro editor di testo nella stessa cartella in cui si trova il nostro gioco (system). Nella sezione SCRIPTS del nostro gioco inseriremo quindi una istruzione
SCRIPTS Include "common.dxl" END_SCRIPTS
Proviamo ora a rieseguire il nostro gioco. Dovrebbe funzionare come prima.
3. Sempre nel nostro gioco.dxw andiamo in cima, vicino alla tag WORLD. Lì inseriamo una tag che specifica che questa è un area di un CLUSTER:
WORLD NAME Il mio gioco CLUSTER polipo
Il nome del cluster non ha importanza, però la logica è che il NAME è il nome dell'area mentre il CLUSTER è un identificativo che rappresenta, all'interno del server, la somma risultante di tutte le aree, il gruppo delle aree insomma. Questa tag è importante perchè DimensioneX accetta di far passare persone e cose solo tra aree dello stesso CLUSTER.
4. Facciamo ora una copia del nostro gioco.dxw ottenendo così il file gioco2.dxw
Apriamo subito il file sorgente gioco2 e cambiamone il nome, senza fare troppi sforzi di fantasia:
WORLD NAME Il mio gioco2 CLUSTER polipo
Chiudiamo il file. Adesso abbiamo due mondi potenzialmente comunicanti che appartengono allo stesso cluster, gioco.dxw e gioco2.dxw. Piccolo particolare: sono uguali ma a questo penseremo dopo.
5. Configuriamo uno slot di DimensioneX perchè carichi il mondo gioco2. Se gioco.dxw è in worldnav1.properties, gioco2 può essere configurato su worldnav2.properties.
Proviamo ad eseguire gioco2, tutto dovrebbe funzionare come funzionava la prima area. Come si diceva, adesso il primo problema che dobbiamo affrontare è che i due mondi non sovrebbero essere uguali.
6. Qui dobbiamo fare una piccola pausa di riflessione e chiederci: cosa deve stare nell'area 1 e cose nell'area 2?
In Underworld ho egregiamente bypassato il problema mettendo TUTTO nell'area 1 e svuotando l'area 2 che è nata come qualcosa di completamente nuovo e aggiunto al primo mondo.
Se il vostro mondo è già troppo grande consiglio però di spezzare in due il mondo attuale.
Comunque decidiate, fate una copia di riserva a questo punto dei file gioco.dxw, gioco2.dxw e common.dxl
Spezzare il mondo
Se decidete che volete spezzare il mondo esistente in due aree, questo è semplice.
Fate chiarezza in mente su cosa deve stare nell'area 1 e cosa no (disegnare una mappa può aiutare). Cancellate ora tutti gli oggetti che apparterranno ad area 1 (stanze, personaggi), eliminandoli uno dopo l'altro dall'area 2. Poi concentratevi su quello che deve stare solo in area 2 e cancellatelo dall'area 1.
Fatto questo avete già partizionato il mondo.
In una delle due aree (supponiamo area2) è sparita la ROOM di default. Mettete l'attributo DEFAULT a una ROOM a vostra scelta così possiamo fare un giro di prova.
Infine facciamo un giro di prova accedendo prima all'area 1 e poi all'area 2.
Tutto dovrebbe funzionare ancora eccetto che ogni area ha una parte del mondo totale, complementare all'altra.
Fare una aggiunta
Se decidete che l'area 2 deve contenere solo nuovi elementi, allora eliminate tutti gli oggetti. Potete lasciare una sola stanza, che poi toglieremo o modificheremo, giusto per fare una prova.
Infine facciamo un giro di prova accedendo prima all'area 1 e poi all'area 2.
Tutto dovrebbe funzionare ancora eccetto che una area è identica al mondo iniziale, complementare all'altra che per ora contiene una sola stanza provvisoria.
Alleggerire il codice comune
Ora il problema che abbiamo è che nel file
common.dxl
nel quale dovrei avere solo le funzioni comuni trovo anche quelle specifiche delle singole aree.
Iniziamo allora a fare pulizia, andando a cercare tutti quegli eventi non generici, legati cioè a oggetti specifici, tipo:
persona.onLook
per ognuno di questi mi devo chiedere: l'oggetto a cui si riferisce (persona) in quale area sta?
Una volta che vi siete dati la risposta (supponiamo questa sia: "nell'area 2!"), spostate il codice nel file relativo (in questo caso gioco2.dxw), nella sezione SCRIPTS, sotto alla Include.
Ripetere fino a esaurimento degli eventi legati a oggetti.
Fare nuovamente un giro di prova, tutto dovrebbe funzionare ancora. Il solo problema che abbiamo adesso è che le aree sono disconnesse.
Non facciamoci illusioni diaver già finito: sicuramente troveremo funzioni apparentemente "generiche" ma che sono di interesse della sola area 2 o della sola area 1. Con il tempo, quando le riconosciamo, possiamo spostarle nell'area in cui vengono usate.
L'idea di base è quella di tenere la libreria common.dxl più piccola che si può, tenendoci dentro soltanto quello che è di utilità generale. Tutto il resto va nell'area specifica.
Il passaggio interdimensionale
Ora dobbiamo collegare le due aree. La cosa è molto semplice, e si può procedere in diversi modi. L'istruzione che ci serve è la
MoveOutside
che sposta una persona o un oggetto in un'altra area di cui specifico il nome.
Nel kit di DimensioneX c'è una demo, demoarea1 e demoarea2, che utilizzano un sistema molto semplice: ognuna di loro ha una stanza che nessun giocatore in realtà vedrà mai, poichè non appena vi si entra (evento onReceive) si viene spostati nell'altra area.
Consiglio di vedere quell'esempio e di copiarlo.
In Underworld ho voluto rendere le cose più complesse creando un passaggio che viene generato dinamicamente (è in realtà un ITEM ma il giocatore non se ne accorge).
Qui potete scegliere il sistema che ritenete più adatto al caso. Ovviamente occorre avere le idee chiare circa il punto di collegamento, ovvero come vado da 1 a 2 e/o viceversa?
Una volta implementato il collegamento fate un giro di prova.
Il punto di ingresso unico
I più attenti a questo punto hanno capito che il giocatore può, volendo, entrare direttamente da qualsiasi area. Nulla lo obbliga per ora a passare da una piuttosto che dall'altra.
Se per il vostro gioco questo è accettabile, okay.
Se non lo è (inizialmente era un mondo unico quindi aveva un solo punto di ingresso) si risolve creando nell'area da cui non si dovrebbe entrare (supponiamo area 2) una ROOM di default dalla quale si è obbligati a seguire un passaggio verso l'area 1, realizzato sempre con la MoveOutside.
Si veda Underworld, Area 2 per un esempio.
Va detto qui che se uno salva la partita, nel profilo rimane registrata l'area nel quale è entrato per cui quando faccio il login lo spostamento nell'area giusta è a totale carico del sistema.
Andiamo online
A questo punto abbiamo un mondo multi area apparentemente funzionante e saremmo tentati di mettere tutto on-line.
La suddivisione però non è ancora finita, ci attende infatti un periodo di piccoli aggiustamenti, vediamo perchè.
Le situazioni problematiche più comuni che, provando il vostro gioco, vi capiterà di dover sistemare cadono generalmente in queste due categorie:
Ogni oggetto, quando passa all'altra area, cambia ID.
E' molto probabile che nel codice tu abbia fatto affidamento sul fatto che quell'oggetto ha quel preciso ID.
If $OWNER=spadamagica ...
Tutti questi IF non funzioneranno più non appena l'oggetto va nell'altra area e torna indietro. Le armi non funzionano più, gli incantesimi non funzionano, che disastro!
soluzione: in realtà risolvere questo problema è semplice, basta usare l'attributo TYPE (tag TYPE) per memorizzare il "tipo" dei vari personaggi e degli oggetti. Questo attributo rimane inalterato e quindi l'oggetto così modificato può essere riconosciuto indipententemente dal suo ID:
If $OWNER.type="spadamagica" ...
non avevi previsto che l'oggetto potesse non esserci
Sembra banale, ma potremmo non aver previsto che un oggetto possa sparire dal gioco e riapparire più tardi. Magari questo oggetto non si può distruggere ma... se lo sposto nell'altra area è come se l'avessi temporaneamente distrutto. In Underworld io creo dinamicamente una chiave d'oro a un certo punto del gioco se questa non esiste già. Ebbene: ora che ho una seconda area come faccio a capire se la chiave non è mai stata creata o se è stata semplicemente portata altrove?
soluzione: Anche questa è semplice: invece di limitarmi a controllare se la chiave "chiaveoro" esiste, vado a controllare una variabile globale "chiavecreata" che metterò a TRUE non appena creo la famosa chiave. Se qualcuno la porta nell'area 2 mi ricorderò, leggendo la variabile "chiavecreata", che l'ho già creata e che quindi non devo farne un'altra.