• Non ci sono risultati.

Capito 4 L'implementazione di Teatrino

N/A
N/A
Protected

Academic year: 2021

Condividi "Capito 4 L'implementazione di Teatrino"

Copied!
32
0
0

Testo completo

(1)

Capito 4

L'implementazione di Teatrino

Teatrino è un applicazione client-server, in cui i client interagiscono tra loro attraverso il Communication Server. I client si connettono al Communication Server tramite il protocollo RTMP e usano il componente AudioConference per scambiarsi in streaming il segnale audio catturato dai microfoni.

Figura 1: I client comunicano attraverso il Communication Server in esecuzione su una macchina server.

(2)

La parte client dell’applicazione è un filmato Flash che viene eseguito dal Flash Player, un plug-in supportato dai browser più diffusi. Nella figura 2, vediamo un client di Teatrplug-ino che è plug-in esecuzione su un nodo e si connette all'istanza di teatrino sulla macchina server.

Figura 2 Il filmato client in esecuzione sul nodo client è associato ad una sola istanza dell'applicazione server. L’istanza server è associata ad un solo filmato sullo stesso nodo. Su un nodo è quindi in esecuzione un solo filmato client

Per accedere a Teatrino basta quindi avere un browser che supporta il Flash Player ed una connessione ad Internet. L’accesso all’applicazione avviene visitando il sito di Teatrino.

4.1 Lato client

Il lato client è un filmato flash. Il filmato è formato da una serie di fotogrammi ognuno dei quali modella una parte diversa del sito. Quindi tutta l’applicazione client è gestita all’interno di una unica finestra del browser web. La filosofia con cui è stato progettato il client è quella della programmazione con i componenti. Affinché il codice venga al meglio raggruppato in classi, si è pensato di costruire dei componenti UI ad hoc che implementassero i casi d'uso previsti da Teatrino. Una possibilità sarebbe stata quella di inserire gli script direttamente nei vari frame del filmato. Questo avrebbe comportato disordine del codice e una conseguente mancanza di leggibilità degli script. Inoltre, anche la scalabilità del software sarebbe stata notevolmente ridotta. Ridefinire i componenti UI di Flash ci permette di strutturare, organizzare e descrivere il codice molto più facilmente.

Sono stati individuati tre componenti principali corrispondenti alle tre attività svolte dagli utenti del sito che sono: costruire la storia, scegliere i burattini e recitare la storia (cfr Capitolo 1). I componenti definiti si chiamano rispettivamente TaleEditor, CharacterEditor e TheStage. Il filmato è composto da ciascuno di questi componenti e vengono istanziati in parti diverse del filmato. Il client infatti, si sposta sul frame che contiene il componente necessario a svolgere l'attività richiesta dall'utente, ogni qual volta l'utente compie una scelta in un menù.

Durante la fase di analisi sono stati individuati dei ruoli che gli utenti avrebbero avuto durante l'utilizzo di Teatrino. Nella fase della recita, ad esempio, i ruoli previsti sono quello del coordinatore, dello spettatore e dell'attore. Mentre è necessario che ci sia un unico coordinatore, gli attori e gli spettatori possono essere più di uno. Per ogni storia salvata sul server, si immagina che

(3)

sia legata ad essa un solo utente coordinatore, un certo numero di attori, gli stessi che hanno partecipato alle creazione della storia, ed un certo numero di spettatori. A questi tre ruoli sono associati rispettivamente il DirectorStage, l'ActorStage e il TheStage. Questo ultimo componente, implementa il teatrino dei burattini attraverso un'interfaccia che permette solo di assistere alla recita e di interagire solo attraverso l'audio conferenza. Gli altri due ruoli, più specializzati rispetto a quello dello spettatore, usano i due componenti ActorStage e DirectorStage che ereditano le funzionalità del TheStage implementandone delle nuove.

Figura 3Il filmato è composto da vari componenti UI che vengono istanziati in base al caso d’uso di Teatrino

Nella figura 4 vediamo uno screenshoot dell’editor di Flash che visualizza il filmato client di Teatrino. In questa figura ogni fotogramma del filmato è suddiviso in tre livelli virtuali. Su ogni livello è possibile collocare degli oggetti grafici. I tre livelli si chiamano: Interface, AudioConference, Actions. Il primo dei tre livelli, viene usato per collocare i componenti UI. Esso è diviso in cinque parti: Login, setUp, Menù, TaleEditing e Performance.

Figura 4 Il filmato è composto da vari componenti UI che vengono istanziati in base al caso

(4)

I frame appartenenti al gruppo Login contengono il form per entrare nel sito. In realtà non sarebbe necessario che questa parte fosse lunga sei frame ma basterebbe un frame soltanto per collocare i bottoni e i textInput necessari a disegnare l’interfaccia di login. Gli altri cinque frame servono per rendere più leggibile la label Login nella timeline e non influiscono sulle dimensione del file. Non c'è alcuna altra ragione per avere questi frame in più. Questo vale per tutte le altri parti in cui è diviso il filmato. Il secondo livello fa parte anch'esso dell'interfaccia e contiene il componente AudioConference che implementa la comunicazione audio tra i client. Il componente è posizionato sul livello omonimo, a patire dal Menù. AudioConference si compone con le altre UI del primo livello. Indipendentemente dalla parte del sito in cui si trova l'utente, la conferenza audio non verrà interrotta proprio perchè nelle tre interfacce viene visualizzata la stessa istanza di audioConference che può continuare a trasmettere e ricevere l'audio senza interruzioni. Il livello

actions è suddiviso come quello interface, e contiene il codice che gestisce le varie parti del

filmato. Nel primo frame del filmato, nella parte Login, viene creata la connessione verso il server con il codice seguente:

var serverUri:String = "teatrino"; // server URL var socket_nc:NetConnection = new NetConnection();

La connessione creata nel primo frame del filmato viene usata per tutto il corso della sessione di Teatrino. L'oggetto loginClickListener, legato al bottone login_bttn attraverso il metodo addEventListener, si occupa di avviare la connessione socket_nc al CommServer attraverso il protollo RTMP.

var serverUri:String = “teatrino”;

var loginClickListener:Object = new Object(); clickListener.click = function(){

// prende la login e la password dal form TextInput username_ti // e password_ti

socket_nc.connect("rtmp:/"+serverUri, username_ti.text, password_ti.text); }

login_bttn.addEventListener("click",loginClickListener);

Si noti come il percorso a cui l’applicazione si connette, sia semplicemente il nome teatrino, ossia il nome dell’applicazione nella directory del Communication Server.

La funzione click dell'oggetto loginClickListener verrà invocata al verificarsi dell'evento omonimo. Il gestore click, invoca quindi il metodo connect che connette il filmato al server inviando anche la password e la login dell'utente. Il server risponde alla richiesta invocando sul client il metodo

onStatus, definito nell'oggetto socket_nc. socket_nc.onStatus = function(info) {

// .. controlla gli altri messaggi dal server // come NetConnection.Connect.Rejected

if (info.code == "NetConnection.Connect.Success") { gotoAndStop("SetUp");

(5)

};

Nel diagramma di sequenza della figura numero 5 viene schematizzato quanto detto finora.

Figura 5

Nel codice suddetto, l'istruzione gotoAndStop(“SetUp”) serve a muovere il filmato sul keyframe etichettato con la label SetUp (cfr figura 4). I frame di setUp servono come appoggio per definire quattro utenti (gli unici previsti per questo prototipo di Teatrino). In questa parte sono definiti anche gli array che contengono i nomi dei file delle risorse grafiche, come sfondi e burattini. Gli array sono riempiti a runtime con l'informazione contenuta in un file di setup scaricato dal server.

Nei frame Menù vengono visualizzati i tasti di scelta attraverso i quali l’utente si muove da una parte all’altra del sito e viene impostato il componente AudioConference. Questo componente si occupa di gestire lo streaming tre i client e viene istanziato nel filmato, tramite un drag’n’drop dalla libreria dei componenti del Communication Server all’interno della lavagna dell’editor di Flash. Poi attraverso una particolare finestra (quella vista nel capitolo 3 nella figura 3), l’istanza trascinata nel filmato viene associata ad un nome che in questo caso è audioConf_mc. Come si capisce, creare una comunicazione audio tra due o più filmati Flash è davvero semplice con il Communication Server.

audioConf_mc.connect(socket_nc); audioConf_mc.setUsername(currentUser); audioConf_mc.swapDepths(1000);

Nel codice riportato, il componente ha solo bisogno di essere connesso ad un applicazione sul Communication Server. Per fare ciò è sufficiente passare al metodo connect di AudioConference la connessione socket_nc creata in precedenza. La terza istruzione serve a porre il componente su un livello di profondità sufficientemente alto da consentire alle UI del sito di avere lo spazio necessario a collocare le proprie risorse grafiche. Come vedremo, i componenti UI di Teatrino sono formate da

