DimensioneX/it/multiarea

From DimensioneX
Jump to navigation Jump to search

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 usando un evento generico:

Da:

EVENT spada5.onUse
...

lo faccio diventare:

EVENT onUse
If $OWNER.type="spadamagica"
 ...

Altro sistema. Se l'oggetto è creato dinamicamente basta allegare l'evento che lo gestisce e in questo modo non hai bisogno dell'ID:

object = NewItem( ...
AttachEvent object,"onUse","spadamagicaonUse"

qui EVENT spadamagicaonUse( ) è una procedura generica che tu alleghi all'oggetto specifico di cui non sai nemmeno l'ID. Questa procedura viene allegata a quell'oggetto, risponde quando scatta l'evento "onUse" specifico e anche se cambia area l'oggetto si "porta dietro" l'evento di gestione.

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.

Trucchi del mestiere

Ormai il più è fatto. Sistemando volta per volta gli errori avremo in breve tempo un gioco multi area funzionante.

Alcuni suggerimenti:

Dati salvati su disco

Il file dei profili (.SAV) e quello dei punteggi (.HOF) non avrà più come nome quello del gioco ma quello del cluster. Per non perdere i dati basta rinominarlo.

Gestione su spazi web separati

Le varie aree con relativi file grafici possono anche essere ospitate su spazi web diversi e gestite da vari amministratori. L'importante è che ognuno includa la stessa libreria comune common.dxl (e non copie della stessa). Per la gestione della common.dxl deve essere nominato un unico responsabile che si incarichi di avvisare gli altri quando qualcosa viene modificato. All'inizio può sembrare più comodo che ognuno abbia la sua copia ma poi i vari mondi potrebbero evolvere in direzioni diverse per cui lo sconsiglio.

Monitoraggio errori

Ogni responsabile di area deve monitorare attentamente il log errori. Se il log non è vuoto, è perchè si è verificato un errore. Andare subito a vedere il codice e correggere il problema. I primi tempi sarà necessario mettere spesso mano al codice comune e forse questo significherà dover lavorare anche sulle singole aree. Col tempo la cosa si normalizza e pian piano ognuno può andare avanti creando e disfacendo nella sua area senza paura di dar fastidio agli altri.


Condivisione Eventi

Ogni area è un mondo a sé stante e quindi anche gli eventi

onStart()

onTick()

Living()

sono separati, anche se - a logica - dovrebbero contenere codice uguale o comunque molto simile.

E' da evitare assolutamente il copia-incolla dello stesso codice in tutte le aree. Ove possibile è meglio condividere la maggior parte del codice e di mettere in ogni area delle estensioni locali.

La soluzione ottimale può essere scelta tra i seguenti due approcci:

1 Dall'evento locale chiamo quello condiviso.

In ogni area ho un evento onStart locale, che però richiama un evento condiviso comune chiamato onStart_Common che contiene il codice di inizializzazione comune a tutte le aree. Per esempio, se tutte le aree devono avere le stesse musciche di sottofondo, potrei inizializzare il set delle musiche qui dentro. La onStart_Common, come tutte le cose comuni, starà dentro la common.dxl mentre gli onStart locali stanno nei file di area gioco1 e gioco2.

2 Dall'evento condiviso chiamo quello locale.

Gli eventi onTick e onLiving li metto nel file common.dxl e sono comuni a tutte le aree. Poi, all'interno del codice di questi eventi, si va a controllare se esista una estensione (opzionale) locale, specifica per l'area. Questa la chiamerò ad esempio onTick_Local() e questo codice starà nel file dell'area gioco1/gioco2. Ecco come potrebbe essere il codice della onTick comune:

EVENT onTick()
	'...
	'... fai qualcosa
	'...
	If ExistScript("onTick_Local")
	    ' Evidentemente è stata definita una estensione locale!
	    Call onTick_Local()
	    ' questa esegue operazioni specifiche per l'area locale
	End_If
End_EVENT

Sezione GUI deve essere condivisa

Tutte le aree DEVONO avere la stessa sezione GUI. Presto in DimensioneX si potrà usare una Include per includere questa sezione in modo da evitare che i vari sviluppatori debbano tenersi in contatto per questo. Se uno in un'area aggiunge comandi o panel che non esistono in altre aree, quando il giocatore passa altrove il nuovo pannello o il nuovo comando non funzionerà. Infine il codice di gestione dei comandi comuni va ovviamente nella libreria comune.

Come aggiungere una nuova area

A situazione normalizzata creare un'area addizionale diventa facilissimo. Basta prendere una delle aree, cambiargli nome e modificarla per ottenere una nuova area aggiuntiva funzionante.

In pratica: una volta superato l'ostacolo inziale, aggiungere aree è pressoché immediato e quindi il progetto potrà allargarsi a macchia d'olio con i contributi di sviluppatori anche poco esperti.

Torna a Italy.gif DimensioneX WIKI Italy