MokaByte
Numero 33 - Settembre 99
|
|||
|
|
|
|
|
Giovanni Lagorio |
Virtual Pub è un gioco di simulazione basato su architettura Client Server. Analiziamo la sua struttura e la sua evoluzione per capire qualcosa di più su Java | |
“Ti trovi all'interno di un dirigibile ipertecnologico del 25° secolo in orbita attorno alla luna. Stai partendo per una vacanza verso il posto più esotico del sistema solare `DreamLand'…” Inizia così la storia di Virtual Pub (http://www.virtualpub.net), un’avventura multiutente completamente in italiano nata nel 1996 e, da allora, in continua crescita. Non è semplice definire Virtual Pub; sicuramente è un videogioco ma, a differenza delle avventure tradizionali, è anche un ambiente virtuale che rende possibile conoscere altre persone ed interagire con loro. Infine, ma certamente non meno importante per noi sviluppatori, è un programma Java. Quest’articolo si concentrerà proprio su quest’ultimo punto di vista discutendo le problematiche che si sono dovute affrontare nello sviluppo del gioco e analizzando le soluzioni adottate. L’articolo non si prefigge di esporre lo stato dell’arte dello sviluppo di videogame, ma piuttosto di inquadrare in una prospettiva realistica vantaggi e svantaggi della scelta di utilizzare Java per lo sviluppo di questo genere di applicazioni. |
Virtual Pub,
d’ora in poi semplicemente “Pub”, è un sistema client/server. Il
server si occupa della gestione degli utenti e di tutto il mondo virtuale
nel quale si svolge il gioco. Il programma lanciato dall’utente per “entrare”
nel pub è invece il client, scritto in Java, che si collega al server
sia per ottenere informazioni (descrizione degli ambienti, lista degli
utenti collegati in un dato momento, etc) sia per interagire con gli altri
utenti (parlare, rubare oggetti e così via). Ad esempio, quando
due persone dialogano tra loro all’interno del gioco i rispettivi client
non si scambiano direttamente i messaggi, ma lo fanno attraverso il server.
Quello che succede, dietro le quinte, è che il client del primo
utente manda un messaggio al server che, a sua volta, lo rispedisce al
client del secondo.
Il server, sviluppato in linguaggio C, gira su una macchina Linux (Intel). La scelta del linguaggio C è stata conservativa: l’altro candidato naturale era il C++ ma tre anni fa i compilatori free erano decisamente immaturi (ricordiamo che, in fondo, il C++ è stato standardizzato solo da pochi mesi). In retrospettiva credo sia stata fatta la scelta giusta, anche se oggi io sceglierei il C++ senza riserve . Java, ai tempi, non è stato neppure preso in considerazione perché era ancora un perfetto sconosciuto per tutti i membri del team di sviluppo. Questa è stata una grossa fortuna perché la stabilità del server è una caratteristica irrinunciabile in questo tipo di applicazioni e le JVM (1.1.x) hanno iniziato ad essere usabili solo da pochi mesi e sono tuttora piene di problemi. Forse Java 1.2 potrebbe essere preso in seria considerazione nello sviluppo di un server, ma non ho ancora acquisito sufficiente familiarità con questo strumento per esprimere un giudizio. Inizialmente anche il client era scritto in C e la sua interfaccia era limitata alla modalità solo testo. La versione Java, nata agli inizi del 1998, è passata attraverso varie operazioni di “lifting” fino ad arrivare alla versione attuale che potete vedere negli screen-shots delle figure [virtual1.jpg e virtual2.jpg]. ComunicazioniFar comunicare due applicazioni Java è piuttosto semplice: è sufficiente aprire un socket e, grazie alla serializzazione (introdotta con la versione 1.1 del JDK), passare da una parte all’altra strutture arbitrariamente complesse. Ho deliberatamente omesso i dettagli; nel mondo reale le cose non sono così facili… ma quasi.I socket (classe Socket) forniscono un canale bidirezionale di comunicazione fra un client e un server. In Java questo si traduce nell’avere a disposizione un OutputStream e un InputStream nei quali è possibile, rispettivamente, spedire e ricevere bytes in modo affidabile (è compito del protocollo TCP garantire che i dati arrivino integri e nella corretta sequenza). Il passo successivo è costruire un OutputObjectStream e un InputObjectStream sui precedenti stream ed il gioco è fatto: salvo esigenze particolari i metodi readObject e writeObject penseranno a tutti i dettagli di basso livello. Far comunicare un’applicazione Java e un’applicazione non-Java, in questo caso un programma C, è un pochino più macchinoso, anche se concettualmente non molto diverso. Stabilire una connessione è facile: l’idea di socket è indipendente dal linguaggio di programmazione; i problemi nascono quando si devono passare i dati da una parte all’altra. Un modo è quello di serializzare “a mano” gli oggetti che si intendono trasmettere. Serializzare un oggetto significa ottenere una sequenza di bytes che permetta, successivamente, di costruire un oggetto equivalente all’originale. Una sequenza di bytes, ad esempio sotto forma di array, può essere scritta senza problemi su un qualsiasi stream e, in particolare, può essere trasmessa attraverso un socket. Creare un array di bytes da trasmettere è un’operazione semplice ma scomoda in quanto gli array hanno una dimensione fissa decisa al momento della loro creazione mentre la dimensione in bytes di un oggetto serializzato dipende quasi sempre dal suo stato e, di conseguenza, cambia continuamente al runtime. Per questo facilitare la creazione degli array sono state scritte due classi: ByteArray e ByteArrayFIterator. La prima, come suggerisce il nome, rappresenta un array che cresce dinamicamente man mano che i bytes vengono aggiunti; la seconda è un iteratore (la “F” sta per forward) che permette di leggere o scrivere il contenuto dell’array (incrementandone le dimensioni automaticamente, non si “esce” mai dall’array scrivendoci dentro). Consideriamo il seguente frammento di codice: ByteArray ba = new ByteArray() ;
bytes = {8 0 0 0 77 111 107 97 98 121 116 101 1 0 0 0 33 }
I primi 4 bytes (cioè un
intero serializzato) contengono la lunghezza della stringa “Mokabyte”,
i successivi 8 bytes contengono invece i caratteri veri e propri. I bytes
rimanenti contengono la forma serializzata della stringa “!”.
La classe Packet contiene un ByteArray
che rappresenta la forma serializzata dell’oggetto e un campo intero che
ne identifica il tipo. Il tipo codifica la classe dalla quale istanziare
l’oggetto che deve essere deserializzato. La classe Packet ha due discendenti
diretti: IPacket e la simmetrica OPacket. La prima è classe base
di tutti i pacchetti di input (che viaggiano dal server verso il client)
e la seconda di quelli di output (trasmessi in direzione opposta).
Interfaccia graficaQuando il client del Pub è stato portato in Java si voleva non solo ottenere un programma in grado di girare su sistemi diversi, ma un prodotto la cui interfaccia fosse consistente su tutte piattaforme.L’AWT, l’unica scelta al momento del porting, fornisce le funzionalità interfaccia utente indipendenti dal sistema ospitante attraverso l'uso di classi peers native. Il motivo di tale scelta è permettere il porting di AWT non solo su diversi sistemi operativi a finestre, ma anche su dispositivi hardware diversi dal “tradizionale” PC; ad esempio si può pensare di "mappare" un pulsante fisico di un telefono su un pulsante (Button) di AWT. Il prezzo di tutta questa indipendenza è che un pulsante sotto Windows, pur funzionando come uno sotto Linux, ha delle dimensioni ed un aspetto diversi. Non solo: i widget che AWT mette a disposizione sono molto limitati: è, ad esempio, impossibile cambiare il colore di un singolo item di una listbox. Questi motivi hanno portato allo sviluppo di una gerarchia di componenti che offrissero caratteristiche più avanzate (ad esempio menù popup, tooltip) e avessero un aspetto identico su ogni sistema. Tutto questo è stato ottenuto usando come unico componente AWT un Canvas per ogni finestra o dialogo (questi ultimi sono normali Frame e Dialog AWT). Tutti i componenti usati nel Pub (pulsanti, textbox, listbox e così via) vengono disegnati “a mano”. La libreria di componenti permette inoltre l’uso di pulsanti di forma qualsiasi grazie all’uso di maschere di trasparenza, si veda ad esempio la rosa dei venti della figura:
essa mostra,
per il pulsante “nord”, la maschera di trasparenza, il pulsante premuto
ed, infine, il pulsante disabilitato.
SuoniOgni videogioco che si rispetti non può fare a meno di qualche effetto sonoro, il Pub non è un’eccezione. Purtroppo il supporto sonoro offerto da Java 1.1.x è veramente scadente. La cosa più scandalosa è che non esiste un metodo standard, documentato, per riprodurre un suono da un’applicazione! Inoltre agli applet, gli unici fortunati fruitori di AudioClip, sono permesse solamente tre operazioni: play, stop e loop. Non è nemmeno possibile sapere se e quando la riproduzione di un suono è terminata. Ciliegina sulla torta: l’unico formato supportato è un orrendo AU mono a 8 kHz.Per il primo problema, quello che solo gli applet possono caricare degli audioclip, esiste almeno una soluzione che consiste nell’usare una classe non documentata (sun.applet.AppletAudioClip), per tutti gli altri problemi l’unica soluzione nota è quella di rassegnarsi. In realtà esistono due altre potenziali soluzioni, entrambe impraticabili, almeno per ora. La prima è quella di passare all’uso di Java 1.2, dove finalmente anche le applicazioni possono utilizzare i suoni e sono supportati molti più formati audio (purtroppo l’interfaccia è rimasta AudioClip e quindi piuttosto povera di funzionalità). Questa strada non risulta percorribile, per il Pub, poiché il runtime di Java 1.2 richiede una configurazione minima (reale) di almeno 64 mega per ottenere prestazioni accettabili che riteniamo sia davvero eccessiva. Una seconda possibilità sarebbe quella di utilizzare il Java Media Framework, un framework aggiuntivo che permette la riproduzione di molti formati audio/video. Purtroppo la versione 1.x nativa per Windows era molto instabile, quella puro-Java troppo lenta. È in beta la nuova versione… speriamo che sia meglio della precedente (non ci vorrebbe molto!). Nel frattempo, il Pub (dalla prossima versione per Windows) utilizzerà i suoni in formato MP3 grazie ad una libreria nativa invocata attraverso l’uso di JNI. ConclusioniJava è uno strumento estremamente potente e flessibile che permetterebbe di realizzare applicazioni piuttosto complesse con relativa facilità. Purtroppo la situazione attuale è piuttosto critica: le JVM 1.1.x hanno gravi problemi (si veda la bug parade sul sito Sun) e la versione 1.2.x richiede una quantità di risorse tali da esser proponibile solo su macchine molto potenti, tipicamente adibite al ruolo di server. Il futuro di Java sul lato client sarà tutt’altro che roseo se Sun non la smetterà di ignorare le richieste degli sviluppatori che non chiedono altro che una piattaforma stabile.Si veda, ad esempio, http://developer.java.sun.com/developer/bugParade/bugs/4014323.html un bug segnalato il 12 novembre 1996 e non ancora “fixato”! Purtroppo non
si tratta di una mosca bianca; si veda, in generale, http://developer.java.sun.com/developer/bugParade/index.html
|
Giovanni
Lagorio, laureando in Scienze dell’Informazione
presso l’Università di Genova, è uno specialista in progettazione
e sviluppo con tecnologie object oriented.
Ha sviluppato progetti in diversi linguaggi, principalmente C/C++ e Java, fra cui Virtual Pub. È raggiungibile tramite posta elettronica all’indirizzo gio@libertyline.com |
|
||
|
||
MokaByte ricerca
nuovi collaboratori
|
||
|