(6)

vari livelli di profondità.

Dopo aver effettuato una scelta nel menù, il filmato si muove sul frame corrispondente alla scelta fatta, attraverso l'istruzione gotoAndStop. Ciò che si sta per descrivere da ora fino alla fine del paragrafo è comune a tutti i componenti che formano l’architettura di Teatrino. Nei paragrafi seguenti si scenderà di più nel dettaglio dei singoli componenti.

Nel caso in cui l'utente scelga di andare in scena premendo il tasto corrispondente nel menù, il filmato si sposta sul keyframe Performance. In questo frame c'è il codice che visualizza il componente corrispondente alla recita. Si riporta il codice che sul frame 28, visualizza il componente UI TheStage nel filmato – usare il nome TheStage è stata una scelta obbligata dato che esiste già il tipo Stage nel framework di Flash -. Il componente, viene istanziato attraverso l' istruzione:

if (guest) createObject("TheStage","stage",0,{_x:-225,_y:-172});

Questa chiamata, prende il componente che si chiama TheStage nella libreria del filmato e ne crea un’istanza nella variabile stage, alla profondità virtuale numero 0 e nelle posizioni _x e _y del filmato client. Ciò provoca l'invocazione, dietro le quinte, del costruttore del componente che quindi non viene invocato direttamente nel codice. Il controllo della guardia guest, prima di istanziare il componente TheStage, serve a controllare se l'utente corrente è uno spettatore. Nel caso in cui il ruolo dell'utente fosse stato quello del direttore o dell'attore sarebbero stati creati rispettivamente il DirectorStage o l' ActorStage. Ecco il codice completo contenuto in questa parte del filmato:

if (coordinator) createObject("DirectorStage","stage",0,{_x: -225,_y:-172}); if (actor) createObject("ActorStage","stage",0,{_x:-225,_y:-172});

if (guest) createObject("TheStage","stage",0,{_x:-225,_y:-172}); this.stageLoaded = function(evnt:Object){

evnt.target.SetUp(currentUser,taleName,url); evnt.target.Connect(socket_nc);

}

var this_mc:MovieClip = this;

this.performanceStopped = function(evnt:Object){

gotoAndStop("Menù"); }

stage.addEventListener("stageLoaded",this); stage.addEventListener("performanceStopped",this);

(7)

Figura 6 Il filmato client istanzia un componente UI (in questo caso TheStage) a cui viene passata la connessione socket_nc.

Quando un'istanza di TheStage viene creata, il flash Player invoca il metodo onLoad del componente, prima che il componente stesso venga visualizzato. In pratica, il metodo viene invocato dopo che il componente è stato istanziato in tutte le sue parti, con gli eventuali subcomponenti, - come bottoni ed altri componenti UI. Nel corpo del metodo onLoad, ogni componente istanziato può essere quindi aggiunto come listener di un qualche evento. Nel caso del TheStage, ad esempio, abbiamo il seguente codice:

function onLoad() {

this.addEventListener("onEnterFrame", this); dispatchEvent({type:"stageLoaded", target:this}); }

Attraverso questo metodo, l'oggetto The Stage chiede al flash Player di essere avvisato quando si verifica l'evento “onEnterFrame”, ossia il momento prima che il componente TheStage venga disegnato. Allo tempo stesso, il componente genera un evento ad hoc chiamato stageLoaded che serve a segnalare al filmato che lo sta istanziando, che esso è pronto per essere usato. L'evento generato, provoca attraverso il metodo dispatchEvent, l'invocazione del gestore di quell'evento in tutti gli oggetti che ne erano all'ascolto. Il filmato client ospita il componente TheStage sul primo frame della parte Performance, ed è in ascolto dell'evento stageLoaded, attraverso l'istruzione:

stage.addEventListener("stageLoaded",this)

dove this è il filmato client. Attraverso il gestore di questo evento il filmato client passa alcuni

parametri al componente appena creato, invocando il metodo SetUp. Allo stesso tempo il componente si connette al server tramite il metodo Connect. I parametri passati a SetUp sono il

(8)

nome dell'utente, il nome della storia e la url da cui scaricare le varie risorse grafiche necessarie durante la recitazione. L’unico parametro passato a Connect è invece la connessione al server. La storia viene conservata sul server attraverso uno SharedObject, al quale i client si connettono e ne ottengono una copia locale. La storia, in formato XML contiene tutte le informazioni risuardo lo sfondo e i burattini da caricare per ogni scena. Per questo è necessario che TheStage ne abbia un copia in locale che poi possa leggere una scena alla volta.

Nel metodo Connect, entra in gioco un nuovo elemento definito appositamente per realizzare Teatrino. Si tratta del tipo SharedObjectFactory, usato per ridefinere i metodi di un normale SharedObject di Flash in modo tale da trasformali in generatori di eventi. Nel capito 3, abbiamo visto che gli SharedObject usano il metodo onSync invocato dal CommServcr per aggiornare le copie locali dell'oggetto sui client. Questo metodo appartiene allo SharedObject stesso. Per questo motivo il metodo Connect avrebbe dovuto definire un’istanza dello SharedObject e al suo interno, definire anche il metodo onSync come mostrato nel seguente esempio:

class MyComponent{

private var localVar:Number;

public function Connect(nc:NetConnection):Void {

so = SharedObject.getRemote(“myRemoteObjectName”, nc.uri, true); so.owner = this; // salvo il riferimento all'oggetto MyComponent so.onSync = function(list:Object){ this.owner.localVar = this.data.remoteVar; } so.connect(nc); } }

In questo esempio, si suppone che il componente si connetta allo SharedObject denominato myRemoteObjectName e che abbia bisogno di essere avvisato quando la variabile remoteVar viene aggiornata da qualunque altro client. Come si vede nel frammento di codice, è necessario definire onSync all'interno dello stesso metodo Connect. Inoltre, dato che all'interno del motodo onSych, l'oggetto this è lo sharedObject so, bisogna prendere un riferimento all'istanza del componente MyComponent e salvarlo in una variabile dello SharedObject per poi potervi accedere e salvare gli aggiornamenti della variabile remoteVar. Questo meccanismo genera molta confusione nel programmatore e rende il codice illegibile. Attraverso il tipo RemoteObjectFactory è invece possibile trasformare il codice suddetto nella seguente maniera:

class MyComponent{

private var so:SharedObject; //[..]

public function Connect(nc:NetConnection):Void {

so = SharedObjectFactory.getRemote(“myRemoteObjectName”, nc.uri, true); so.addEventListener("onSync", this);

so.connect(nc); }

public function onSync(){

this.localVar = this.so.remoteVar; }

(9)

}

Data la complessità dei componenti definiti per Teatrino, questa scelta è risultata essere inevitabile.

4.1.1

Il client durante la Perfomance

Gli utenti decidono congiuntamente di recitare la storia andando nella parte del sito chiamata Performance. Ogni client si specializza in base al ruolo dell'utente corrente. Se, l'utente corrente è semplicemente uno spettatore, allora il client istanzia il componente TheStage. Altrimenti, il client istanzia il DirectorStage o l'ActorStage.

Ogni personaggio sarà rappresentato da un burattino scelto in precedenza durante la fase di creazione della storia. Nel momento in cui la recita comincia, avremo almeno un client con un'instanza del DirectorStage e almeno due client che istanziano l'ActorStage ( se ci fosse un solo ActorStage sarebbe un monologo). Tutti i componenti, come prima cosa creano una copia locale della storia conservata sul Communication Server. Ogni client che istanzia l'ActorStage, controlla un burattino. Quindi se in una scena sono presenti n personaggi ci sarà lo stesso numero n di client che al fine di comandare i burattini corrispondenti, istanziano l'ActorStage. In generale, l'ActorStage legge la stringa XML della storia per trovare la lista dei personaggi presenti nella scena corrente . Dopo di che, verifica che il nome dell'utente corrente sia associato ad uno dei burattini caricati. Quindi, se esiste un burattino controllato da quell'utente ordina al burattino di notificare gli altri client ogni qualvolta cambierà la sua posizione, la scala, o quando viene animato dall'utente. In particolare, il burattino comunica direttamente con il suo alter ego sul Communication Server, il quale a sua volta si occupa di notificare le istanze di quel burattino sugli altri client. Questo meccanismo avviene tramite uno SharedObject che analizzeremo più avanti nel corso del capitolo. I burattini che non sono controllati dall’utente corrente rimangono in attesa di ricevere messaggi dal server.

Figura 7 I componenti UI dei client e le istanze dei burattini comunicano in maniera diversa sfruttando shared object diversi.

Nella figura 7, il burattino A è animato dall'utente del client 1. Le frecce blu dal burattino A, dirette verso gli altri client, sono i messaggi di aggiornamento che vengono colti dalle rispettive istanze

(10)

dello stesso burattino. Alla stessa maniera, il burattino B controllato dal client 3 aggiorna le rispettive istanze dei client 1 e 2. Le istanze dei burattini del DirectorStage sul client 2, sono entrambe passive, in quanto come abbiamo visto, l'utente coordinatore non può agire sui personaggi. Il DirectorStage offre invece la possibilità all'utente coordinatore di far incominciare la recita facendo aprire il sipario su tutti i client. Questo avviene attraverso dei messaggi che i componenti ActorStage, TheStage e DirecotorStage si scambiano tra loro. Nella figura 8 questi messaggi scorrono sulle frecce nere.

Figura 8

Il meccanismo attraverso cui questi due tipi di messaggi che i burattini e le UI si scambiano, viene implementato è quello degli SharedObject. Le UI dei client, ossia ActorStage, TheStage e il DirectorStage, creano un’istanza dello sharedObject sul Communication Server che contiene la storia. Questo SharedObject è persistente tra le varie sessioni, in quanto esso contiene la storia. I burattini, invece usano gli sharedObject temporanei perché finita la scena in cui sono stati caricati, non hanno necessità di lasciare informazione sul server.

DirectorStage, ActorStage e TheStage usano lo SharedObject tale_so per sincronizzarsi. Le comunicazioni partono sempre dal DirectorStage perché ha il controllo della scena. Quando l'utente coordinatore preme il bottone con cui fa aprire il sipario, l'evento click viene gestito con l'invio di un messaggio a tutti i client connessi al tale_so. Questo messaggio contiene il nome di un metodo che deve essere invocato e che ovviamente sarà implementato da tutti i client. Il codice che gestisce la pressione del bottone di apertura del sipario è il seguente:

function click(event:Object) { switch (event.target) {

case startScene_bttn : // il nome del bottone che apre il sipario tale_so.send("openCurtains");

[..]

Tramite lo SharedObject tale_so, la chiamata al metodo openCurtains si verifica su tutti i client. Nella figura 9, vediamo il caso ristretto ad un solo ActorStage che interagisce con il DirectorStage.

(11)

Figura 9 ObjName è il nome della storia a cui L’ActorStage e il DirectorStage si connettono

Come abbiamo visto, il metodo Connect, crea un’istanza dell'oggetto condiviso che nella figura 9 si chiama objName, il nome della storia.

Quindi, al verificarsi dell'evento click, lo sharedObject tale_so che rappresenta il riferimento locale allo sharedObject remoto, invia la chiamata a tutti i client perché essi hanno un riferimento a quel SharedObject. La propagazione del messaggio avviene attraverso il server, il quale riceve il messaggio per primo e lo redirige su tutti i client, anche quello che lo ha inviato.

Figura 10 Il DirectorStage invia il messaggio “openCurtains” al CommServer il quale redirige il messaggio a tutti client. E’ il server che invoca in questa maniera il metodo OpenScene su tutti i client.

(12)

Quindi, anche il DirectorStage, come tutti gli altri client, riceve una chiamata remota al metodo OpenScene, malgrado sia stato esso stesso a inviare il comando. Il DirectorStage non apre direttamente il suo sipario ma invia il messaggio di apertura al Communication Server, conscio del fatto che il server risponderà invocando OpenScene anche per esso.

Il coordinatore controlla l'esecuzione della recita tramite quattro bottoni che corrispondono all'invocazione su tutti i client dei seguenti metodi:

• OpenScene, apre il sipario. • CloseScene, chiude il sipario

• ShowNextScene, carica lo sfondo e i personaggi della prossima scena trovata nella stringa XML della storia

• QuitTheShow, fa ritornare il filmato al menù

Il meccanismo con cui questi metodi vengono invocati è quello visto pocanzi. Nella figura 9 abbiamo visto quello che succede in presenza del DirectorStage e di un solo ActorStage. Nella situazione reale, sono presenti più ActorStage e più componenti TheStage. Per semplicità, nel diagramma sono stati trascurati gli altri componenti perché il meccanismo di invocazione è analogo. Nei prossimi paragrafi entreremo più nel merito dei metodi sopra citati.

4.1.1.1 TheStage

TheStage è il componente da cui ereditano il DirectorStage e l'ActorStage. Tutti i metodi che controllano la visualizzazione della storia, sono definiti in questo componente. DirectorStage e ActorStage li usano secondo i principi dell'ereditarietà.

Figura 11 Gerarchia delle UI per Teatrino. La semplice UI degli spettatori viene specializzata per gli attori e il coordinatore.

Nella figura 12 si vede l’interfaccia a forma di sipario di TheStage. Ma che cos'è in realta? E' un'interfaccia nel senso classico? La risposta è no. In flash, le interfacce sono comunque dei filmati, dei MovieClip. Il componente TheStage è un MovieClip di un solo frame, in cui sono collocate

(13)

delle risorse grafiche. Il MovieClip è gestito da un classe che si chiama TheStage. Le risorse grafiche sono, il bordo dorato – stageFrontEnd_mc –, le pareti laterali e il pavimento – room_mc – il sipario, diviso in due parti: – curtainLeft_mc e curtainsRigt_mc –; infine, lo sfondo della scena che non è però una risorsa statica ma cambia durante l'esecuzione della scena.

Figura 12 L’interfaccia di Teatrino per lo spettatore. In basso a destra si può vedere il componente AudioConference di Flash attraverso cui avvine le comunicazione audio

Nella classe, è definito un campo per ciascun elemento sopra citato. Queste risorse grafiche sono contenuti nella libreria del filmato come MovieClip e vengono sovrapposti insieme per comporre l’interfaccia desiderata. In particolare i tendoni del sipario, sono due MovieClip differenti lunghi 100 frame. Sono stati disegnati, frame per frame in modo tale che simulassero le oscillazioni delle tende. Quando il sipario si apre o si chiude, il movimento dei tendoni verso destra e sinistra è dovuto semplicemente all'aggiornamento della coordinata x dei due filmati con la frequenza di 12 fps. L’aggiornamento avviene nel metodo onEnterFrame di TheStage che viene invocato dal Flash Player 12 volte al secondo Allo stesso tempo i due filmati sono riprodotti per l'intera durata simulando l'oscillazione delle tende.

I MovieClip che compongono la scena sono disposti su diversi livelli di profondità. Il posto più in basso è riservato per lo sfondo della scena. Al di sopra di esso vi è collocato il MovieClip delle pareti interne del teatrino. Poi vengono i personaggi in numero massimo di quattro, disposti ciascuno ad una profondità diversa. Al fine di nascondere alla vista dell’utente lo sfondo e i burattini quando il sipario è chiuso, vi sono il tendone destro e sinistro, e infine il bordo dorato del teatrino.

Riassumendo, i livelli che compongono l’interfaccia sono:

 background_mc: livello 0  room_mc: livello 1  burattino: livello 2

(14)

 burattino: livello 3  burattino: livello 4  burattino: livello 5  curtainRight_mc: livello 6  curtainLeft_mc: livello 7  stageFrontEnd: livello 8

La classe TheStage, eredita da UIComponent. Come abbiamo visto nel capito 3, la nostra classe deve sovraccaricare alcuni metodi che eredita da UIComponet.

Figura 13

La figura 13, riporta a sinistra i campi della classe TheStage e a destra tutti i metodi. I metodi che incominciano con la lettera minuscola sono quelli che ereditano da UIComponent. Nel metodo init, ad esempio vengono posizionati i componenti grafici nei vari livelli impostandone anche la posizione e la dimensione:

public function init():Void { super.init();

(15)

// background viene creato dinamicamente quindi non viene considerato in questo //metodo

attachMovie("room","room_mc",1);

//depths 2,3,4,5 are reserved to the characters attachMovie("curtain_right","curtainRight_mc",6); attachMovie("curtain_Left" ,"curtainLeft_mc",7); attachMovie("stageFrontEnd" ,"stageFrontEnd_mc",8); with (curtainLeft_mc){ _x = -270; _y = 0; _xscale = 100; _yscale = 100; stop(); } //[..]

Le posizioni si riferiscono fissate affincheè la visualizzazione avvenga correttamente con una risoluzione maggiore o uguale a 1152 per 864.

Nel metodo onLoad, si dichiara che il componente deve essere notificato tramite l'evento onEnterFrame prima che venga ridisegnato:

function onLoad() {

this.addEventListener("onEnterFrame", this); dispatchEvent({type:"stageLoaded", target:this}); }

In più, il componente stesso notifica di essere pronto per essere usato generando l'evento stageLoaded. A quel punto, viene chiesto al componente di connettersi al server per ricevere la storia da mettere in atto. Il componente crea il proprio SharedObject tale_so che viene sincronizzato dal server tramite onSync. A quel punto, il component può leggere la stringa XML che contiene le informazioni riguardo la storia.

Il metodo OpenScene, fa eseguire i filmati curtainLeft_mc e curtainRight_mc e imposta un flag che fa avviare lo scorrimento delle tende verso destra e sinistra.

public function OpenScene(Void):Void { curtainRight_mc.gotoAndPlay(1); curtainLeft_mc.gotoAndPlay(1);

if ((!closing) && (!areCurtainsOpen)) opening = true; }

Nel metodo onEnterFrame, vengono aggiornate le posizioni relative alla coordinata x di curtainRight_mc e curtainLeft_mc.

Il metodo ShowNextScene, si occupa di leggere nella stringa XML della storia un nodo <scene> alla volta. Essendo la storia una sequenza di nodi scena, ad ogni invocazione, il metodo legge il

(16)

nodo successivo. Ecco un esempio di come viene rappresentata una scena in XML:

<scene label='Scena dell’incontro' id='1'>

<backimage label='Sfondo' filename='Notte' id='2'/>

<text label='Testo di Intro' id='3'> La nostra storia incomincia.. </text> <cue label='Battuta Taddi:' owner='Marco' id='5'> Ciao Pegghi come stai? </cue>

<cue label='Battuta Pegghi:' owner='Gino' id='7'> Bene Teddi,sai ,che </cue> <characters label='Personaggi' id='4'>

<character label='Teddi' owner='Marco' filename='PinguBlue' side='riht' scale='50' id='6'/>

<character label='Pegghi' owner='Gino' filename='PinguNero' side='left' scale='50' id='8'>

<action id='10' label='Stupisciti'>piegati_indietro;alza_testa;</action> </character>

</characters> </scene>

Ogni nodo scene contiene il nome del file da usare come sfondo, le battute dei personaggi e il nome dei burattini che devono essere visualizzati. Gli sfondi sono immagini JPG e i burattini sono file SWF di Flash. TheStage ha l'interesse di leggere solo il nodo backimage e i nodi character in quanto non deve mostrare il testo della storia agli spettatori. Vedremo meglio come vengono usati gli altri tag nella descrizione di DirectorStage e ActorStage. Guardiamo il codice del metodo ShowNextScene:

public function ShowNextScene(Void):Void { // dichiarazione variabili locali

var tmp:XMLNode = this.GetNextScene(); // restituisce il prossimo nodo “scene” puppetNodes = new Array();

if (tmp != null) {

currentBackground = tmp.firstChild; // il primo tag descrive lo sfondo this.createEmptyMovieClip("background_mc", 0);

background_mc.loadMovie(url+currentBackground.attributes.filename+".jpg"); characters = tmp.lastChild;

var i:Number = 0; var depth:Number = 2;

for(var item:XMLNode = characters.firstChild; item != null; item=item.nextSibling) {

puppetNodes[i] = item;

this.createEmptyMovieClip("character"+counter+"_mc", depth); // nuova variabile this["character"+counter+"_mc"].loadMovie(url+item.attributes.filename+".swf"); counter++; // contatore per creare una nuova variabile dinamica nel corso della // recita

currentNumOfPups++; // numero personaggi della scena corrente depth++; // depth sul layer

i++; //contatore per l'array puppetNodes }

}else{

// è finita la storia }

(17)

Il metodo legge il contenuto del primo figlio che è di default il nodo dello sfondo. L’ immagine dello sfondo viene scaricata dal server con l'istruzione loadMovie. Il tag che contiene la descrizione dei personaggi è di default l'ultimo. Con questa scelta è molto facile trascurare dei nodi battuta – cue – che sono presenti in numero variabile. L’oggetto XMLNode, che contiene l'albero della storia, ha delle properties per prendere il primo e l'ultimo figlio di un nodo, senza dover scorrere l’intera lista.

Anche il caricamento dei burattini avviene attraverso il metodo loadMovie. Tuttavia è necessario che il contenuto scaricato dal server venga sempre messo in una nuova variabile MovieClip. A differenza, di quanto avviene per gli sfondi, che sono semplici immagini statiche, i burattini sono dei filmati creati come componenti, e hanno bisogno di essere scaricati in una variabile sempre diversa. Quando il file SWF del burattino è stato completamente scaricato dal WebServer, il flash Player lo riproduce. Il file SWF contiene un'istanza della classe Puppet. Affinché il burattino venga visualizzato, è necessario utilizzare una variabile creata dinamicamente. Per fare ciò il metodo ShowNextScene usa la variabile statica counter per generare variabili della forma characterN_mc, dove N è un numero progressivo conservato in counter. Il tipo della variabile generata sarà MovieClip. Questo strano scenario è un lato oscuro della programmazione in ActionScript. Questa soluzione, adottata per aggirare il problema della visualizzazione dell’oggetto Puppet, è giustificata da un pattern di programmazione suggerito dalla documentazione ufficiale di Macromedia Flash, che non è tuttavia descritto chiaramente e non sottolinea la necessità di usare tale pratica in casi come questo.

Un altro problema difficile da risolvere è stato poi, accedere ai metodi del burattino, definiti nella classe Puppet. Infatti, creare per ogni burattino caricato una nuova variabile dinamica, risolve solo il problema della visualizzazione del burattino. Infatti la variabile dinamica è solo un oggetto MovieClip che contiene il burattino. Per poter catturare l'istanza vera e propria dell'oggetto è stato necessario usare un trucco: l'oggetto Puppet, è un filmato di un solo frame - eredita da MovieClip - Quando viene caricato nel TheStage, esegue una chiamata ad un metodo definito nel filmato che lo ha caricato, ossia TheStage. Questo meccanismo è possibile tramite il riferimento contenuto nella variabile globale _parent, definita per ogni filmato e che punta al filmato che lo contiene, se esiste. Il metodo definito nel TheStage e invocato dal burattino si chiama puppetLoaded:

public function puppetLoaded(target_mc:MovieClip):Void { var i:Number = 0;

var tmpNode:XMLNode = XMLNode(this.puppetNodes[0]); while (target_mc.id != tmpNode.attributes.filename){

i++;

tmpNode = XMLNode(this.puppetNodes[i]); if (i>=MAXNUMPUPPETS){ break; }

}

if (tmpNode.attributes.side =="right"){ charPosX = rightPosConst;

target_mc.Put(charPosX, charPosY); target_mc.ChangeState(-1);

}

if (tmpNode.attributes.side =="left") { target_mc.Put(charPosX, charPosY); target_mc.ChangeState(1);

}

if (tmpNode.attributes.scale != undefined) {

(18)

charScaleY = Number(tmpNode.attributes.scale); }

target_mc.Scale(charScaleX, charScaleY);

var ret = target_mc.ConnectPuppet(connection_nc,false); }

Quando il file SWF del burattino viene caricato e visualizzato, l'oggetto Puppet istanziato al suo interno, invoca PuppetLoaded passando un riferimento a se stesso come parametro al metodo. In questa maniera il parametro target_mc è un riferimento all’istanza di Puppet e TheStage ha accesso ai metodi del burattino. In questa maniera TheStage posiziona il burattino nella corretta posizione iniziale e ne cambia le dimensioni secondo i dati presi dal nodo character.

Il metodo puppetLoaded verrà invocato da ogni burattino caricato. Quindi se la scena corrente usa tre burattini, puppetLoaded verrà invocato tre volte. Per ciascuna invocazione, TheStage deve sapere quale dei tre burattini sta invocando in quel momento il metodo puppetLoaded, affinché possa posizionarlo nella posizione iniziale. Infatti niente ci assicura che il primo burattino per cui è stato invocato loadMovie, sarà anche il primo ad invocare puppetLoaded. Per questo motivo, ogni burattino deve contenere un attributo id che lo identifichi in maniera univoca. Questo id viene impostato nella fase di design del burattino e coincide con il nome del file SWF che lo contiene. Quindi l'attributo del nodo character, chiamato filename, sarà sia il nome del file che TheStage usa per scaricare il burattino dal WebServer, sia la stringa di riferimento che TheStage usa per riconoscere chi ha invocato il metodo PuppetLoaded. L'array puppetNodes è una struttura di appoggio in cui vengono salvati tutti i nodi character della scena corrente. Ad ogni invocazione di puppetLoaded, il metodo cerca all'interno dell'array il tag con l'attributo filename uguale all'id del burattino. Se l’id e l'attributo filename coincidono , allora il nodo corrente in puppetNodes è quello da cui prendere le informazioni di setup del burattino. Per ogni nodo character trovato nella scena corrente, viene inserita una copia nell'array puppetNodes e viene scaricato il burattino corrispondente dal WebServer. Ciò significa che la lunghezza dell'array puppetNodes e il numero di volte che verrà invocato puppetLoaded coincidono.

Una volta che è stato caricato, il burattino non viene direttamente controllato da TheStage, ma dall’istanza dello stesso burattino sull'ActorStage. Questa impostazione viene controllata dal secondo parametro passato al metodo ConnectPuppet. In questo caso il valore è false e significa che il burattino deve connettersi al server e leggere i messaggi di aggiornamento provenienti da un altro client.

4.1.1.2 DirectorStage

(19)

che controlla la recita. Il componente funziona alla stessa maniera del componente TheStage. Ciò in cui si differenzia da quest'ultimo, è l'interfaccia che ha dei controlli per aprire e chiudere il sipario su tutti i client. Il coordinatore segue la recita leggendo la storia in una area di testo in basso al centro, e in questo modo si accorge quando deve aprire e chiudere il sipario per ogni scena. Ecco allora che i nodi XML della storia che sono inutilizzati nel componente degli spettatori, divengono ora necessari per visualizzare l'informazione aggiuntiva al coordinatore. Il metodo ShowNextScene, che sovraccarica quello di TheStage, prende in considerazione anche i nodi text e cue, che sono rispettivamente il testo che introduce alla scena e le battute degli attori. Il contenuto dei tag vengono quindi visualizzati sul TextPad al centro dell'interfaccia (vedi figura 13).

Figura 14 In basso a sinistra, quattro bottoni permettono di aprire e chiudere il sipario, di caricare una nuova scena e di uscire dalla recita

Anche il DirectorStage carica i burattini impostandoli in maniera passiva, cioè passando il valore false al metodo ConnectPuppet. I burattini quindi ascolteranno i messaggi provenienti dalle altre istanze degli stessi, presenti sui client degli attori.

4.1.1.3 ActorStage

L'utente bambino ha un ruolo centrale nella recita, e usa il componente ActorStage per controllare i burattini in scena. La figura 14 mostra la recita durante una scena con il sipario aperto. L'utente sta controllando il personaggio blue e lo si vede dall'icona in alto a sinistra. Il bambino può leggere le battute nella finestra che riporta tutto il testo della scena corrente, nell'area in basso al centro.

(20)

Figura 15 In basso a sinistra il combobox e l'area di testo attraverso cui selezionare e/o digitare i comandi dei burattini. Al centro l'area di testo dove leggere le battute. A destra il componente AudioConference che mostra gli utenti connessi e esegue lo streaming audio.

Il componente ha anche una piccola area di testo editabile, in basso a sinistra nella figura 14, dove l'utente può digitare i comandi interpretati dal burattino. I comandi sono listati in menù a tendina e possono essere selezionati evitando di scriverli. Il menù a tendina mostra anche le procedure definite nella fase di scrittura della storia dall'utente. Le procedure sono lette dall’ActorStage nel nodo action contenuto in quello character. Il metodo ShowNextScene, di cui abbiamo già parlato, viene sovraccaricato nell’ ActorStage in modo tale da leggere anche questi nodi action.

L'ActorStage, sovraccarica anche il metodo puppetLoaded, affinché possa impostare il burattino in modo che sia controllato in locale oppure in remoto. Infatti, solo uno dei burattini presenti nella scena corrente sarà controllato dall'utente corrente. Il burattino quindi si connette al server e se il secondo parametro del metodo ConnectPuppet è impostato a true, trascurerà i messaggi provenienti dal server.

(21)

4.1.2 I burattini: la classe Puppet

I burattini, insieme agli sfondi, sono le uniche risorse caricate in un secondo momento dal client. Come abbiamo visto nel capitolo 3 è possibile caricare facilmente immagini e filmati all'interno di un MovieClip durante la sua esecuzione. Nella maggior parte delle applicazioni sviluppate con Flash, le risorse caricate a tempo di esecuzione da un filmato sono sempre immagini e filmati che hanno bisogno di essere solo visualizzati. Con Teatrino, il problema da affrontare è stato quello di caricare oggetti come i burattini e poi riuscire a controllarli dal componente che li aveva scaricati dal server. Il framework di Flash fornisce più di un metodo per scaricare un filmato e accedervi come un normale oggetto MovieClip, ma si limitano ad essere efficaci solo nei casi in cui si tratta di impostare delle caratteristiche grafiche del filmato, come la posizione, le dimensioni o la scala. Alla fine di una travagliato periodo di test si è riusciti ad ottenere il risultato voluto ma non senza trucchi e salti acrobatici. Mi accingo a spiegare come funziona la classe Puppet.

Un burattino è un componente, ossia una classe che eredita da UIComponent, associata ad un filmato. I componenti sono esportabili in forma SWC, un formato di distribuzione che costringe il programmatore a passare per l'editor di Macromedia per importare o esportare questi nuovi componenti. Il nuovo componente viene infatti creato attraverso l'editor, e esportato nel formato SWC. Chiunque lo voglia usare, deve importare nella finestra dei componenti dell'editor il nuovo file SWC. Quindi potrà usarlo nei suoi filmati, trascinandolo sulla lavagna dell'editor. Non c'è quindi alcun modo per caricare un componente dinamicamente se non lo si ha già a disposizione nella libreria del filmato. Ciò implicherebbe, nella realizzazione di Teatrino, che tutti i burattini previsti, dovrebbero essere stati già inglobati nel file di Teatrino. E' facile immaginare come questa possibilità annulli la scalabilità dell'applicazione oltre che ingigantire le dimensioni del file di Teatrino.

Una soluzione a questo problema è istanziare il componente burattino in un altro filmato. Il filmato, pubblicato come file SWF, sarà allora caricabile nell'applicazione con i comuni meccanismi previsti. Tuttavia rimane un problema: se il burattino, inteso come istanza della classe Puppet, è incapsulato in un filmato, come può l'applicazione accedervi e invocarne i metodi? La risposta è nel seguente frammento di codice:

function puppetReady(evnt:Object){

_parent.puppetLoaded(evnt.target);

}

this.createObject("Puppet","puppet",0); puppet.addEventListener("puppetReady",this);

I burattini sono definiti come componenti alla stessa maniera dell'ActorStage, DirectorStage e TheStage, visti finora. Il meccanismo per creare una istanza del burattino in un filmato è lo stesso, come si può vedere dal codice. L'unica differenza è che i burattini sono istanziati in un filmato di appoggio che a sua volta viene caricato da Teatrino a runtime. Questo permette di lasciare i burattini al di fuori dell'applicazione principale. Nella figura 16, il filmato client di Teatrino contiene già al suo interno il componente TheStage. Quindi il componente viene solo istanziato al

(22)

momento opportuno e visualizzato in un frame.

Figura 16 Il file teatrino.swf contiene TheStage nella sua libreria.

La figura 17 invece, mostra ciò che succede, nel caso dei burattini. Immaginiamo di voler fare un burattino che si chiama Pingu. Il componente è istanziato all'interno del filmato Pingu.swf. Teatrino, scarica il filmato Pingu.swf con una chiamata al metodo loadMovie. Quando il filmato Pingu è stato interamente scaricato, il flash Player lo visualizza.

Figura 17 Teatrino carica Pingu,swf che a sua volta contiene un oggetto Puppet.

Sul primo frame del filmato Pingu, si trova il codice visto pocanzi, che viene eseguito al momento della sua visualizzazione. Questo codice invoca un altro metodo che si chiama PuppetLoaded ed è definito nel filmato che lo ha caricato con il metodo loadMovie, in questo caso Teatrino. In un filamto, il riferimento al filmato che lo contiene è esprimibile attraverso la proprietà _parent del filmato contenuto. Quindi il componente Puppet notifica il filmato di appoggio Pingu attraverso l'evento puppetReady, catturato da Pingu, che a sua volta notifica il filmato che lo ha caricato, ossia Teatrino attraverso la chiamata _parent.puppetLoaded. L'istanza creata all'interno di Pingu passa come parametro un riferimento a se stesso e cioè l'oggetto evnt.target. In questa maniera Teatrino riesce ad accedere all'istanza di Puppet e ne può usare i metodi.

Sul webServer, sono conservati una serie di file SWF che, come Pingu.swf, contengono al loro interno un burattino diverso. Questi file sono i singoli burattini.

La classe Puppet, eredita da UIComponent e quindi presenta la stessa struttura dei componenti visti in questo capito. Dei metodi che ogni componente deve definire, analizzeremo solo creteChildren perché all’interno di questo metodo avviene la costruzione del burattino.

La classe definisce un membro per ciascuna parte del burattino:

public var head_mc:MovieClip; // la testa public var bust_mc:MovieClip; // il busto

public var arm1_mc:MovieClip; // il braccio destro public var arm2_mc:MovieClip; // il braccio sinistro

public var base_mc:MovieClip; // la gonnella del burattino

(23)

burattino visto frontalmente, il secondo ne ritrae il didietro, il quarto il quinto ritraggono i due profili. Dopo di che, i quattro disegni sono stati divisi in tante parti quante quelle che dovevano essere animabili. Il risultato è stato avere cinque componenti per ogni visuale del burattino, che sono state inserite come simboli nella libreria del filmato. I simboli nella libreria sono filmati della lunghezza di un frame. Sono praticamente delle immagini statiche. Fanno eccezione le braccia del burattino che invece sono disegnate su più frame in modo tale da permettere ad esse di fare movimenti più complicati che richiedono deformazioni della loro forma. Dato che queste trasformazioni non possono essere rese con la programmazione, allora si è stati costretti a disegnare frame per frame questo tipo di animazioni.

Figura 18 Il burattino ritratto nei quattro modi osservabili.

Nel metodo createChildren, tutte le parti del burattino vengono caricate dalla libreria e associate a delle variabili MovieClip corrispondenti. Ad esempio, il frammento di codice carica le parti del burattino visto nel profilo destro:

arm2SideDx_mc = createObject("arm2SideDx", "arm2SideDx_mc", 6); baseSideDx_mc = createObject("baseSideDx", "baseSideDx_mc", 7); bustSideDx_mc = createObject("bustSideDx", "bustSideDx_mc", 8); headSideDx_mc = createObject("headSideDx", "headSideDx_mc", 9); arm1SideDx_mc = createObject("arm1SideDx", "arm1SideDx_mc", 10);

Questo gruppo di codice crea il burattino visto di profilo destro. Il metodo createChildren è un metodo invocato prima del costruttore della classe Puppet, e si occupa di caricare tutte le parti del burattino. L'ultimo parametro di createObject è la profondità su cui porre il MoviClip. Le parti del burattino sono distribuite quindi a profondità diverse in modo che ad esempio il braccio sinistro sia coperto dal busto nella configurazione del profilo destro e così via. Il codice che abbiamo appena visto è replicato per i quattro punti di osservazione.

Se lo stato corrente del burattino è il profilo destro, tramite il seguente frammento di codice, verrà visualizzato il profilo destro del burattino:

if (_state == 1){

head_mc = headSideDx_mc; arm1_mc = arm1SideDx_mc; arm2_mc = arm2SideDx_mc;

(24)

base_mc = baseSideDx_mc; }

Solo le variabili a sinistra dell'assegnamento sono quelle che vengono visualizzate mentre quelle a destra sono variabili di appoggio, che servono a mantenere un riferimento ai MovieClip. In base alle variabili che in un certo istante sono state assegnate a quelle di sinistra, il burattino si mostra dall'uno o dall'altro punto di vista. Le varie parti che compongono il burattino sono disposte nelle posizioni corrette all'interno del metodo puppetReady del filmato. Vediamo ad esempio, come sono impostate le proprietà delle componenti delle burattino visto nel profilo destro.

function puppetReady(evnt:Object){

evnt.target.ChangeState(1); // imposta lo stato profilo destro // x , y,Xscaling,Yscaling in percentuale

evnt.target.SetHeadDim(15,-335,100,100); evnt.target.SetBustDim(12,-180,100,100); evnt.target.SetArm1Dim(5 ,-300,100,100); evnt.target.SetArm2Dim(5 ,-300,100,100);

evnt.target.arm1_mc.stop(); // le braccia sono MovieClip più lunghi di un frame evnt.target.arm2_mc.stop(); // quindi bisogna invocare stop

evnt.target.SetBaseDim(0 ,0 ,100,100);

// di seguito le altre parti del burattino [..] evnt.target.ChangeState(1);

evnt.target.SetUp("PinguBlue"); _parent.puppetLoaded(evnt.target); }

In questo frammento di codice, ogni singola componente del burattino viene posizionata in posizione x, y e scalata di una certa percentuale. Se le componenti del burattino sono state disegnate in maniera proporzionata, come in questo caso, non serve scalare le componenti, ma basta solo posizionarle nella giusta posizione. Inoltre, attraverso questi parametri si possono modificare le proporzioni del burattini in modo da crearne dei nuovi, senza dover ricorrere al disegno. Ciò può essere fatto in fase di design impacchettando il burattino in diversi filmati di appoggio, con parametri diversi. Questi parametri possono comunque essere cambiati anche a runtime. Quindi, sarebbe possibile che un burattino possa indirizzare un braccio e scagliarlo come un missile. Sarebbe possibile farlo ingigantire. Tutto ciò è possibile a runtime perchè si tratta di trasformazioni geometriche semplici come traslazione, rotazione o scala. Se si volesse, si potrebbe far apparire anche il braccio con un utensile ma per far ciò , ovviamente è necessario prevederne l’eventualità in fase di design e aggiungere uno o più frame al filmato del braccio con l'utensile.

Uno degli aspetti più interessanti dei burattini è la capacità di interpretare semplici comandi testuali che animano le singole parti di cui essi sono composti. Il metodo che implementa questo semplice interprete è il metodo ParseAndExec. Esso viene invocato dall'interfaccia di Teatrino ActorStage. I parametri del metodo sono la stringa che contiene i comandi digitati nell'interfaccia dell'ActorStage, e un flag che indica se eseguire i comandi in maniera sequenziale oppure parallela. Infatti, riferendosi a diverse parti del corpo, alcuni comandi possono essere eseguiti contemporaneamente. Ad esempio, si può chiedere ad un burattino di alzare la testa e allo stesso tempo di abbassare un braccio. Certo, non si può alzare e abbassare lo stesso braccio contemporaneamente! Come il fa burattino a capire quali comandi può mandare in esecuzione in un particolare istante? Analizziamo

(25)

cosa succedo quando il metodo ParseAndExec viene invocato.

La stringa che contiene le azioni digitate viene ripulita da spazi e altri caratteri di formattazione. Dopo di che viene divisa in tante parti prendendo il punto e virgola come elemento separatore. In questa maniera otteniamo un array di istruzioni di tipo Instruction che contengono un codice e dei parametri. Il codice dell'istruzione continua ad essere per semplicità il nome del comando.

Dopo questa fase di analisi sintattica, che produce un array di istruzioni chiamato code, il metodo Controller, definito nella classe Puppet, viene invocato ripetutamente ad intervalli di 500 ms. L'invocazione ad intervalli di tempo regolari di una funzione si ottiene con SetInterval, la quale imposta un timer che conta il tempo desiderato.

if (code.length != 0)

timer_controller = setInterval(Controller, 500, this, seqMode);

Se l'analisi sintattica ha prodotto del codice nell’array code, il metodo Controller legge l'array di istruzioni e per ciascuna di esse invoca un altro metodo, anch'esso richiamato ad intervalli regolari, che si occupa di effettuare l'animazione vera e propria. Questa metodo si chiama Animate e implementa le animazioni attraverso la rotazione delle singole parti che compongono il burattino. In base all'istruzione, Animate si occupa di aggiornare la rotazione di una parte del corpo del burattino ad intervalli regolari di 83 ms, creando dei movimenti rotatori molto uniformi.

Figura 19

Nella figura 19, vediamo come il metodo ParseAndExec, analizza sintatticamente il codice, ed invoca Controller il quale per ogni istruzione crea un gestore Animate. Per ogni istruzione mandata in esecuzione, esisterà un timer che richiama la funzione Animate passandogli come parametri il nome della parte del corpo da aggiornare, l'incremento∂ rdell'angolo, e l'angolo totale α raggiunto il quale l'animazione deve essere interrotta. In ogni istante possono esistere al massimo quattro timer, ossia quante le parti del corpo animabili del corpo del burattino - le due braccia, il busto e la testa - . Nel momento in cui si verifica la condizione di arresto, Animate smette di aggiornare l’angolo e cancella il timer corrispondente a quella parte del corpo tramite un numero identificativo restituito da SetInterval. L'eliminazione del timer da parte della stessa. Animate, azzera anche la variabile che contiene il numero identificativo. Queste variabili sono in numero di quattro, ognuna per ogni parte del corpo animabile. Quindi esiste una corrispondenza biunivoca tra ogni parte del corpo del burattino e la variabile che contiene il numero identificativo del timer istanziato per

(26)

animare quella parte del corpo. In base a questo, il metodo Controller può prendere la prima istruzione nell'array code e se la variabile corrispondente a quella parte del corpo che riferisce l'istruzione, è uguale a zero, allora significa che non ci sono timer impostati per essa. In caso contrario significa che esiste un timer impostato che sta aggiornando quella parte del corpo. Quindi è necessario attendere che finisca quell'animazione prima di poterne avviare un'altra che potrebbe generare una situazione di stallo. Immaginiamo infatti che vengano eseguite le istruzioni alza_testa e abbassa_testa contemporaneamente. La prima incrementerebbe l'angolo di rotazione e la seconda lo decrementerebbe. In questa maniera i due comandi non terminerebbero mai. Se esiste già un timer impostato per una parte del corpo, Controller lascia l'istruzione corrente nell'array e passa a quella successiva finché non legge tutto l'array code. Altrimenti, crea un timer e invoca Animate eliminando quell'istruzione dall'array. Quando la lunghezza dell'array code è zero, Controller azzera il suo timer e l'esecuzione della lista delle istruzione è completata.

L'esecuzione di alcune istruzioni non ha bisogno del metodo Animate, per essere eseguite, in quanto sono effettuate attraverso la riproduzione del filmato. In particolare, la rotazione dell'intero corpo da frontale al profilo ad esempio, è implementata facendo cambiare lo stato di visualizzazione del burattino che fa visualizzare le parti del burattino nella versione di profilo e rende invisibili quelle frontali. Questa operazione è implementata con un semplice assegnamento dei filmati di profilo alle variabili di visualizzazione. Le braccia possono essere ruotate, (alzate e abbassate) ma allo stesso tempo possono anche essere animate tramite l'esecuzione del filmato disegnato frame per frame. In questa implementazione, il burattino può ruotare le braccia parallelamente allo schermo – tramite la semplice rotazione delle parti - e simulare l'apertura e la chiusura delle braccia verso l'interno e l'esterno dello schermo, tramite l’animazione disegnata frame per frame.

4.1.2.1 La comunicazione tra i burattini

L’istanza di un burattino comunica con le altre istanze tramite uno SharedObject. Come accennato nel paragrafo 4.1.1.1, nella parte relativa al caricamento dei personaggi dal WebServer all’interno del filmato client, ogni istanza della classe Puppet è contenuta in un file SWF. Inoltre, il metodo PuppetReady visto a pag. 73, imposta tramite il metodo SetUp quello che abbiamo chiamato il codice identificativo del burattino. Per semplicità, abbiamo imposto che questo codice fosse uguale al nome del file SWF che lo contiene. In questa maniera, il nome del file da caricare, salvato nel tag character della storia può essere usato sia per scaricare il file SWF, sia per capire quale burattino ha invocato il metodo puppetLoaded. Bisogna chiarire che il codice identificativo è lo stesso per ogni istanza creata a partire dallo stesso file SWF: ad esempio ogni istanza del burattino Astolfo visualizzata nei client ha lo stesso codice - che sarà diverso da quello del burattino Orlando.

Questo codice ha anche un’altra importante funzione: è il nome che viene usato dal burattino per pubblicare lo SharedObject sul server. Quando il burattino Astolfo viene caricato in un client, esso si connette allo SharedObject chiamato Astolfo. Alla stessa maniera faranno tutte le istanze di Astolfo sugli altri client. Il metodo che si occupa di connettersi allo SharedObject si chiama

ConnectPuppet .

public function ConnectPuppet(_nc:NetConnection,controlled:Boolean):Boolean{ this.locallyControlled= controlled;

(27)

// add a fiels named owner that holds a reference to this class object this_so.owner = this;

this_so.addEventListener("onSync", this); this_so.addEventListener("onFirstSync", this); this_so.exec = function (msg,seqMode){

// in questo scope 'this' è lo shared object e non l’oggetto // Puppet!!!

this.owner.ParseAndExec(msg,seqMode); }

this_so.scale = function (x,y){ this.owner.Scale(x,y); }

this_so.move = function(x,y){ this.owner.Put(x,y); }

this_so.rotate = function (bodyComponent,degrees){ this.owner.Rotation(bodyComponent,degrees); }

return this_so.connect(_nc); }

Il metodo crea uno SharedObject nella maniera già vista in precedenza per i componenti UI. I burattini comunicano tra loro in due modi: il primo consiste nell’invocazione di metodi remoti, il secondo consiste nell’aggiornare un membro dello SharedObject. Il primo meccanismo viene usato per mandare in esecuzione i comandi testuali del burattino, per cambiare la scala e per ruotare le singole parti del corpo. Queste funzionalità vengono usate dall’utente premendo rispettivamente il bottone “Esegui”, le frecce “Su-Giù” della tastiera e premendo una parte del corpo con il mouse sul burattino. Quindi ogni qualvolta aggiorniamo la rotazione di una braccio del burattino o mandiamo in esecuzione dei comandi, l’ActorStage invia una richiesta di invocazione remota al Communication Server il quale redirige la richiesta ad ogni singolo client. Come abbiamo visto per i messaggi scambiati alla stessa maniera dalle UI, il server invierà lo stesso messaggio anche al client che ha richiesto l’invocazione. Perciò in questi casi l’esecuzione del comando o del click del mouse da parte dell’utente corrente avrà effetto sul burattino dopo un roundtrip dal client al server. Questi messaggi sono exec, scale, move e rotate e servono rispettivamente a chiedere in remoto l’esecuzione dei comandi testuali, un cambiamento di scala, un nuovo posizionamento del burattino e la rotazione di una parte del corpo.

Nella figura 19 si mostra il sequence diagram nel caso di due client, 1 e 2, e un burattino chiamato Astolfo del quale l’ActorStage invoca il metodo Run, per eseguire dei comandi testuali. Poniamo che il parametro script sia la stringa che contiene la sequenza dei comandi digitati dall’utente. Nel momento in cui l’utente preme il bottone Esegui in basso a sinistra dell’ActorStage, viene in vocato il metodo Run:

public function Run(script:String):Void{ this_so.send("exec",script); }

L’invocazione del metodo exec giungerà su tutti i client tramite il Communication Server. Tra i due client della figura, bisogna immaginare il Communication Server che smista il messaggio exec

(28)

ai client. In particolare la freccia dell’invocazione send dovrebbe arrivare all’istanza remota dello SharedObject sul Communication Server, il quale a sua volta invoccherebbe exec su entrambi gli SharedObject sui client. Per semplicità della figura è stato trascurato questo passaggio.

Illustrazione 19

L’aggiornamento della posizione e dello stato di visualizzazione (frontale, di lato etc.) del burattino avviene invece tramite l’aggiornamento di un membro dello sharedObject this_so. In questi due casi l’ActorStage aggiorna la proprietà dello SharedObject ma non aspetta che arrivi una notifica della modifica dal server. Ciò vuol dire che l’ActorStage aggiorna direttamente in locale il proprio burattino e al contempo aggiorna gli altri burattini in remoto. Il server notifica in ogni caso tutti i client ma il messaggio che perviene al burattino che ha modificato lo sharedObject verrà trascurato. I burattini che sono controllati dall’utente corrente, trascurano i messaggi di aggiornamento perché sono stati generati da loro stessi.

Nel codice suddetto, il senso della variabile locallyControlled è proprio questo. In base ad essa il codice di OnSync, invocato dal server, aggiorna la versione locale delle variabili dello SharedObject oppure no.

function onSync(objList:Object) { if (!locallyControlled){ this._x = this_so.data.x; this._y = this_so.data.y; ChangeState(this_so.data.stateId); } }

Quindi a differenza delle altre funzionalità del burattino, il cambiamento della posizione sullo schermo e lo stato di visualizzazione ( frontale, laterale etc) hanno un effetto immediato sul burattino in locale. Le altre funzionalità richiedono un round trip client-server affinché vengano eseguite.

(29)

4.1.3 Il client durante la costruzione della storia: TaleEditor e CharacterEditor Nella parte del sito dove si costruisce la storia hanno accesso solo gli attori e il coordinatore. I primi inventano la storia e costruiscono la trama mentre, il coordinatore ha il ruolo di assistere i bambini tramite l'audio conferenza. Entrambi i ruoli usano la stessa interfaccia utente che si chiama TaleEditor. L’istanza del TaleEditor viene creata nel frame TaleEditing del filmato client (cfr. figura 4).

Il TaleEditor ha bisogno dei seguenti parametri: il nome dell’utente corrente, il nome della storia su cui lavorare, la url del WebServer e due array di stringhe che contengono il nome dei file dei burattini e degli sfondi.

Quando l’utente corrente aggiunge un nuovo elemento alla storia, il TaleEditor usa il nome dell’utente corrente per riempire l’attributo owner del nodo XML corrispondente. Ad esempio questo tag:

<cue label='Battuta Teddi' owner='Andrea' id='5'> Ciao Pegghi come stai? </cue>

viene visualizzato come un nodo etichettato con “Battuta Teddi” nell’albero UI del TaleEditor. L’attributo owner significa che il nodo è stato creato dall’utente Andrea. TaleEditor lascerà cancellare o modificare questo nodo solo a questo utente.

Il nome della storia passato al TaleEditor serve per creare un’istanza dello SharedObject remoto che contiene la storia corrente. Lo SharedObject notifica il TaleEditor ogni volta che un client aggiunge un elemento alla storia. L’albero dell’interfaccia viene quindi aggiornato. Lo SharedObject del TaleEditor si chiama tale_so e contiene una campo per memorizzare la stringa dell’intera storia. Quando viene aggiunto un nodo all’albero, la stringa XML corrispondente viene assegnata allo SharedObject:

tale_so.data.xml = xmlTree.toString();

Lo SharedObject, quindi aggiorna tutte le copie locali dello stesso SharedObject sui client. L’oggetto xmlTree è il DataProvider del componente Tree che TaleEditor usa per visualizzare la storia nell’interfaccia.

Il parametro url è la localizzazione dei file che contengono i burattini e le immagini per gli sfondi delle scene sul WebServer. In questa parte del sito infatti gli utenti scelgono quali burattini e quali sfondi usare per la loro storia e TaleEditor permette loro di sfogliare queste risorse.

In particolare,i nodi character per i personaggi e quelli backimage per gli sfondi vengono creati in automatico dal TaleEditor. Il nodo character viene creato solo la prima volta in cui l’utente aggiunge una battuta. Per ogni scena infatti deve esistere un solo nodo che descrive il personaggio mentre possono esistere più battute per quel personaggio. Il nodo backimage, viene creato nel momento in cui l’utente aggiunge una scena. In entrambi i casi, l’attributo filename sarà lasciato vuoto e TaleEditor lo riempirà nel momento in cui l’utente sceglie la risorsa grafica che più

(30)

l’aggrada per quell’elemento della storia. Al momento di scegliere la risorsa, TaleEditor scarica su richiesta dell’utente, un nuovo file da visualizzare. I file da visualizzare sono delle immagini JPG, anche nel caso dei burattini.

I nomi dei file da scaricare sono contenuti nei due array di stringhe passate al TaleEditor, uno per gli sfondi e uno per i file dei burattini.

Quando l’utente chiede di provare le animazioni del burattino che ha scelto per una scena, tramite un apposito bottone, il TaleEditor prende il nome del file contenuto nel nodo character correntemente selezionato nell’albero, e istanzia una nuova UI di Teatrino, il CharacterEditor. Quest’ultimo, scarica il filmato corrispondete al burattino anziché una semplice immagine e lo visualizza sullo schermo. Da questo momento il burattino è pronto a ricevere i comandi digitati dall’utente.

A differenza di quanto avviene durante la recita, il burattino caricato nel CharacterEditor non crea nessuno SharedObject in quanto non ha bisogno di aggiornare alcuna istanza presente negli altri client. Il CharacterEditor invoca direttamente i metodi del burattino senza che il server ne faccia da tramite.

Attraverso questo componente, si può scegliere quali saranno le dimensioni del burattino in scena e da quale parte della scena il personaggio entrerà sul palcoscenico. Questa informazione viene scritta nel nodo personaggio che il TaleEditor ha passato al CharacterEditor durante la sua invocazione. Ad esempio, vediamo come l’utente Gino ha scelto il burattino PinguNero per impersonare il personaggio Pegghi, ha deciso che il burattino verrà fuori dal lato sinistro della scena e avrà una dimensione del 50 % rispetto a quella reale.

<character label='Pegghi' owner='Gino' filename='PinguNero' side='left' scale='50' >

La creazione di una nuova procedura per quel burattino consiste, nel digitare i comandi base che il burattino sa eseguire. Dando la possibilità all’utente di provare la procedura appena creata, l’utente può verificare in loco se la procedura produce l’effetto desiderato. In caso affermativo, la sottomissione tramite un tasto della procedura, crea un nuovo nodo che si chiama action:

<action label='Emozione'> alza_testa;alza_braccio_destro; abbassa_braccio_destro; ruota_braccio_sinistro(-100,1); abbassa_testa;ruota_braccio_sinistro(0,1); </action>

Alla chiusura del CharacterEditor, questo nodo viene restituito al TaleEditor il quale provvede ad inserirlo come figlio nel nodo character corrente e aggiornando la storia . Il risultato sarà ad esempio:

<character label='Pegghi' owner='Gino' filename='PinguNero' side='left' scale='50'> <action label='Emozione'> alza_testa;alza_braccio_destro; abbassa_braccio_destro; ruota_braccio_sinistro(-100,1); abbassa_testa;ruota_braccio_sinistro(0,1); </action> </character>

(31)

4.2 Il lato Server

Di fatto sul lato server non è stato necessario implementare alcunché. Come abbiamo visto nel capitolo 3, per sviluppare un’applicazione che fa uso del Communication Server è sufficiente creare una cartella vuota con il nome dell’applicazione. Come abbiamo visto all’inizio del capitolo, i client si connettono alla url “rtmp:/teatrino”, ossia il percorso relativo alla cartella teatrino sul server. Tuttavia, se il client fa uso dei componenti del Communication Server, come il componente AudioConference usato da Teatrino, è necessario creare un file chiamato main.acs nella cartella dell’applicazione, in cui è sufficiente copiare la seguente linea

load("components.asc");

ossia il file che gestisce i componenti del CommunicationServer. Per controllare il server durante le sessioni, si può usare la console di monitoraggio fronita da Macromedia e mostrata in figura 20.

Figura 20

Durante una sessione di Teatrino possiamo verificare il numero delle connessioni e gli SharedObject attualmente creati. Ad esempio nella figura 21, vediamo nel riquadro di sinistra, lo

(32)

SharedObject persistente favola, l’oggetto che tutti i client usano per aggiornare e leggere la stringa xml della storia L’oggetto favola viene visualizzato quando c’è almeno un client che si è connesso all’applicazione e ha creato un‘istanza in locale di questo oggetto. Nel riquadro di destra vediamo gli SharedObject temporanei.

Figura 21

PinguBlue e PinguNero sono i nomi di due burattini attualmente istanziati dai client. Questi oggetti sono temporanei in quanto servono a sincronizzare i burattini durante la sola sessione corrente di Teatrino.

L’ultimo SharedObject, FCAudioConference, serve al componete AudioConference per implementare la pubblicazione e la sottoscrizione di uno stream audio.

In una sessione con tre client attivi avremo nove stream di cui tre sono in uscita, e gli altri sei sono in entrata a coppie di due verso ogni client.

Figura 22 Gli ultimi tre stream della lista sono quelli pubblicati o in uscita dai tre clien I primi sei stream sono quelli che in entrata verso i client, due per ognuno di essi.

Figura

Figura 1:  I  client  comunicano attraverso il Communication Server in esecuzione su  una macchina server.
Figura 10 Il DirectorStage invia il messaggio “openCurtains” al CommServer il quale redirige il messaggio a   tutti  client
Figura 11 Gerarchia delle UI per Teatrino. La  semplice UI degli spettatori viene specializzata per gli attori e  il coordinatore.
Figura 12  L’interfaccia di Teatrino per lo spettatore. In basso a destra si può vedere  il componente AudioConference di Flash attraverso cui avvine le comunicazione audio
+4

Riferimenti

Documenti correlati

La stragrande maggioranza delle imprese, non solo quelle appartenenti al settore agroalimentare, hanno negli ultimi anni attuato misure volte al perseguimento

More specifically, ADS are defined as providing Air Navigation Service Providers (ANSPs), airspace users (AUs), and airports with information on the intended movement of

I componenti sono gli stessi descritti nel capitolo (Principali componenti hardware), e le loro caratteristiche vanno scelte in modo tale che verifichino i requisiti richiesti

from bagnanti Mostra il codice dell’ultimo iscritto select nome, cognome.

e.g.: search in a database, file request, purchase a book, booking a flight, … Transmit user data to the Web server. Process data on server-side, accessing a database

I programmi in esecuzione su ciascun nodo sono in grado di condividere le proprie informazioni e di richiedere l’esecuzione di altri programmi da parte di altri nodi.

L’architettura più semplice e più diffusa L architettura più semplice e più diffusa Un client invia una richiesta ad un server per l’esecuzione di un compito (task). Un task

Cliente (Client): quando l’applicazione utilizza dei servizi messi a disposizione da altre applicazioni Servente (Server): quando l’applicazione fornisce servizi usati da