Sei sulla pagina 1di 106

Corso di Laurea in Informatica

Realizzazione e sviluppo di
un mini-gioco per la riabilitazione
della mano

RELATORE
Prof. Nunzio Alberto BORGHESE

TESI DI LAUREA DI
Jacopo Essenziale

CORRELATORE

Matr. 740006

Dott. Renato MAINETTI

Anno Accademico 2013/2014

INDICE
1. INTRODUZIONE
2. RIABILITAZIONE
a. RIABILITAZIONE A CASA: PROGETTTO REWIRE
b. IGER: UN GAME ENGINE PER LA RIABILITAZIONE
3. RIABILITAZIONE DELLA MANO
a. STRUMENTI DIGITALI GIA UTILIZZATI NELLA RIABILITAZIONE DELLA MANO
b. OBIETTIVO DEL PROGETTO REALIZZATO
4. STRUMENTI SOFTWARE UTILIZZATI
5. PANDA 3D
a. FUNZIONAMENTO LOOP DI GIOCO
b. COLLISION DETECTION IN PANDA3D
c. MAPPATURA DI INPUT BIDIMENSIONALE NELLO SPAZIO: CAMERA ORTOGRAFICA
d. MAPPATURA DI INPUT BIDIMENSIONALE NELLO SPAZIO: RAY CASTING
6. LEAP MOTION CONTROLLER
a. PROGRAMMARE IL LEAP API
b. PRIMI TEST
c. INTRODURRE NUOVE GESTURES GRABBING
d. IL LEAP MOTION CONTROLLER E LA RIABILITAZIONE
7. REALIZZAZIONE DEL GIOCO: HOT AIR BALLOON
a. SCELTA DELLARCHITETTURA WEB-BASED
b. IL PROGETTO DA REALIZZARE
c. HTML 5 TAG CANVAS E REALIZZAZIONE DEL LOOP DI GIOCO IN JAVASCRIPT
i. DEFINIRE LA ZONA DI GIOCO ALLINTERNO DELLA PAGINA WEB
ii. INIZIALIZZARE E LANCIARE IL GIOCO
d. HOT AIR BALLOON DISEGNARE SUL CANVAS
e. HOT AIR BALLOON IL PRIMO SFONDO E LA MONGOLFIERA
i. CARICAMENTO ASINCRONO E PRELOADING DELLE SPRITES.
f. HOT AIR BALLOON RILEVARE E VALIDARE LINPUT DELLUTENTE
i. RILEVARE LE GESTURES PINCH IN e PINCH OUT
ii. MISURARE LA DISTANZA DELLE DITA SULLO SCHERMO
iii. VALIDARE LINPUT DELLUTENTE
g. HOT AIR BALLOON STRUTTURA DEL GIOCO E GAMEPLAY
h. HOT AIR BALLOON FASI DI GIOCO
i. HOT AIR BALLOON ANIMAZIONI
j. HOT AIR BALLOON GAMEPLAY: MOVIMENTO DEI NEMICI; MONETE E COLLISION
DETECTION
k. HOT AIR BALLOON FEEDBACK E SUGGERIMENTI ALLUTENTE
l. HOT AIR BALLOON SISTEMA DI AUTO-CALIBRAZIONE
8. REALIZZAZIONE DELLA PIATTAFORMA PER I TERAPISTI
i

a. REQUISITI E NECESSITA DEI TERAPISTI


b. IL PANNELLO DI CONFIGURAZIONE DEL GIOCO
i. INVIO DELLA CONFIGURAZIONE DAL SERVER AL GIOCO
c. IL PANNELLO DELLE STATISTICHE DEI PAZIENTI
i. INVIO DEI DATI DI SESSIONE DAL GIOCO AL SERVER
d. GESTIONE UTENTI E SICUREZZA
9. CONCLUSIONI
a. RISULTATI OTTENUTI CON HOT AIR BALLOON
b. POSSIBILI SVILUPPI FUTURI
10. BIBLIOGRAFIA

ii

1 - INTRODUZIONE
La riabilitazione fisica e cognitiva richiede ai pazienti un lavoro intenso e prolungato nel tempo.
Il lavoro che i pazienti devono affrontare durante il periodo di riabilitazione costituito da esercizi
ripetuti che mirano a sollecitare le funzionalit da recuperare. La figura del terapista, in qualit di
istruttore, motivatore, assistente e osservatore del paziente durante lesecuzione dellesercizio
riabilitativo fondamentale. E particolarmente importante che il paziente esegua gli esercizi in
modo corretto per evitare linsorgere di problemi muscolari o scheletrici o acquisisca movimenti non
corretti, situazione che viene indicata come maladaptation (adattamento dannoso).
Purtroppo, oggigiorno, gli oneri relativi al supporto del personale, delle strutture e delle attrezzature
necessarie a mantenere attivi ed efficienti i reparti di riabilitazione allinterno delle strutture
sanitarie, sono quanto mai ingenti, ci costringe tali strutture a cercare di abbattere i costi,
riducendo al minimo necessario la fornitura di tali servizi, rischiando cos di ridurre la qualit del
servizio offerto.
Una possibile soluzione al problema dei costi, che potrebbe addirittura innalzare il livello di qualit
dei servizi riabilitativi offerti dalle strutture sanitarie, quella di decentralizzare il lavoro sul paziente,
permettendo a questultimo di svolgere gran parte degli esercizi riabilitativi a casa, in modo
autonomo, ma sempre sotto la supervisione dei terapisti.
Questo approccio reso oggi possibile, dallampia diffusione, di nuovi strumenti a basso costo di
interazione uomo-macchina (HID Human Interface Devices), che spesso vengono prodotti e
utilizzati in ambito videoludico.
Abbiamo ad esempio:
- Microsoft Kinect
- Wii Balance Board
- Leap Motion Controller
- Tablet e Smartphone con schermi multi-touch
Questi dispositivi sono in grado di tracciare, con sufficiente precisione, molti dei movimenti nello
spazio, o nel piano che compiono gli utenti che li utilizzano. Tali rilevazioni possono essere utilizzate
in ambito riabilitativo per controllare la postura e i movimenti del paziente mentre svolge i propri
esercizi di riabilitazione a casa, fornendo cos la possibilit ai terapisti di supervisionare a distanza il
paziente, e al paziente di svolgere a casa il proprio lavoro di riabilitazione in completa sicurezza.
Inoltre, essendo i dispositivi sopracitati, spesso nati dallindustria videoludica, possibile mappare
allinterno di videogiochi creati ad hoc, gli esercizi di riabilitazione che ogni singolo paziente deve
svolgere, creando cos degli esercizi-giochi (exer-games), che permettano di introdurre una
componente di intrattenimento e divertimento allinterno dellesercizio, rendendo il lavoro di
riabilitazione quotidiano pi stimolante.
Questo ci che viene svolto oggi nellambito del progetto REWIRE (di cui si parler di seguito), ed
ci che si cercato di fare, in piccolo, per quanto riguarda la riabilitazione della mano nel progetto
descritto nei capitoli successivi.

2 - RIABILITAZIONE
giusto, prima di procedere oltre, definire cosa si intende per riabilitazione e medicina fisica o
riabilitativa, poich sar largomento centrale di tutto il lavoro descritto in seguito.
Si tratta di quella branca della medicina volta alla diagnosi, terapia e riabilitazione di quelle disabilit
derivate da malattie invalidanti, che possono compromettere le funzionalit motorie, cognitive o
emozionali del paziente.
La medicina riabilitativa coinvolge una serie di figure professionali tra cui quella del fisioterapista,
che si occupa di valutare la condizione dei singoli pazienti e stabilire un percorso riabilitativo
adatto, costituito, spesso da una serie di esercizi da compiere atti al recupero della funzionalit
compromessa.
La funzione del fisioterapista fondamentale nel processo riabilitativo, esso infatti non si limita a
fornire una serie di esercizi che il paziente pu svolgere in autonomia, ma spesso, deve assisterlo
durante lesecuzione fornendo tempestive correzioni in caso di errore, per impedire il
peggioramento delle condizioni iniziali, e fornendo anche la giusta motivazione, che lo spinga a
persistere nellesercizio e ad affrontare le inevitabili difficolt che affronter durante il suo percorso.
importante tenere ben presente questa triplice funzione del terapista di:
- Ideatore del programma di esercizi adatti al paziente
- Correttore di eventuali errori commessi nellesecuzione dei singoli esercizi
- Motivatore del paziente.
In quanto questi tre elementi sono fondamentali affinch il processo riabilitativo vada a buon fine e
dovranno, pertanto, essere presenti anche nel sistema intelligente che affiancher il paziente
durante la riabilitazione a casa propria, quindi in assenza del terapista.

2.a - RIABILITAZIONE A CASA: PROGETTO REWIRE


REWIRE un progetto sostenuto dallUnione Europea che mira a generare una piattaforma di
riabilitazione multi-livello basata su exer-games (http://www.rewire-project.eu).
Scopo del progetto quello di creare un sistema personalizzato che possa essere distribuito su
grande scala nelle case di tutti i pazienti che abbiano bisogno di affrontare un processo di
riabilitazione per recuperare funzionalit compromesse.
un progetto indirizzato a pazienti, dimessi dalle strutture ospedaliere, che hanno bisogno di
continuare a eseguire esercizi di riabilitazione, sotto la sorveglianza remota dei loro terapisti.
La piattaforma REWIRE costituita principalmente da tre macro-componenti:
-

PATIENT STATION (PS): Si tratta di un sistema, inteso come insieme di hardware / software,
che viene installato a casa dei pazienti. Consiste in sostanza di un insieme di mini-videogiochi
che vengono mostrati sullo schermo di un televisore ai pazienti.
Il sistema di input segue il paradigma Hands Free (o a mani libere), si tratta, in breve di
utilizzare una serie di dispositivi (ad esempio il Microsoft Kinect), per il tracciamento della
posizione del paziente nello spazio, questo gli permette di interagire con lambiente virtuale
senza dover agire su nessun dispositivo fisico quale ad esempio mouse, tastiera, joypads,
joysticks ecc.

Cuore della Patient Station il Game Engine IGER (Intelligent Game Engine for
Rehabilitation), sviluppato presso il Laboratorio di Sistemi Intelligenti Applicati del
Diparitmento di Informatica dellUnviersit degli Studi di Milano, un Game Engine basato su
Panda3D, che oltre a fornire le funzionalit classiche di un Game Engine, affronta una serie
di problematiche dovute alla necessit di monitorare ed adattarsi alle necessit del paziente,
analizzeremo queste problematiche nel dettaglio pi avanti.
-

HOSPITAL STATION (HS): Questa stazione, viene utilizzata dalle strutture e dal personale
sanitario, permette di definire a distanza il programma personalizzato di esercizi che ogni
paziente deve affrontare, inoltre fornisce gli strumenti per monitorare e analizzare i
progressi dei singoli pazienti attraverso lelaborazione dei dati ricevuti dalle singole Patient
Stations.

NETWORKING STATION (NS): Installata a livello regionale, la Networking Station, fornisce


funzionalit per lanalisi avanzata dei dati collezionati dalle esperienze di riabilitazione dei
singoli pazienti su tutte le strutture che utilizzano Rewire, questo permette di individuare e
collezionare percorsi riabilitativi comuni tra diverse strutture, creando una consistente
knowledge base, e permettendo cos di migliorare la qualit globale del servizio.

Attualmente REWIRE fornisce principalmente strumenti e minigiochi studiati per la riabilitazione


cognitiva in pazienti affetti da NSU (Negligenza Spaziale Unilaterale), o con disabilit fisiche postictus.
Lo scopo ultimo quello di supportare e fornire strumenti riabilitativi adatti al maggior numero di
patologie e disabilit possibili.

2.b - IGER: Un game engine per la riabilitazione


bene, ai fini di comprendere al meglio il progetto descritto in questo documento, spendere due
parole su IGER, il game engine utilizzato allinterno della Patient Station di REWIRE.
Un game engine un sistema (software) disegnato per la creazione e lo sviluppo di videogiochi. Si
tratta, in sostanza, di una collezione di librerie che forniscono funzionalit atte a risolvere una serie
di problematiche comuni allo sviluppo di quasi tutti i videogiochi.
Tradizionalmente esso si compone di:
-

Rendering Engine (o Renderer): componente che si occupa della generazione dei singoli
frame bidimensionali o tridimensionali a partire da modelli o sprites. il motore sul quale si
basa la generazine di qualsiasi genere di animazione allinterno del gioco.
- Engine Fisico: componente che si occupa di simulare la fisica allinterno del mondo virtuale
ricreato allinterno del videogioco.
- Collision Detection: ogni game engine deve fornire tool adeguati ed efficienti per la
rilevazione di qualsiasi tipo di collisione tra i modelli caricati allinterno del gioco.
- Tools opzionali: quali ad esempio, tool per la gestione dei suoni, le animazioni, lintelligenza
artificiale, input devices differenti e tutto ci che pu essere utile allo sviluppo di un
videogioco.
IGER eredita tutti questi aspetti dal game engine su cui si basa, PANDA3D.
Poich gli esercizi di riabilitazione possono essere molto diversi tra di loro e le abilit e bisogni dei
pazienti possono variare, i videogiochi per la riabilitazione devono potersi adattare alle esigenze dei
3

singoli utenti, e nel frattempo essere in grado di monitorare e archiviare ogni singolo movimento,
mantenendo il pi possibile invariata lesperienza di gioco da un paziente allaltro.
IGER aggiunge alle funzionalit standard di un game engine tradizionale:
-

Tools standard: per la configurazione offline, da parte dei terapisti, dei singoli videogiochi
riabilitativi, permettendo di aggiustare i parametri che pi si ritengono opportuni alle
esigenze di un determinato paziente.
- Strumenti per il graduale adattamento automatico dei parametri di gioco per meglio
rispecchiare levoluzione delle abilit psicomotorie del paziente.
- Strumenti per il monitoraggio in tempo reale dei movimenti dei pazienti.
- Strumenti per lacquisizione e salvataggio dei dati di gioco sia per la valutazione dei progressi
del paziente sia per la motivazione.
Inoltre IGER fornisce un layer di astrazione sul device di Input, denominato IDRA (Input Device for
Rehabilitaion Abstraction layer) questo per potersi adattare al meglio alle esigenze del paziente:
Sar ad esempio pi facile utilizzare un joystick per un paziente affetto da NSU (Neglect) piuttosto
che per un paziente con qualche tipo di paralisi alle braccia. Daltro canto un dispositivo come la
Nintendo Wii Balance Board, non potr essere utilizzata da pazienti con qualche sorta di paralisi alla
parte inferiore del corpo.
Questo layer di astrazione sui dispositivi di Input permetter a qualsiasi paziente di giocare a
qualsiasi gioco della piattaforma, in quanto esso fornisce un interfaccia di input verso il gioco
comune a tutti i dispositivi.
Al momento i dispositivi supportati da IGER sono:
-

Microsoft Kinect.
Sony Playstation 3 Eye camera.
Nintendo Wii Balance Board.
Tyromotion Tymo.
Motycon OpenGo Insoles.
Phantom Omni.
Novint Falcon.
E in parte il Leap Motion Controller.

3 - RIABILITAZIONE DELLA MANO


Ispirandosi al modello fino a qui descritto, quello che si voluto fare in questo progetto stato
ricreare, in piccolo, questi sistemi applicandoli al caso specifico della riabilitazione della mano.
Questo progetto stato svolto in collaborazione con lunit di chirurgia della mano dellOspedale S.
Giuseppe di Milano, diretta dal Prof. Giorgio Pajardi. A questo progetto hanno partecipato anche la
Dott.ssa Erica Cavalli e la Dott.ssa Elena Mancon, le quali ci hanno invitato ad alcune sessioni di
riabilitazione con una paziente e aiutato a inquadrare il tipo di lavoro che avremmo dovuto svolgere
per realizzare il progetto. Per quanto riguarda il laboratorio AISLab, hanno collaborato al progetto il
Dott. Renato Mainetti e il Dott. Michele Pirovano.
Innanzi tutto bene inquadrare il target di pazienti considerato.
Abbiamo avuto la possibilit di sviluppare un mini-gioco riabilitativo per pazienti molto giovani (dai
quattro anni in su) affetti principalmente da agenesia delle dita della mano, o che hanno subito
traumi che hanno portato allamputazione parziale o totale delle dita.
Tali pazienti hanno subito un intervento di transfer delle falangi di alcune dita dei piedi al posto di
quelle amputate o mancanti: scopo di tale operazione quella di restituire al paziente, almeno
parzialmente, la funzionalit della mano, dando la possibilit di toccare e prendere oggetti.
Si tratta di un intervento particolarmente consigliato, specialmente a pazienti affetti da agenesia
delle dita, in quanto, essendo nati senza le dita della mano, e trovandosi nella fase iniziale di
apprendimento, che specialmente a quellet, particolarmente legata allinterazione con
lambiente circostante tramite il tatto e il contatto con gli oggetti, rischiano di sviluppare ritardi
nellapprendimento.
Anche in questo caso, la fase riabilitativa post-operatoria, particolarmente lunga, si tratta di periodi
di tempo dellordine di diversi anni e ha in questo caso due obbiettivi:
-

Il primo, prettamente fisico, quello di esercitare il pi possibile le dita impiantate,


effettuando dei movimenti di avvicinamento e allontanamento delle dita, rotazione e presa
di oggetti.
- Il secondo, di carattere pi neurologico, consiste in una serie di esercizi atti a sviluppare la
consapevolezza nel bambino del nuovo arto e delle sue capacit.
Bisogna notare come il target di utenza sia profondamente differente da quello a cui si riferisce ad
oggi la piattaforma sviluppata allinterno del progetto REWIRE.
Mentre per REWIRE si ha a che fare con pazienti generalmente affetti da danni cerebrali dovuti a
ictus o a pazienti con problemi posturali o di movimento, in ogni caso adulti, in questo caso si ha a
che fare con pazienti molto giovani, bambini, per cui le tecniche di intrattenimento, e motivazione
del paziente vanno rivisitate in un'altra ottica.
E importante, soprattutto, strutturare i giochi in maniera diversa, tenendo il conto dellet
dellutente medio che lo andr ad utilizzare, in particolare:
-

Il gameplay deve essere adattato alle capacit e allet del bambino, non bisogna proporre
nulla di molto complesso, ed fondamentale, per mantenere acceso linteresse del
bambino, che a ogni azione o movimento svolto in maniera corretta dal paziente
corrisponda un immediata risposta, divertente e stimolante del sistema.
Gli obbiettivi stabiliti dal gioco devono essere quanto pi flessibili possibile e non vincolanti,
il modo in cui il bambino si approccer al gioco spesso imprevedibile, per tanto la
5

valutazione delle prestazioni e dei miglioramenti non potr essere misurata ad esempio
dalla quantit di obbiettivi raggiunti, quanto dallosservazione dei dati ottenuti dalle
misurazioni effettuate durante lintera sessione di gioco
Il feedback fornito al paziente dal sistema sia in caso di movimento scorretto che di
motivazione deve essere studiato in maniera adeguata, ad esempio la figura del terapista
virtuale, usato con gli adulti in REWIRE ha poco effetto su un paziente di quattro anni, avr
sicuramente pi effetto uno smiley sullo schermo, piuttosto che fuochi dartificio, colori e
animazioni.

3.a - STRUMENTI DIGITALI GI UTILIZZATI NELLA RIABILITAZIONE DELLA MANO


A seguito dei primi incontri effettuati con i terapisti abbiamo potuto apprendere, che al momento,
vengono gi utilizzati in maniera del tutto sperimentale, dei videogiochi durante alcune sessione di
riabilitazione con il bambino.
Si tratta di giochi per tablet, generalmente iPad, sviluppati da terzi a scopo di intrattenimento. I
terapisti, con i genitori dei giovani pazienti, hanno analizzato alcune delle applicazioni offerte nei
vari App Store, scelto quelle che pi sembravano adatte al paziente in questione, e hanno provato
ad utilizzarle durante alcune sessioni riabilitative.
Questo tipo di approccio al problema porta sicuramente alcuni vantaggi:
-

Innanzi tutto la moltitudine di applicazioni e giochi presenti nei vari store multimediali, offre
sicuramente unampia scelta, questo permette di trovare lapplicazione che pi riesce a
catturare linteresse del bambino e sar quindi pi facile mantenerlo concentrato
sullesercizio da svolgere.
- Lapplicazione installata disponibile per tutti, senza necessit di infrastrutture particolari,
come ad esempio un server ospedaliero per lo storage dei dati ottenuti dallapplicazione, o
sistemi di autenticazione e gestione dei diversi pazienti, questo permette in sostanza ai
genitori dei pazienti di installare sul proprio dispositivo la stessa applicazione provata in
ospedale, e fare giocare il bambino a casa per esercitare il pi possibile la mano da riabilitare
- Inoltre i costi relativi allacquisto di un gioco sullapp store sicuramente irrisorio rispetto a
quello di realizzazione di applicazioni ad hoc.
Vi sono per purtroppo una serie di svantaggi nellutilizzo di queste applicazioni commerciali,
pensate per lintrattenimento in ambito medico/riabilitativo:
-

Spesso questi giochi non sono configurabili, non vi cio la possibilit da parte del terapista,
n di predisporre una tabella di esercizi da svolgere, n di impostare il singolo gioco in modo
da accettare unicamente il tipo di movimento desiderato.
Questi giochi non sono pensati per adattarsi alle capacit fisiche del loro utente, ci significa
che generalmente un interazione errata viene interpretata come un errore, e il giocatore
viene semplicemente penalizzato; in realt, come si visto in precedenza analizzando le
funzionalit di IGER, nei videogiochi per la riabilitazione spesso necessario aggiustare
gradatamente i parametri di gioco in modo da adattarsi alle capacit del singolo paziente.
Spesso utilizzando questi giochi necessaria la presenza fisica del terapista, in quanto il
gioco non pensato per assistere e correggere lutente in caso di movimenti sbagliati, inoltre
non si considera neanche laspetto motivazionale, per cui il paziente, rischia semplicemente
di rimanere frustrato da un insuccesso, e pu rifiutarsi di continuare, oppure pu giocare
controvoglia, aumentando cos il rischio di errore.

Infine non viene fornita ai terapisti un interfaccia per lanalisi dei dati acquisiti durante il
gioco, riguardanti i movimenti effettuati, per cui non possibile monitorare eventuali
progressi o peggioramenti del paziente.

3.b - OBIETTIVO DEL PROGETTO


Alla luce di quanto descritto finora, a seguito degli incontri preliminari avuti coi terapisti, stato
stabilito lobbiettivo del progetto da realizzare:
Un prototipo di mini-gioco per pazienti a cui sono state gi impiantate due dita, lesercizio sar quello
di avvicinare le dita come per pizzicare (pinching) lo schermo di un dispositivo touch screen (nel
caso specifico dei test eseguiti, si tratta di un tablet), lapplicazione dovr reagire sia
allavvicinamento che allallontanamento delle dita (pinch-in e pinch-out), dovr essere
configurabile dal terapista tramite un opportuna interfaccia, e dovr essere in grado di registrare i
movimenti effettuati durante lintera sessione di gioco, i dati raccolti dovranno poi essere presentati
al terapista mediante un opportuna interfaccia in grado di evidenziare eventuali miglioramenti o
peggioramenti delle abilit motorie del paziente.
Lintero sistema di monitoraggio dovr essere trasparente al paziente, e il sistema di feedback ingame dovr essere adattato per essere di facile comprensione a bambini molto piccoli.
Il prototipo verr realizzato utilizzando HTML5 e Javascript e verr installato su un webserver in
universit, lapplicazione verr quindi resa disponibile alla struttura e testata su un campione di
pazienti scelto a discrezione dei terapisti.

4 - STRUMENTI SOFTWARE UTILIZZATI


Prima di addentrarsi nei dettagli dello sviluppo del progetto, bene parlare degli studi che sono stati
effettuati su tecnologie gi esistenti quali il game engine Panda3D, il software di modellazione 3D
Blender, e le API del Leap Motion controller, scopo di tali studi stato quello di prendere confidenza
con alcuni degli strumenti utilizzati, ad oggi nello sviluppo di videogiochi sia allinterno del progetto
REWIRE che in ambito commerciale.

5 - PANDA 3D
Panda 3D un game engine, un framework open source per il rendering 3D e lo sviluppo di
videogiochi. Il nucleo scritto in C++ e le sue funzionalit possono essere estese utilizzando il
linguaggio di scripting Python.
Tra le features offerte ci sono:
-

Renderer 3D che utilizza sia le librerie Mircrosoft DirectX su Microsoft Windows, che le
librerie OpenGL, compatibili con vari sistemi operativi
Librerie per la gestione dellaudio
Un physic engine integrato (oltre a supportarne di pi complessi, come ad esempio lNvidia
Physx engine) che fornisce API per la gestione dei problemi pi comuni nello sviluppo dei
videogiochi, quali quello del collision detection.
Un sistema di effetti particellari dedicato
Un sistema per la gestione delle GUI, per la realizzazione ad esempio di menu di gioco, o
scritte o elementi grafici da mostrare sullo schermo durante la fase di gioco
Una semplice libreria PandaAI per una gestione basilare dellintelligenza artificiale nel
gioco, che fornisce ad esempio strumenti per la ricerca di un oggetto nello spazio di gioco,
levitare gli ostacoli, il trovare e seguire percorsi per spostarsi da un punto a un altro ecc.

5.a - FUNZIONAMENTO LOOP DI GIOCO


Il funzionamento basilare di un qualsiasi videogioco mai prodotto sempre il medesimo, e consiste
sostanzialmente in quattro fasi:
-

Fase di inizializzazione (o init()): la fase in cui vengono istanziati e inizializzati tutti i


componenti che verranno utilizzati allinterno del videogioco, dal timer, ai giocatori, agli
oggetti inanimati, lambiente, suoni, luci ecc.
- Fase di input: la fase in cui vengono letti dallesterno gli input del giocatore (per esempio
il movimento di un joystick, la pressione di un tasto).
- Fase di aggiornamento (o update()): la fase in cui vengono modificati alcuni parametri di
gioco, come ad esempio la posizione dei giocatori.
- Fase di disegno (o draw()): la fase in cui viene effettivamente disegnato il frame che verr
mostrato al giocatore, con tutti i componenti e i modelli le cui propriet sono state definite
nelle fasi precedenti.
In genere la fase di inizializzazione, viene eseguita una sola volta, allavvio del gioco.
Dopo di che si entra in un ciclo (loop) infinito (in realt non proprio infinito, ma termina in
condizioni particolari, quali ad esempio la terminazione del gioco), che esegue alternativamente la
fase di aggiornamento e di disegno del frame.
8

La velocit con cui avviene questo ciclo, o meglio lintervallo di tempo trascorso tra due fasi di
disegno indica il frame rate del nostro gioco.
Initialize

In Panda3D, lo schema descritto sopra ovviamente implementato, ma trasparente al


programmatore.
Linizializzazione di un nuovo gioco e il conseguente lancio del ciclo di Update e Draw viene fatto in
Python estendendo la classe ShowBase, che inizializza alcuni strumenti utili allo sviluppo del gioco
quali ad esempio:
-

Il loader: utilizzato per caricare qualsiasi tipo di gameObject allinterno del nostro gioco, dai
modelli 3d, alle textures, ai suoni, ecc.
- Il render: che si occupa, di tutto ci che grafica allinterno del gioco
- Il taskMgr: per la gestione dei vari task da eseguire allinterno del gioco
- La camera: ovvero la telecamera di default che delimita larea attualmente visibile del gioco
E diversi altri componenti.
Inoltre la classe ShowBase, ci fornisce accesso al metodo run(), che in maniera del tutto trasparente
avvia il loop di gioco.
Tutta la logica che concerne il disegno del singolo frame in Panda3D gestita da un grafo di nodi, la
cui radice loggetto render (o render2D nel caso di giochi bidimensionali); sar sufficiente
impostare un nodo generato dal caricamento (tramite loader) di un qualsiasi modello
tridimensionale come figlio delloggetto render (o come figlio di uno dei suoi figli), affinch il
renderer di panda3D renderizzi loggetto nella scena.
Ci che particolarmente utile in questo schema di gestione dei nodi ad albero, che possibile
impostare parentele tra i diversi oggetti, in tal modo sar ad esempio possibile muovere, scalare o
ruotare un singolo oggetto, non solo in funzione dellorigine della scena visualizzata, ma anche in
funzione del suo genitore.
Ultimo componente degno di nota, utile a descrivere macroscopicamente il funzionamento di un
gioco creato con panda3D, il taskManager.
9

Il TaskManager, implementa in parte il concetto che sta dietro al metodo update, descritto nel
capitolo precedente, si tratta di una struttura dati a cui si possono appendere funzioni, tali funzioni
verranno eseguite a ogni loop e serviranno per i pi disparati scopi, come ad esempio:
- Modificare la posizione della camera di gioco
- Modificare la rotazione del giocatore sulla scena
- Modificare qualsiasi parametro di gioco
- Aggiornare i punteggi
- Oppure semplicemente eseguire controlli per il debugging del gioco
Ecco un esempio di codice per realizzare una semplice animazione, di una sfera che ruota su se stessa
in panda3D.
from direct.showbase.ShowBase import ShowBase
from direct.task import Task
class FirstGame (ShowBase):
def __init__(self):
# Call superclass constructor
ShowBase.__init__(self)
# Set default camera position
base.disableMouse()
self.camera.setPos(0,-10,0)
# Load a model
self.myModel = loader.loadModel("smiley.egg");
# Reparent the model to render so that it will be displayed in the scene
self.myModel.reparentTo(render)
# Attach a task to taskManager to make the spin
self.taskMgr.add(self.spinTheBall, "Spin The Ball Task")
# TASKS
def spinTheBall(self, task):
# Rotate by 20 degrees per second
angleDegrees = task.time * 20.0
self.myModel.setHpr(angleDegrees, 0,0)
return Task.cont
#Instantiate and launch the game
game = FirstGame()
game.run()

Una volta compresa la struttura di base di un gioco realizzato con panda3D si passati alla
realizzazione di semplici giochi di test, nei quali si cercato di mappare un movimento su uno spazio
bidimensionale, come quello del mouse, allinterno della scena di un gioco 3D, scopo di tale test
stato quello di capire come effettuare il mapping dellinput ricevuto da un dispositivo esterno
allinterno del gioco.
Per questo tipo di test, sono stati utilizzati due approcci differenti:
-

Lutilizzo di una camera ortografica invece che proporzionale per inquadrare la scena, tale
approccio consente di mappare, senza distorsioni unarea allinterno di uno spazio
tridimensionale, su un piano.
- Lutilizzo della tecnica del Ray Casting, che permette di rilevare sfruttando il sistema di
collision detection integrato nel game engine, il punto sulla superficie nello spazio
tridimensionale sul quale ci si trova col puntatore del mouse.
Entrambe queste tecniche verranno descritte qui di seguito, ma per fare ci necessario prima
introdurre il sistema di collision detection in Panda3D.

10

5.b - COLLISION DETECTION IN PANDA3D


Il problema del rilevare le collisioni (Collision Detection), un problema chiave nello sviluppo di un
qualsiasi videogioco, sia esso bidimensionale che tridimensionale.
Ogni qual volta due oggetti si toccano, abbiamo una collisione, e se non vogliamo che, ad esempio,
una palla che cade da 10 metri, attraversi semplicemente la superficie e prosegua indisturbata la sua
caduta verso il nulla, dovremmo rilevare la collisione con il suolo e gestirla appropriatamente,
fermando ad esempio la palla allimpatto, o se stiamo realizzando un gioco fedele alle leggi della
fisica, la faccia rimbalzare pi volte fino a farla fermare completamente.
Esistono varie tecniche per rilevare in maniera pi o meno efficiente la collisione tra due oggetti nei
videogiochi.
In videogiochi bidimensionali molto semplici, spesso viene usato quello che in inglese viene
chiamato Box Collider:
Si tratta di costruire un quadrato invisibile attorno agli sprites su cui vogliamo rilevare la collisione
della stessa altezza e larghezza di entrambi gli oggetti, quando i due quadrati si intersecano, allora
si verificata una collisione.
In Panda3D, vengono forniti sostanzialmente due metodi per gestire il problema delle collisioni:
1. La creazione di geometria dedicata alla collisione.
2. Abilitare il rilevamento delle collisioni su tutta la geometria visibile.
Il primo metodo di pi complessa implementazione, in quanto necessario costruire una
geometria secondaria dedicata esclusivamente alla collision detection, ma pi efficiente in quanto i
poligoni su cui rilevare le collisioni sono, in genere, molti meno, di quelli presenti in tutta la scena di
gioco, e sono, di solito, anche figure poco complesse come cubi, sfere, ecc.
Questo metodo dunque pi adatto ad applicazioni finite, il secondo invece molto pi grezzo, ma
molto pi rapido da implementare.
Il secondo metodo, ovviamente, di pi facile gestione, ogni elemento che costituisce la geometria
visibile del gioco, pu collidere con qualsiasi altro elemento nel gioco, il vantaggio ovviamente
dovuto al fatto che non necessario costruire geometrie dedicate alla collision detection, lo
svantaggio, che i poligoni visibili allinterno di un gioco di media complessit pu essere spesso
molto elevato, per cui il gioco costruito pu risultare molto oneroso in termini di risorse di calcolo
richieste per gestire il rilevamento di collisioni su ognuno di essi.
In ogni caso in Panda3D, il sistema di collisioni ruota sostanzialmente attorno ai seguenti oggetti:
-

CollisionSolids:
o CollisionSphere
o CollisionTube
o CollisionPlane
o CollisionPolygon
o CollisionRay []
Sono tutti i solidi, generalmente invisibili che costituiscono la collision geometry, se
volessimo ad esempio far collidere la palla rotante creata nel capitolo precedente con
qualche altro oggetto, allora dovremmo costruire una CollisionSphere invisibile attorno alla
nostra palla, delle stesse dimensioni della palla stessa, a quel punto su questo oggetto
riusciremmo a rilevare collisioni con altri collider.
11

CollisionHandlers:
Ce ne sono di diversi tipi in Panda3D, il pi semplice il CollisionHandlerQueue, che
sostanzialmente si occupa di rilevare tutte le collisioni avvenute nella scena di gioco in un
dato istante di tempo e inserirle in una coda, consultabile dal programmatore, che potr
estrarre le diverse collisionEntries una per volta e gestirle nel modo che ritiene pi
appropriato.

CollisionTraverser, che loggetto fondamentale del sistema di collisioni di Panda3D, si


tratta in sostanza di una struttura dati che colleziona al suo interno tutti i collider creati dal
programmatore nella scena: un collider sostanzialmente, una coppia oggetto
(generalmente un collisionSolid) / handler.
Ecco un esempio del sistema di collisioni di panda3D in funzione, le collisionSphere intorno alle due
sfere sono state scalate in modo da essere un po pi grandi dei modelli visibili, in modo da essere
facilmente visibili:

5.c - MAPPATURA DI INPUT BIDIMENSIONALE NELLO SPAZIO: CAMERA ORTOGRAFICA


Ora che abbiamo visto come funziona il sistema di collision detection in PANDA3D, possiamo tornare
al problema originario, ovvero quello di mappare il movimento del mouse, che avviene su un piano
bidimensionale, allinterno della scena di gioco in panda3D, mappato di default su un ambiente a 3
dimensioni.
La prima delle due tecniche elencate prima era quella della camera ortografica.
12

Il concetto che sta dietro a questa tecnica, quello di sostituire le lenti della camera che inquadra la
scena di gioco, che di default sono lenti a proiezione prospettica: che riprendono lambiente, come
farebbe una telecamera reale, o come farebbe locchio umano; con una lente ortografica, che
elimina di fatto gli effetti prospettici, mantenendo le linee di proiezione perfettamente parallele fra
di loro.
Leffetto ottenuto dallutilizzo di queste lenti, ovviamente, la perdita di percezione visiva della terza
dimensione, per cui ad esempio avvicinandosi o allontanandosi da un oggetto, esso non si
ingrandisce n si rimpicciolisce: questo ovviamente poco intuitivo, ma fornisce un grande
vantaggio, quello di poter scalare la scena inquadrata in modo da mappare in maniera diretta le
coordinate, del dispositivo di input su due dimensioni, nel nostro caso un mouse, allinterno della
scena di gioco.
Per testare questa tecnica stato realizzato un mini-gioco in cui una pallina doveva seguire il
puntatore del mouse allinterno di un percorso delimitato da dei muri laterali, quando la pallina
toccava uno dei muri si bloccava e il gioco terminava.
Analizziamo solo la parte di codice relativa al mapping del device allinterno della nostra scena:
Sappiamo che Panda3D mappa la posizione del mouse con dei valori decimali compresi tra -1 e 1 sia
sullasse delle ascisse che su quella delle ordinate, il nostro scopo dunque quello di montare le
lenti ortografiche, e regolare la dimensione della pellicola fino a quando anche le coordinate della
scena non sono comprese tra -1 e 1, in modo che a certe coordinate del mouse corrisponda il
medesimo punto nella scena.
stato definito un metodo setupCamera, che si occupa proprio di questo:
# This method prepare the camera
def setupCamera(self):
# since this is a 2d game, we use Orthographic lenses, this should avoid
# perspective related issues with the mouse
lens = OrthographicLens()
# we set the camera film size to fit 4/3 aspect ratio, x size is set to
# 2, because we want the coordinate system to be in the [-1, 1] range
lens.setFilmSize(2,1.5)# setFilmSize sets the camera film size in spatial
units
base.camNode.setLens(lens)
# With Orthographic lenses, camera height doesn't really matters
base.camera.setPos(0,0,10)
base.camera.setHpr(0,270,0)

Il metodo che setta la dimensione della pellicola, e quindi definisce la dimensione dellarea ripresa,
setFilmSize.
Una volta definita la dimensione dellarea ripresa si tratter solamente di convertire le coordinate
del mouse, comprese tra -1 e 1 in coordinate spaziali, comprese tra -1 e 1 sullasse delle ascisse e 0.75 e +0.75 sullasse delle ordinate (questo valore per le ordinate dipendente dallaspect ratio
della finestra di gioco,in questo caso 4/3), facciamo questo nel task del gioco che si occupa del
tracciamento del mouse.
La conversione che dobbiamo fare per lasse Y realizzata semplicemente sapendo che laspect ratio
della nostra finestra 4:3, per cui si tratter di prendere le coordinate del mouse ricevute dividerle
per 4 e moltiplicarle per 3, supponiamo ad esempio che il mouse si trovi in coordinate (0,1), allora
la nostra posizione nello spazio di gioco sar 1 / 4 = 0,25 * 3 = 0,75.
13

Vediamo la parte che ci interessa:


def trackMouse(self, task):
# Track the mouse only if it's in the window otherwise we get errors
if(base.mouseWatcherNode.hasMouse() == True and self.trackingEnabled == True):
mousePos = base.mouseWatcherNode.getMouse()
self.mouseVector = Vec3(mousePos.getX(), mousePos.getY(), 0)
# Mouse position in X goes from -1 to 1
tX = self.mouseVector.getX()
# Since camera aspect ratio is 4:3, mouse relative position in our world
# will be 4/3 of its absolute position
tY = (self.mouseVector.getY() / 4) * 3
newWaypoint = Point3(tX,tY,0)
# etc. etc. . . . . .

Ed ecco uno screenshot del gioco finito, da notare come la posizione della pallina corrisponda alle
attuali coordinate del puntatore del mouse:

5.d - MAPPATURA DI INPUT BIDIMENSIONALE NELLO SPAZIO: RAY CASTING


La tecnica della camera ortografica vista nel capitolo precedente, semplifica di molto il problema
della mappatura di devices bidimensionali, o che utilizzano semplicemente un sistema di coordinate
differente rispetto a quello di panda3D, allinterno della scena di gioco; ma allo stesso tempo
impedisce la realizzazione di giochi completamente tridimensionali: questo dovuto alla natura non
14

prospettica delle lenti ortografiche, che non permette di avere percezione della profondit e quindi
della distanza tra la camera e i vari oggetti.
La tecnica del ray casting, ci viene in soccorso nel momento in cui abbiamo necessit di rilevare il la
posizione del mouse, o di altri dispositivi allinterno di una scena tridimensionale.
Il procedimento consiste nel rilevare la posizione del mouse nel suo sistema di coordinate (-1,1) e
proiettare ortogonalmente alla superficie di gioco un raggio (un collisionRay in panda3D) dalla
telecamera: il punto in cui avviene la collisione col terreno, sar il punto su cui si trova il mouse nella
scena.
stato realizzato, anche in questo caso, un gioco simile a quello precedente, ma, questa volta,
utilizzando lenti prospettiche e inquadratura non esattamente ortogonale alla superficie.

Analizziamo anche in questo caso le parti di codice utili al fine di comprendere il funzionamento di
questa tecnica:
Innanzi tutto, quello che dobbiamo fare preparare il collisionRay che andr proiettato sulla
superficie di gioco, si tratta di aggiungere semplice geometria di collisione:

15

# This generates a collisionRay and attaches it to the active camera, then it


# returns a nodePath to that ray
def setupMouseRayCastingLogic(self):
collisionNode = CollisionNode('Mouse Ray collision node')
self.pickerRay = CollisionRay()
collisionNode.addSolid(self.pickerRay)
return base.camera.attachNewNode(collisionNode)

Da notare, lultima istruzione, in cui viene impostato il nodo relativo al nuovo collisionRay come figlio
del nodo della telecamera: questo viene fatto perch proprio da essa che faremo partire il nostro
raggio.
A questo punto la classe collisionRay, ci consente comodamente di settare la posizione e
lorientamento del nostro raggio tramite un comodo metodo setFromLens():

Tale metodo setta lorigine del collisionRay su cui viene chiamato nel punto corrispondente alle
coordinate nel range [-1,1] sul piano focale minimo (ovvero sul piano pi vicino allosservatore, che
verr messo a fuoco dalla camera) e lo proietter, perpendicolarmente a questo piano, per una
lunghezza virtualmente infinita.
A questo punto, se riusciremo a rilevare una collisione tra il raggio e la nostra superficie, allora il
punto di collisione sul terreno, sar il punto del gioco su cui il giocatore ha cliccato, potremmo quindi
rilevare levento e gestirlo di conseguenza.

# This tasks checks the mouse status, and eventually sets the tracked WayPoints
def mouseMonitor(self, task):
# We check if the mouse is within the game window
if(base.mouseWatcherNode.hasMouse() == True):
mpos = base.mouseWatcherNode.getMouse()
# Shoot a ray from the camera towards the surface
self.pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY())
base.cTrav.traverse(render)
if self.cHan.getNumEntries() > 0:
# This is so that we get the closest object
self.cHan.sortEntries()
pickedObj = self.cHan.getEntry(0).getIntoNodePath()
if(pickedObj.getName() == "Ground Collision Node"):
pickedObjPoint = self.cHan.getEntry(0).getSurfacePoint(render)
# etc. etc. [. . .]

Come possibile osservare dal codice, anche in questo caso, si utilizza un semplice il
CollisionHandlerQueue per la gestione delle collisioni: si proietta il raggio invisibile verso la superficie
con setFromLens, si chiama il metodo traverse() di collisionTraverser per rilevare le collisioni, se sono
state rilevate delle collisioni con il collisionNode della superficie, allora il punto di collisione ovvero:
16

pickedObjPoint = self.cHan.getEntry(0).getSurfacePoint(render)

il punto su cui lutente ha cliccato, secondo il sistema di coordinate del nostro gioco.
Una volta presa analizzate le possibili tecniche per la gestione di input da diversi device in Panda3D,
il lavoro s concentrato sullo studio delle potenzialit e la possibilit di integrare allinterno del
game engine, un dispositivo di input differente, il Leap Motion Controller.

17

6 - LEAP MOTION CONTROLLER


Il Leap Motion Controller, un dispositivo di input, pensato e sviluppato per il tracciamento
dettagliato delle mani e di tutte le dita dellutente, senza richiedere nessuna forma di contatto fisico
con il dispositivo.
Il funzionamento molto simile a quello del Microsoft Kinect, ma a differenza di questultimo, il cui
scopo principalmente quello di tracciare lintero corpo degli utenti nel suo raggio dazione, larea
analizzata dal leap motion controller molto ristretta, questo permette di ottenere una risoluzione
superiore a quella di kinect, che pu lavorare, in near mode, a 1 metro di distanza dal soggetto,
contro il range di interazione di 7 - 25 cm del Leap, ci rende il dispositivo potenzialmente adatto a
tracciare i movimenti della mano e la posizione delle singole dita nel dettaglio.

6.a - PROGRAMMARE IL LEAP API


Leap Motion fornisce agli sviluppatori una serie di librerie scritte in diversi linguaggi per interfacciarsi
con il dispostivo: poich scopo dello studio effettuato era quello far funzionare il leap con Panda3D,
sono state analizzate le librerie Python.
Il concetto che sta dietro alla comunicazione con il leap, quello di creare una propria classe,
sottoclasse di Listener, che implementi le funzioni che vengono richiamate per gestire i diversi tipi
di eventi che possono essere sollevati dal device, vediamo i quelli fondamentali:
-

on_init(): questa funzione viene chiamata quando il controller a cui il nostro listener
collegato, viene inizializzato: sar opportuno inserire qua tutta la logica di inizializzazione
della nostra classe
- on_connect(): questa funzione viene richiamata quando il dispositivo si connesso con il
software del leap motion ed pronto a inviare frame
- on_disconnect(): questa funzione esattamente opposta alla precedente, serve ad esempio
a gestire il caso in cui il dispostivo venga accidentalmente scollegato dal computer.
- on_exit(): questa funzione viene chiamata quando il nostro listener viene scollegato dal
controller attivo, qua implementeremo tutta la logica di deallocazione della nostra classe.
- on_frame(): questa la funzione pi importante, richiamata dallevento sollevato quando
viene ricevuto un nuovo frame dal dispositivo, allinterno di questa funzione dovremmo
compiere tutte quelle operazioni necessarie ad analizzare il singolo frame ricevuto, e ad
estrapolare tutte le informazioni necessarie, alla nostra applicazione finale, nel nostro caso,
il gioco costruito con panda3D, affinch essa risponda correttamente agli input dellutente.
Oltre alla classe Listener, la libreria proposta dal produttore del dispositivo, ci fornisce una serie di
classi che ci permettono di astrarre sui dati ricevuti dal dispositivo e di lavorare in comodit su aspetti
quali ad esempio, la posizione della normale del palmo della mano, il numero di dita rilevate, la
posizione della punta di un singolo dito, ecc.
Analizziamo quelle fondamentali:
-

La classe FRAME: probabilmente la classe pi importante, essa contiene tutte le istanze di


tutti i singoli oggetti rilevati nel frame corrente, quali ad esempio: quante mani sono state
rilevate, quante dita, quanti strumenti (come matite, penne, ecc.), quanto grande al
momento larea monitorata dal dispositivo, i frame al secondo generati attualmente, ecc.

18

La classe HAND: fornisce informazioni su una singola mano rilevata dal dispositivo, da
informazioni riguardo, la posizione del palmo, la direzione della normale, se si sta muovendo
e se s in quale direzione e con che velocit, quante dita della mano sono state rilevate ecc.
- La classe POINTABLE: fornisce informazioni analoghe a quelle della classe HAND, ma per il
singolo dito della mano, o per il singolo tool rilevato. Le classi FINGER e TOOL estendono la
classe POINTABLE.
Le classi disponibili nella libreria, sono molte di pi, ma queste sono quelle fondamentali.
bene inoltre spendere qualche parola su un altro elemento fondamentale dellhand tracking: le
gestures.
Per gesture si intende un tipo di movimento molto comune, ricorrente o che ha un intenzione
particolare che pu essere compiuto dallutente.
Il software del Leap in grado di riconoscere alcune di queste gestures, quali ad esempio la rotazione
della punta del dito formando un cerchio: Gesture.Circle, oppure la traslazione di un dito sul piano
orizzontale o verticale: Gesture.Swipe, o il movimento che simula la pressione di un pulsante
Gesture.KeyTap o Gesture.ScreenTap, per ognuna di queste gesture possibile definire una
serie di parametri per la validazione del tipo di movimento da rilevare, quali ad esempio velocit,
ampiezza e direzione del movimento per personalizzare il tipo di risposta del dispositivo in funzione
della nostra applicazione.
Al momento le librerie del Leap non forniscono strumenti ufficiali per la realizzazione di gestures
personalizzate, nonostante ci, in seguito verr descritto un test realizzato in panda3D per il
rilevamento della gesture Grabbing ovvero della presa di un oggetto col Leap Motion Controller.
Prima per descriviamo il primo test realizzato con il Leap Motion Controller:

6.b - PRIMI TEST


Scopo del test stato quello di prendere confidenza con le API fornite dal produttore del dispositivo,
si realizzata una semplice applicazione a riga di comando che a ogni frame verificava linclinazione
di una mano posizionata sopra il dispositivo e stampava su standard input stringhe come La mano
punta verso il basso di gradi oppure la mano inclinata verso sinistra di gradi, ecc.
Ovviamente il cuore di un applicazione cos semplice sar solamente limplementazione del metodo
on_frame(), di cui riportiamo il codice;
Il codice del main poco interessante, conterr unicamente la logica di inizializzazione del nostro
listener e un loop infinito, che manterr il programma attivo fino alla pressione di un tasto,
vediamo dunque la parte interessante:

19

# Questo l'evento che ci interessa realmente, ogni volta che il listener fa


polling sul leap genera un frame che contiene tutte le info
# su ci che il dispositivo riuscito a trackare
def on_frame(self, controller):
# Recupero il frame corrente
frame = controller.frame()
# Controllo che ci sia almeno una mano:
if(not frame.hands.is_empty):
print("%d Mano/i Rilevata/e" % (len(frame.hands)))
# Recupero la prima mano, hands un vettore
hand = frame.hands[0]
# la direzione della mano intesa dal palmo verso le dita
direction = hand.direction
# la normale del palmo della mano, un vettore che
# punta verso il basso
normal = hand.palm_normal
pitch = direction.pitch * Leap.RAD_TO_DEG
roll = normal.roll * Leap.RAD_TO_DEG
yaw = direction.yaw * Leap.RAD_TO_DEG
if(pitch > 0):
print("La mano punta verso l'alto di %f gradi" % (pitch))
else:
print("La mano punta verso il basso di %f gradi" % (pitch))
if(roll < 0):
print("La mano e' inclinata verso destra di %f gradi" % (roll))
else:
print("La mano e' inclinata verso sinistra di %f gradi" %
(roll))
if(yaw > 0):
print("La mano e' ruotata verso destra di %f gradi" % (yaw))
else:
print("La mano e' ruotata verso sinistra di %f gradi" % (yaw))
else:
print("Metti la mano sopra il leap!")

Come stato detto in precedenza, la callback on_frame viene richiamato dal software di tracking
fornito col leap motion controller, ogni volta che il dispositivo trasferisce un frame al computer:
per prima cosa utilizziamo il metodo frame della classe Controller per recuperare listanza del frame
corrente.
A questo punto con un semplice controllo sulla lista dimensione della lista di oggetti Hand nel frame
corrente decidiamo se stampare la scritta Metti la mano sopra il leap!, o meno.
Nel caso in cui la mano sia stata rilevata dal dispositivo, accediamo allattributo hands della classe
frame, che sostanzialmente una lista di mani, e estraiamo il primo elemento, ovvero la prima mano
rilevata.
Ricaviamo direzione e normale del palmo, e ricaviamo attraverso dei semplici attributi gli angoli di
inclinazione della mano (pitch, roll, yaw) che convertiamo da radianti a gradi, fatto ci
stampiamo le stringhe pi appropriate.
Ecco un esempio di output del programma:

20

Realizzato questo primo test, si cercato di utilizzare il leap motion controller allinterno di panda3D:
stato realizzato in Blender un semplice modello tridimensionale di un aeroplano, che, una volta
importato in panda3D, stato programmato per muoversi seguendo i dati rilevati a posizione e
inclinazione della mano sul leap motion controller.
Rispetto al test precedente, la differenza dovuta al fatto che questa volta non bastato limitarsi a
stampare i valori di inclinazione della mano ottenuti a ogni frame, ma si dovuto mantenere in
memoria i dati rilevati in un dato frame, fornendo i metodi necessari al recupero degli stessi
allinterno di panda3D.
Una delle difficolt riscontrate nellutilizzo del leap allinterno del loop di gioco stata quella di
sincronizzare linput ricevuto dal dispositivo con la frequenza di aggiornamento del gioco stesso. Il
problema dovuto al fatto che il leap, generalmente, genera frame a velocit diversa rispetto a
quella necessaria al completamento del ciclo di aggiornamento del gioco: per tanto, se ad esempio,
si cerca di accedere a un parametro impostato allinterno del leapListener in due punti del codice
nella nostra procedura di aggiornamento di gioco, si rischia di ottenere valori diversi: perch pu
accadere che nel tempo che abbiamo impiegato per arrivare alla seconda istruzione di accesso ai
dati di movimento della mano, il leap abbia generato un nuovo frame, e il dato precedente stato
sovrascritto.
Si pu affrontare tale problema in due modi:
-

Sincronizzando il frame rate del gioco e del leap motion controller, cosa non banale, in
quanto bisognerebbe richiedere al gioco un frame rate costante, cosa che penalizzerebbe
calcolatori poco potenti che dovranno eseguire il gioco.
- Organizzando il codice del gioco in modo da richiedere il valore estratto dal leap una sola
volta per ciclo, e conservare quel valore fino a loop completato, eliminando cos qualsiasi
problema di concorrenza alla fonte.
stata adottata questa seconda strategia, analizziamo le parti fondamentali del codice in questo
secondo test:
21

Innanzi tutto dovremo scrivere un listener personalizzato in grado di ottenere per ogni frame i gradi
di rotazione della mano intorno ai tre assi, inoltre avremo bisogno della posizione normalizzata del
palmo della mano rispetto allarea scansionata dal dispositivo lungo lasse Z, questo ci permetter
di settare la velocit dellaereo in funzione della posizione della mano: pi la mano vicina al bordo
superiore dellarea scansionata, pi la velocit sar alta, pi si avviciner al bordo inferiore, pi la
velocit sar bassa.
Il nostro listener estender ovviamente la classe Leap.Listener, concentriamoci su due dei metodi da
implementare metodi:
Il metodo on_init() , come abbiamo visto prima, il luogo in cui bisogna implementare tutta la logica
di inizializzazione del nostro listener: poich dovremo poter accedere dal gioco ai dati sulla posizione
della mano, dovremo dichiarare e inizializzare dei campi ad accesso pubblico che conterranno i dati
rilevati a ogni frame.
def on_init(self, controller):
# Questo contiene il frame precedente, serve per rilevare le traslazioni
# della mano
self.prevFrame = None
self.handPitch = None
self.handRoll = None
self.handYaw = None
self.translationStatus = None
self.reversed_normalized_palm_position = None
print("Listener inizializzato...")

Come possibile osservare, i dati ottenuti riguardano beccheggio (pitch), rollio (roll) e
imbardata, (yaw) della mano.
Concentriamoci ora sul metodo pi importante del listener, on_frame(): come nel test precedente
dovremo accedere ai dati riguardanti la posizione rilevata della mano sopra il dispositivo e
aggiornare le i campi impostati dal metodo di inizializzazione.
I dati relativi alla rotazione della mano, vengono convertiti, anche in questo caso da radianti a gradi
per moltiplicando il loro valore per la costante Leap.RAD_TO_DEG = (180/) con lunico scopo di
semplificarne lutilizzo allinterno di panda3D.
Degno di nota anche il metodo normalized_point() della classe InteractionBox (classe che
rappresenta lattuale area scansionata dal dispositivo), tale metodo richiamato con argomento
hand.palm_position restituisce la posizione normalizzata della mano nello spazio in un sistema di
coordinate ([0,1],[0,1],[0,1]) dove 0 il punto allestremit inferiore dallarea nel range attuale del
dispositivo e 1 il punto allestremit superiore per ognuno dei 3 punti cardinali dello spazio.

Ecco di seguito il codice:

22

def on_frame(self, controller):


# Recupero il frame corrente
frame = controller.frame()
# Controllo che ci sia almeno una mano:
if(not frame.hands.is_empty):
# Recupero la prima mano, hands e' un vettore
hand = frame.hands[0]
# la direzione della mano intesa dal palmo verso le dita
direction = hand.direction
# E' la normale del palmo della mano, un vettore che punta verso il
# basso
normal = hand.palm_normal
# Rilevo l'attuale rotazione della mano
self.handPitch = direction.pitch * Leap.RAD_TO_DEG
self.handRoll = normal.roll * Leap.RAD_TO_DEG
self.handYaw = direction.yaw * Leap.RAD_TO_DEG
# Estraggo la posizione della mano sull'asse Z
interactionBox = frame.interaction_box
# Ottengo un vettore che rappresenta la posizione del palmo
# normalizzata in relazione all'attuale
# interaction_box
normalized_palm_position =
interactionBox.normalize_point(hand.palm_position)
# Il leap usa un sistema di coordinate per cui l'avanti e' 0
# e indietro e' 1, a me serve il contrario
self.reversed_normalized_palm_position =
1 - normalized_palm_position.z
else:
print("Metti la mano sopra il leap!")
self.prevFrame = frame

Costruito il nostro listener personalizzato, vediamo ora come utilizzare i dati ottenuti dal listener per
far muovere laeroplano in panda3D. Innanzi tutto in fase di inizializzazione del gioco necessario
istanziare il listener, facciamo ci nel costruttore della classe World, il cui scopo quello di creare il
nostro mondo di gioco, chiamando la funzione controllerSetup cos implementata:
def controllerSetup(self):
# Istanzio il listener e il controller
self.leapListener = MyLeapListener()
self.leap = Leap.Controller()
# Assegno il listener al controller
self.leap.add_listener(self.leapListener)

A questo punto accedendo alla variabile self.leapListener avremo accesso a tutti i campi pubblici
riguardanti linclinazione e la posizione della mano nello spazio impostati nel punto precedente.
Copia di questo oggetto viene quindi passata al costruttore della classe Player, il cui scopo quello
di visualizzare e gestire il movimento del giocatore.

23

def move(self, task):


# Get target values for pitch roll and yaw,
targetPitch = self.inputListener.handPitch;
targetRoll = self.inputListener.handRoll;
targetYaw = self.inputListener.handYaw;
# If values are set to None, simply terminate the task
if(targetPitch is None or targetRoll is None or targetYaw is None):
return task.cont
# Yaw and Roll values are inverted in leap, so
# we need to change their sign
targetRoll *=-1
targetYaw *=-1
# Get dt to move smoothly
dt = globalClock.getDt()
# We want to rotate smoothly towards the target so we don't
# simply set the player Hpr to the values
# obtained from the inputListener, but we rotate a certain
# number of degrees per second towards the target
# Smoothly change heading
if(self.playerRootNP.getH() < targetYaw):
newH = self.playerRootNP.getH() - self.yawSpd * dt
if(newH > targetYaw):
newH = targetYaw
if(self.playerRootNP.getH() > targetYaw):
newH = self.playerRootNP.getH() - self.yawSpd * dt
if(newH < targetYaw):
newH = targetYaw
else:
newH = self.playerRootNP.getH()
# Smoothly change pitch
if(self.playerRootNP.getP() < targetPitch):
newP = self.playerRootNP.getP() + self.pitchSpd * dt
if(newP > targetPitch):
newP = targetPitch
if(self.playerRootNP.getP() > targetPitch):
newP = self.playerRootNP.getP() - self.pitchSpd * dt
if(newP < targetPitch):
newP = targetPitch
# Smoothly change roll
if(self.playerRootNP.getR() < targetRoll):
newR = self.playerRootNP.getR() + self.rollSpd * dt
if(newR > targetRoll):
newR = targetRoll
if(self.playerRootNP.getR() > targetRoll):
newR = self.playerRootNP.getR() - self.rollSpd * dt
if(newR < targetRoll):
newR = targetRoll
# Rotate the plane
self.playerRootNP.setHpr(newH,newP,newR)
# Check Throttle
self.checkThrottle()
# This actually moves the plane
self.playerRootNP.setPos(self.playerRootNP, 0,
self.throttle * self.playerMaxSpd * dt,0)
self.throttleLabel.setText("Throttle: %.1f" %(self.throttle * 100))
return task.cont

Come possibile osservare dal codice del Move Player Task creato nella classe Player, la gestione
dellinput affrontata in maniera piuttosto semplice:
Per prima cosa si accede allinputListener e si salvano in variabili locali i valori di inclinazione della
mano rilevati dal dispositivo, in questo modo in caso il listener cambiasse tali valori perch il
dispositivo ha generato un nuovo frame, avremmo comunque dei dati consistenti per il nostro ciclo
di aggiornamento della posizione dellaeroplano.
24

A questo punto tramite semplici operazioni matematiche si convertono i dati ricevuti dal dispositivo
in dati utilizzabili allinterno del gioco, per fare un esempio, possibile osservare come i gradi di
rollio e imbardata dellaeroplano arrivino dal dispositivo con segno opposto a quello utilizzato
allinterno di panda3D, in questo caso tali valori vengono semplicemente moltiplicati per -1 prima di
essere utilizzati per il movimento del giocatore.
In realt questi dati, una volta convertiti potrebbero essere utilizzati direttamente come parametri
del metodo setHpr(H,P,R) della classe NodePath di panda3D per far corrispondere con un mapping
diretto alla posizione della mano lesatta posizione dellaeroplano.
Il problema di questo approccio che i movimenti dellaereo risulterebbero troppo repentini per
essere realistici in caso di movimenti molto rapidi della mano dellutente; il codice scritto qua sopra
cerca di ovviare al problema in maniera molto semplice: lattuale posizione della mano non la
posizione attuale dellaereo, ma la posizione in cui laereo deve arrivare.
Come possibile osservare, laereo ha delle velocit di rotazioni impostate per ogni asse: il valore
ricevuto in input langolo a cui laereo deve arrivare (ad esempio targetRoll), se la rotazione attuale
dellaereo inferiore o superiore rispetto allangolo obiettivo allora bisogna ruotare laereo verso
tale angolo della sua velocit.
In questo modo si ottiene un movimento molto pi fluido e naturale.

6.c - INTRODURRE NUOVE GESTURE GRABBING


Come stato detto nella parte di introduzione al Leap Motion Controller, al momento le API fornite
dal produttore del dispositivo non forniscono strumenti semplici per lintroduzione di gestures
personalizzate e le gestures rilevate di default dal dispositivo sono molto poche e limitate, in
25

particolare se si vogliono sviluppare giochi per la riabilitazione della mano, dove, a seconda della
disabilit da riabilitare, necessario rilevare movimenti diversi.
Si voluto perci esplorare la possibilit di rilevare movimenti non preimpostati, e il movimento
scelto per questi test un movimento molto comune, quello di Grabbing o presa di un oggetto.
Innanzi tutto bene spendere due parole sul perch rilevare questo movimento non un operazione
cos banale con il leap motion controller, per fare ci ci avvaliamo di uno strumento molto comodo
fornito con il software di gestione del dispositivo il visualizer:
Si tratta di un software che si interfaccia col dispositivo e ci fornisce una rappresentazione
tridimensionale di ci che il dispositivo sta rilevando nello spazio intorno a se in tempo reale.

Come possibile osservare dallimmagine, nel caso della presa, il dispositivo non riesce a rilevare la
presenza delle dita nel momento in cui esse si avvicinano troppo fra di loro: questo dovuto al fatto
che il dispositivo osserva lambiente da un'unica posizione: dal basso, ci significa che nel momento
in cui le dita cominciano a essere troppo vicine fra loro e sovrapposte, dal suo punto di osservazione,
al palmo, esso non pi in grado di rilevarle e di conseguenza la lista fingers della classe hand del
frame corrente indica 0 dita presenti.
Il problema che a questo punto ci si pone come rilevare un evento come il grabbing, basato
strettamente sulla distanza delle dita (che deve essere a ogni frame sempre minore) con un
dispositivo in cui le dita superato un certo angolo spariscono?
Ovviamente non si pu rilevare con certezza assoluta, ma si pu pensare alla seguente osservazione
statistica:
Se per un certo numero di frame, il dispositivo ha rilevato 5 dita, e magari per ognuno di essi, le
punte delle dita si stavano avvicinando e improvvisamente a un certo frame, tutte le dita sono
scomparse (e rimangono assenti anche nei frame successivi), allora c una buona probabilit (e si
tratta di una probabilit, non di una certezza) che sia stata effettuata una gesture di grabbing.
Scopo del test effettuato, era dunque scrivere una sotto-classe del Leap-Listener in grado di
segnalare, utilizzando questa tecnica di monitoring delle dita rilevate frame per frame nel tempo,
leventualit di una possibile gesture di presa, e il conseguente rilascio.
Per prima cosa bisogna identificare un modo per poter segnalare allutente leffettivo rilevamento
della gesture di grabbing, si potrebbe studiare un sistema ad eventi per cui a ogni gesture valida
rilevata si potrebbe sollevare un evento che potremmo gestire allinterno della nostra applicazione.
26

Un alternativa meno complessa, vista la semplicit del nostro test, quella di utilizzare un campo
pubblico del nostro listener (self.grab) di tipo booleano, che viene settata a true quando lutente ha
effettuato una presa e a false quando non lha effettuata oppure lha rilasciata.
Ci serviamo inoltre di una lista di interi che utilizzeremo per mantenere i dati relativi al numero di
dita rilevate in un certo numero di frame trascorsi.
Definiremo quindi un metodo catch_grab(hand) che verr chiamato allinterno del metodo
on_frame per rilevare se sulla mano in argomento stata rilevata la gesture di grabbing, e in caso
affermativo setter la variabile che abbiamo definito prima a true.
Vediamo come si comporta il metodo catch_grab(hand):
def catch_grab(self, hand):
# If we have at least one element in our finger_story list
if(not self.__finger_count_story is None):
# Let's sort the finger list, we just need to get the maximum finger
# number in the last 5 frames
self.__finger_count_story.sort()
max_finger =
self.__finger_count_story[len(self.__finger_count_story) - 1]
if(max_finger >= len(hand.fingers)):
# If the maximum finger number from the last 5 frames is 5, and
# now we got a 0, than this is a grab
if(max_finger == 5 and len(hand.fingers) == 0):
self.grab = True
print("GRAB")
# If grab is set to true, but we have five fingers in our
# current frame, than we should release the grab
if(self.grab == True and len(hand.fingers) == 5):
self.grab = False
print("GRAB RELEASED")
# This if-else statement simply updates the list
if(len(self.__finger_count_story) < 5):
self.__finger_count_story.append(len(hand.fingers))
else:
self.__finger_count_story.pop(0)
self.__finger_count_story.append(len(hand.fingers))
# If we didn't have any element in our finger story list,
# simply add one
else:
self.__finger_count_story = [len(hand.fingers)]

In questo caso la nostra lista: __finger_count_story contiene fino a 5 element0,i ovvero il numero
delle dita rilevate negli ultimi cinque frame, calcoliamo il numero massimo di dita rilevate in questi
frame: questo viene fatto per eliminare alcune rilevazioni fasulle dovute a eventuali elementi di
disturbo nellambiente scansionato dal leap, che possono per qualche frame far rilevare meno dita
di quelle che sono realmente sul dispositivo, in questo modo consideriamo il numero massimo di
dita rilevate in un dato frame con un valore attendibile del numero di dita che fino al frame scorso
erano effettivamente sul dispositivo.
A questo punto, come possibile osservare dal codice si tratta, semplicemente di confrontare il
numero ottenuto prendendo il massimo dalla lista, con il numero di dita rilevate nel frame corrente
e in particolare abbiamo che:
SE avevamo 5 dita fino al frame scorso e il numero di dita rilevate in questo frame 0 allora lutente
sta effettuando una presa.
Inoltre:
27

SE avevamo gi rilevato un evento di grabbing, quindi self.grab settato a True e ora rileviamo di
nuovo 5 dita sopra il dispositivo, allora la presa stata rilasciata.
A questo punto se in un ipotetico gioco Panda3D, in cui vogliamo rilevare la presa di un oggetto,
andiamo a controllare lo stato della variabile inputListener.grab, possiamo sapere se lutente sta
cercando di prendere qualcosa o meno.
Vediamo un esempio di esecuzione in un semplice gioco scritto in panda3D in cui utilizzando le
tecniche di raycasting viste in precedenza rileviamo su una superficie la posizione della mano, se
chiudiamo la mano scatenando levento di grabbing, allora una pallina sulla superficie comincer a
seguire la nostra mano:

6.d - IL LEAP MOTION CONTROLLER E LA RIABILITAZIONE


Dai test effettuati, il Leap Motion Controller si dimostrato essere uno strumento particolarmente
valido per la realizzazione di videogiochi per la riabilitazione della mano, in quanto permette un
tracciamento preciso e molto rapido della posizione di ogni singolo dito, del palmo, della velocit di
movimento e ci permette di sviluppare, con questi dati, moduli per il rilevamento di diverse
gestures adeguate per i singoli esercizi richiesti dai terapisti.
Nel caso specifico della riabilitazione della mano dei bambini, con cui abbiamo avuto a che fare in
questo periodo di lavoro, per, stato deciso di non adottare il dispositivo, almeno nella fase iniziale,
per i seguenti problemi riscontrati:

28

Le dita impiantate sulla mano dei giovani pazienti, sono spesso troppo piccole per essere
rilevate con precisione dal dispositivo: spesso, il software di visualizzazione del leap motion
controller con cui abbiamo effettuato alcuni test, mostra unicamente il palmo della mano,
ci rende praticamente impossibile realizzare qualsiasi tipo di esercizio che coinvolga le dita
della mano.
Il Leap Motion Controller certamente uno strumento di facile utilizzo, molto immediato: si
mette la mano sopra il dispositivo e succede qualcosa sullo schermo; per richiesta una
certa coordinazione mano-occhio che non da dare per scontata per utenti con meno di 5
anni: necessario infatti operare con la mano sopra al dispositivo, mentre allo stesso tempo
si osserva uno schermo che non si trova sotto al dispositivo, questo obbliga lutente a
operare sul leap senza guardarlo, il che porta spesso i bambini a spostarsi al di fuori del
raggio dazione del dispositivo rendendo non fluido linput del programma e difficile, e poco
preciso il rilevamento dei dati sul movimento effettuato.
Questo fa s che un sistema basato su leap non sia cos facile da istallare e utilizzare in pratica.

Inoltre:
- Un problema di attrezzature: il Leap Motion Controller non funziona da solo, ha bisogno
almeno di un PC con sufficiente capacit hardware da gestire sia il software del Leap, che il
videogioco riabilitativo che lo utilizza. Inoltre, essendo lo strumento da realizzare studiato
per la riabilitazione a casa, necessaria una fase di configurazione e installazione dei
software relativi al Leap e al gioco, operazione che non sicuramente alla portata di
chiunque e che richiederebbe del personale dedicato, in grado di fornire il minimo di
assistenza necessaria a chi deve configurare lambiente.
- Un problema di costi: a differenza di molti altri dispositivi, come smartphone o tablet,
difficilmente un paziente possieder a casa un Leap Motion Controller, esso dovrebbe quindi
o essere fornito dalla struttura sanitaria o acquistato direttamente dal paziente, generando
un problema di costi, che si voluto evitare, quanto meno nella prima fase di
sperimentazione.
Il Leap Motion Controller rimane comunque uno strumento assolutamente valido per la
riabilitazione della mano in pazienti pi grandi, o per la riabilitazione cognitiva: ad oggi, infatti, il
dispositivo stato gi parzialmente integrato allinterno del Game Engine IGER, per quanto riguarda
il posizionamento del palmo e il tracciamento della posizione di un singolo dito o di un tool e andr
ad aggiungersi alla lista dei dispositivi supportati dalla piattaforma REWIRE.

29

7 - REALIZZAZIONE DEL GIOCO: HOT AIR BALLOON


Per quanto riguarda il progetto descritto nelle prossime pagine si preferito utilizzare dei comuni
tablet, molto pi diffusi del Leap Motion Controller, per una serie di motivazioni:
-

Abbiamo innanzi tutto potuto notare come i giovani pazienti abbiano gi sviluppato un
altissima dimestichezza nellutilizzo di questi dispositivi con applicazioni commerciali,
questo semplifica di molto la fase di apprendimento nellutilizzo dellapplicazione che verr
realizzata.
- Spesso i pazienti, o le loro famiglie, sono gi in possesso di uno di questi dispositivi, vista la
loro diffusione, ci permette di abbattere i costi.
- Inoltre i dispositivi multi-touch permettono di rilevare pi dita contemporaneamente sullo
schermo, il rilevamento molto preciso, e difficilmente le dita scompariranno senza che
lutente le sollevi realmente dal dispositivo.
Si cercato, come vedremo nel prossimo capitolo, di sviluppare unapplicazione che fosse
compatibile con il maggior numero di dispositivi, pertanto, lintero gioco stato sviluppato come
applicazione web in HTML5 e Javascript.

7.a - SCELTA DELLARCHITETTURA WEB-BASED


Le motivazioni di questa scelta sono molteplici:
Innanzitutto, volendo realizzare unapplicazione per la riabilitazione a casa a bassissimo costo, lidea
stata quella di dare la possibilit ai pazienti di sfruttare qualsiasi dispositivo di cui fossero gi in
possesso a casa, per utilizzare lapplicazione sviluppata, gli unici requisiti sarebbero stati una
connessione ad internet, un browser web e uno schermo touchscreen, di cui sono dotati ad oggi
tutti i pi comuni smartphone e tablet.
Inoltre prototipare un videogioco utilizzando le canvas di HTML 5 e javascript, molto pi rapido
che sviluppare lo stesso nativamente per ciascun dispositivo da supportare, questo ha permesso di
presentare ai terapisti in un tempo relativamente breve, un prototipo di gioco da testare sui pazienti.
Larchitettura web-based presenta inoltre una serie di vantaggi anche per quanto riguarda lo
sviluppo dellinterfaccia di configurazione e analisi dei dati da fare utilizzare ai terapisti.
Anche in questo caso non necessario installare nulla in ospedale per permettere ai terapisti di
accedere ai dati dei singoli pazienti, ma sufficiente sfruttare un qualsiasi terminale con accesso a
internet per accedervi. Tutti i dati dei pazienti vengono mantenuti anonimamente sul server e sono
sempre a disposizione dei terapisti
I vantaggi delladottare questa architettura sono quindi: completa indipendenza dal client, velocit
di sviluppo, gestione centralizzata dei dati dei pazienti, la possibilit di creare interfacce web per la
configurazione e analisi dei dati relativi ai pazienti in maniera molto rapida ed efficace.
Tale approccio presenta per alcuni svantaggi:
Innanzi tutto un gioco sviluppato per un browser web non pu essere utilizzato in assenza di
connessione a internet o con connessioni molto lente, ci pu non essere adatto per pazienti che
abitano in luoghi con accesso difficoltoso alla rete.

30

Le capacit delle librerie grafiche per web per quanto molto avanzate, sono ancora lontane
dallessere equiparabili a quelle disponibili per i singoli dispositivi, per tanto sar difficile realizzare
videogiochi particolarmente complessi per browser web.
Ad oggi esistono una serie di difficolt per quanto riguarda la riproduzione audio in videogiochi
basati sul web che vengono utilizzati su dispositivi mobili.
Inoltre lintera piattaforma sviluppata: gioco + pannello di configurazione + pannello di analisi ha
bisogno di funzionare su un webserver centralizzato, questo deve essere installato, gestito e
mantenuto direttamente dalla clinica che lo utilizza, tale gestione richiede personale competente e
quindi un potenziale costo per la struttura.
Detto ci, si comunque ritenuto opportuno utilizzare, almeno in questa fase preliminare questa
architettura, per rendere disponibile ai clinici e ai pazienti nel pi breve lasso di tempo possibile un
prototipo di piattaforma da testare e valutare per sviluppi futuri.

7.b - IL PROGETTO DA REALIZZARE


A seguito di una serie di incontri preliminari svolti con i chirurghi che si occupano di operare i
pazienti, con i fisioterapisti che si occupano della riabilitazione, con una piccola paziente di 4 anni e
i suoi genitori, stato deciso come procedere nella realizzazione del prototipo del primo videogioco.
La paziente, molto giovane, affetta da agenesia delle dita di una mano, aveva subito qualche mese
prima un operazione di transfer delle falangi di due delle dita dei piedi sulla mano.
Abbiamo potuto assistere a una sessione di riabilitazione:
Essa si divide sostanzialmente in due parti, una prima parte consiste in una serie di esercizi fisici in
cui al paziente, attraverso il gioco, vengono fatti maneggiare oggetti di diversa consistenza e
dimensione per realizzare costruzioni, tale processo serve a stimolare il processo di corticalizzazione
del nuovo arto.
La seconda parte, cognitiva, serve a fare prendere al paziente consapevolezza della sua nuova mano,
e del fatto che essa pu essere usata in maniera funzionale per diversi scopi.
Come gi accennato in precedenza, ci stato mostrato come i terapisti abbiano gi provato ad
utilizzare applicazioni commerciali per tablet allinterno della sessione di riabilitazione.
stato interessante osservare come la piccola paziente utilizzasse queste applicazioni: si trattava in
genere di giochi senza uno scopo preciso, a cui ad una qualsiasi azione corrispondeva una reazione:
applicazioni di disegno, o semplici giochi in cui toccando elementi della scena mostrata succedeva
qualcosa.
Tali applicazioni, come spiegato in precedenza, avevano il difetto di non richiedere un movimento in
particolare, per cui non potevano essere utilizzate per svolgere e valutare un esercizio in particolare,
ma solo per far esercitare e muovere la mano in libert alla paziente.
A seguito delle osservazioni svolte durante la sessione di riabilitazione, abbiamo avuto un colloquio
con i terapisti, con i quali abbiamo identificato un movimento da tracciare con il primo gioco da
realizzare.
Il movimento in questione quello di avvicinamento e allontanamento delle dita della mano a
pizzico (in inglese pinching): questo ad esempio il movimento, molto spesso richiesto, da

31

applicazioni per la gestione di fotografie, per la scalatura delle immagini: allontanando le dita si
zoomma ovvero si ingrandisce limmagine, avvicinandole si restringe.
Abbiamo quindi pensato di realizzare un semplice gioco, la cui protagonista una mongolfiera che
sale quando le dita di avvicinano, e scende quando si allontanano: scopo del gioco fare decollare
la mongolfiera, farla volare in un cielo pieno di uccellini da evitare e monetine da prendere, e quindi
farla atterrare dolcemente a destinazione.
Insieme al gioco stata sviluppata un interfaccia per la configurazione dei parametri relativi al
singolo paziente, e per lanalisi delle singole sessioni di gioco e della storia delle sessioni effettuate
Nei capitoli che seguiranno verranno descritte tutte le fasi di sviluppo e le problematiche affrontate
nello sviluppo di questo videogioco, e dellintera piattaforma per i terapisti.

7.c - HTML 5 TAG CANVAS E REALIZZAZIONE DEL LOOP DI GIOCO IN JAVASCRIPT


Elemento fondamentale per la realizzazione di videogiochi basati sul web ed eseguibili su un
qualsiasi browser web, lelemento Canvas.
Si tratta di un estensione dellHTML, che permette, tramite un linguaggio di scripting, nel nostro caso
Javascript, il rendering dinamico di immagini bitmap.
Il tag html canvas definisce un area di altezza e larghezza definiti su cui possibile, tramite API
javascript dedicate disegnare dinamicamente, cos come viene fatto utilizzando in altri ambiti API
per la grafica bidimensionale.
Di per s il tag canvas delimita unicamente un contenitore di grafica bidimensionale, e le API ad esso
associate sono unicamente API grafiche, il contenitore non predisposto di per se ad ospitare un
videogioco.
Come possibile, allora, realizzare animazioni sfruttando questo elemento?
Il concetto di per s abbastanza semplice:
Si scrive allinterno di una funzione, che dora in poi chiameremo DRAW, che verr richiamata
costantemente ogni tot millisecondi tramite la funzione javascript setInterval(function, time)
Questa funzione draw, non far altro che cancellare come prima cosa, lintero contenuto del canvas,
e ridisegnarlo subito dopo con opportune modifiche.
In questo modo genereremo una serie di frame, il cui framerate sar regolato dal parametro time
che passeremo alla funzione setInterval.
Trattandosi i giochi sostanzialmente di animazioni interattive, il passo tra il realizzare un animazione
e un semplice videogioco molto breve, vediamo quindi come ricreare il loop di gioco, la cui
struttura stata descritta nel capitolo sugli studi preliminari, in unapplicazione web in javascript.
7.c i - DEFINIRE LA ZONA DI GIOCO ALLINTERNO DELLA PAGINA WEB
Per prima cosa dobbiamo dedicare una zona della nostra pagina web a ospitare il gioco, per fare ci
utilizziamo il tag canvas.
Bisogna ricordarsi di assegnare un id univoco allelemento e fissare altezza e larghezza:

32

<canvas id="myCanvas" width="800", height="600" style="border 1px solid black">


OPS! Your broser doesn't seem to support canvas!
</canvas>

Come possibile notare, sufficiente scrivere del testo allinterno del tag canvas per gestire
leventuale accesso allapplicazione da parte di un client dotato di browser non in grado di gestire il
tag canvas.
A questo punto possiamo preparare la classe principale del nostro gioco, il cui scopo sar quello di
inizializzare il gioco, e avviare il loop di update e draw che ci permetter di animarlo!
7.c ii - INIZIALIZZARE E LANCIARE IL GIOCO
Questa fase particolarmente importante, attraverso il costruttore della nostra classe game,
recuperiamo lelemento canvas definito allinterno della nostra pagina HTML attraverso il suo id,
recuperiamo loggetto context, che utilizzeremo allinterno del gioco per disegnare fisicamente le
nostre sprites, scriveremo la vera e propria logica di inizializzazione del gioco, per cui il caricamento
delle singole sprites, immagini, inizializzeremo tutte le variabili di gioco, e instanzieremo tutti i nostri
game object.
Definiremo i metodi per laggiornamento e il disegno dei singoli frame, solo a questo punto
attraverso un metodo run, che la nostra classe dovr fornire, avvieremo il loop di gioco:

/* CONSTANTS */
var UPDATE_FREQ = 15;
function Game(canvasID){
/* Game Variables */
this.canvas = document.getElementById(canvasID);
this.ctx = this.canvas.getContext('2d');
/* Public Methods */
this.update = function(self){
// GAME UPDATE LOGIC HERE
console.log("update");
self.draw(self);
}
this.draw = function(self){
// FRAME DRAW LOGIC HERE
console.log("draw");
}
this.run = function(){
// This frequency should give us a framerate slightly higher
// than 60, fps:
// by increasing this value we reduce the frame-rate making
//the game slower but easier to run
// on slower devices
var self = this;
setInterval(function(){self.update(self)}, UPDATE_FREQ);
}
}

33

Elemento degno di nota allinterno del codice qui sopra la frequenza in millisecondi passata al
metodo setInterval(), essa ci permette, in generale, di regolare il frame-rate del nostro gioco:
Per come stato settato nellesempio qui sopra, e per come stato impostato il sistema, il metodo
update viene richiamato ogni 15 millisecondi, garantendo un frame rate di un po pi di 60 frame
per secondo.
Ovviamente incrementando questo valore, si riduce il numero di frame al secondo, rallentando il
gioco ma rendendolo giocabile anche su dispositivi meno performanti.
Di seguito la semplice formula per il calcolo dei millisecondi necessari a ottenere il frame-rate
desiderato:
1000 =
Per cui ad esempio, per ottenere un frame rate di 25 frame per secondo, la frequenza di
aggiornamento sar di:
1000 25 = 40
A questo punto ben chiaro che sar sufficiente istanziare in una variabile loggetto game e
richiamare il metodo run() per lanciare il gioco.
<script type="text/javascript">
var game = new Game("myCanvas");
game.run();
</script>

In poche righe di codice si cos ricreato in ambiente web, il loop di gioco, vedremo ora nei prossimi
capitoli come a partire da questo semplice schema si sia realizzato il gioco HotAirBalloon,
analizzando man mano, tutti i problemi che sono stati affrontati nello sviluppo.

7.d - HOT AIR BALLOON DISEGNARE SUL CANVAS


Uno dei primi problemi affrontati nello sviluppo del gioco stato quello di passare dalla struttura
descritta nel capitolo precedente, a qualcosa di visualizzabile e giocabile.
Il tag canvas, di per s solamente un contenitore di contenuti grafici, non possiede di suo nessuno
strumento adatto a disegnare, per fare ci necessario utilizzare un linguaggio di scripting, come
javascript e loggetto javascript che ci permette di disegnare sul nostro canvas il Graphic Context:
recuperabile con il metodo canvas.getContext(2d); tale oggetto permette al programmatore di
disegnare proceduralmente diverse figure allinterno del canvas, di cancellare disegni effettuati in
parte o nella totalit di esso, scrivere del testo, e disegnare immagini da file sia intere che parziali.
I metodi forniti da questo importante oggetto sono diversi e di diversi tipi:
METODI DI CREAZIONE
-

Rect(): crea un rettangolo di dimensioni date, nella posizione specificata


Arc(): crea un arco di lunghezza e ampiezza specificata: notare come un ampiezza di 2 x Pi
Greco, formi un cerchio perfetto.
METODI DI DISEGNO
Sono sostanzialmente i metodi fill e stroke applicati ai vari elementi creati, ad esempio:

34

fillRect() disegna un rettangolo pieno con lo stile specificato nella propriet fillStyle
strokeRect() disegna un rettangolo vuoto, con il bordo nello stile specificato nella
propriet strokeStyle
- In modo analogo funzionano i metodi: fillArc() strokeArc(), fillText()
Vi sono poi una serie di metodi e attributi per modificare direttamente il contesto:
METODI DI TRASFORMAZIONE
- scale(): scala lintero contesto ai valori passsati come parametri
- rotate(): ruota lintero contesto allinterno del canvas
- translate(): trasla il contesto, ovvero sposta in nuove coordinate il punto di origine 0,0
Attenzione che metodi come questi non vanno a operare direttamente sulloggetto da disegnare,
ma sullintero contesto, questo significa che, ad esempio, dopo avere utilizzato scale, tutti gli
elementi che verranno disegnati successivamente utilizzeranno la nuova scalatura impostata.
Per ovviare a questo problema, sono stati implementati altri due metodi che permettono di salvare
e ripristinare il contesto precedente, ripristinando, ad esempio la scalatura originale.

METODI PER LO SWITCH DI CONTESTO


-

save(): salva il contesto attuale


restore(): ripristina il contesto precedentemente salvato con il metodo save()

Ora che conosciamo gli strumenti basilari per disegnare allinterno del nostro canvas, bene
spendere qualche parola sul sistema di coordinate utilizzato per posizionare i disegni allinterno di
questo contenitore:
[IMMAGINE]
Come possibile osservare il punto di origine della nostra tela si trova nellangolo superiore
sinistro, la distanza da questo punto misurata in pixel: sia sullasse X che sullasse Y ci fornisce un
metodo univoco per posizionare i nostri oggetti sulla scena.
Ad esempio se volessimo disegnare un pallino qualcosa esattamente nellangolo superiore destro di
un canvas di 800x600px, allora posizioneremo il nostro pallino nel punto: (800,0).

7.e - IL PRIMO SFONDO E LA MONGOLFIERA


Ora che conosciamo meglio lelemento canvas e il graphic context, possiamo tornare ad
HotAirBalloon, la prima cosa da fare disegnare lo sfondo della nostra scena, e in seguito posizionare
la mongolfiera, che verr fatta muovere al giocatore.
Partiamo dallo sfondo, si tratta di un semplice sfondo statico, una singola immagine da mostrare
allinterno dellintero canvas: la sequenza di operazioni da svolgere la seguente:
-

Creare un oggetto di tipo immagine, vuoto allinterno del DOM html, questo in javascript si
pu fare sfruttando la classe standard Image
Caricare effettivamente limmagine da file, che risiede sul server, e impostarla come
sorgente, dellimmagine appena creata
Scalare limmagine per entrare comodamente allinterno del nostro contenitore
35

- Disegnare limmagine a ogni frame.


Prima di fare ci, per, necessario fare una riflessione: il target della nostra applicazione web, sono
i dispositivi mobili, in particolare tablet, il paziente mentre gioca non dovr avere la sensazione di
trovarsi allinterno di una pagina web, ma di una vera e propria applicazione, per tanto sarebbe
opportuno che il nostro canvas, non avesse dimensioni predefinite, ma occupasse lintera pagina
visualizzata.
Javascript fortunatamente, ci fornisce due propriet particolarmente utili al nostro scopo, esse sono:
- window.innerWidth
- window.innerHeight
Queste due propriet ci forniscono la dimensione, in pixel dellarea che pu ospitare del contenuto
allinterno della nostra pagina: in sostanza, ci fornisce la dimensione della zona bianca della pagina
web dove possiamo inserire contenuto, senza che lutente debba scrollare verticalmente o
orizzontalmente la pagina.
In questo modo sar sufficiente, impostare le propriet:
- canvas.width = window.innerWidth
- canvas.height = window.innerHeight
Per occupare lintera pagina visualizzata con il nostro canvas.
Ora che abbiamo ridimensionato correttamente il nostro contenitore, possiamo procedere con lo
sfondo:
Bisogna innanzi tutto fare una considerazione sulla scelta di questo elemento: ora il nostro canvas,
non ha pi dimensioni note, ma esse saranno strettamente dipendenti dalla dimensione dello
schermo del dispositivo utilizzato dallutente,
Sar possibile adattare la dimensione dello sfondo in funzione della dimensione del canvas
accedendo alle propriet width e height di questultimo, ma dobbiamo pensare che esso potrebbe
essere, allargato o ristretto senza rispettare le sue proporzioni per poter occupare lintera area su
cui si estende il canvas.
Per questo motivo il motivo rappresentato nello sfondo dovr essere il pi neutrale possibile, per
evitare che lutente si accorga di eventuali deformazioni dellimmagine:
Un cielo con delle nuvole, paesaggi collinari, ecc. possono fare al caso nostro in quanto, in caso di
allargamento dellimmagine senza mantenere le proporzioni, ci ritroveremmo semplicemente con
nuvole un po pi allungate o colline un po pi piatte, ma nulla di estremamente innaturale.
Ecco quindi come caricare e disegnare il nostro sfondo:
-

Allinterno del costruttore della nostra classe game, creiamo limmagine e le assegniamo la
sorgente.

this.takeoffbg = new Image();


this.takeoffbg.src = "sprites/bg.jpg";

Allinterno del metodo Draw a questo punto dobbiamo fare in modo che a ogni frame, venga
cancellato il contenuto della scena al frame precedente, e ridisegnato il contenuto della scena
relativa al frame corrente:

36

this.draw = function(self){
// FRAME DRAW LOGIC HERE
self.ctx.clearRect(0,0,window.innerWidth, window.innerHeight);
self.ctx.beginPath();
self.ctx.drawImage(self.takeoffbg ,0,0,
window.innerWidth, window.innerHeight);
}

Come possibile osservare, il metodo clearRect() ci serve per pulire la scena: esso infatti elimina
qualsiasi contenuto grafico presente nellarea definita dal rettangolo passato come argomento.
Il metodo beginPath() un metodo di servizio che permette di comunicare al context linizio di un
nuovo tratto da disegnare.
Il metodo drawImage(), infine, serve per disegnare la nostra immagine, prende come parametro
limmagine da disegnare, le coordinate da cui iniziare il disegno, e la larghezza e altezza desiderata.
Il tutto cos scritto, sembra funzionare a dovere e produce, quanto meno in locale, quanto mostrato
nellimmagine seguente:

C per purtroppo un problema nascosto in quanto scritto, dovuto alla modalit in cui vengono
caricate le immagini allinterno di una pagina web.
7.e i - CARICAMENTO ASINCRONO E PRELOADING DELLE SPRITES.
In genere il caricamento di immagini attraverso javascript avviene in maniera asincrona: questo
significa che quando noi comunichiamo di recuperare limmagine sprites/bg.jpg dal server, viene
lanciato il caricamento del file, quindi il download dellimmagine dal server alla macchina client, ma
lesecuzione del codice, non viene interrotta, questo vuol dire che spesso ci si ritrover a eseguire
listruzione successiva a quella di caricamento, prima che il client sia realmente in possesso
dellimmagine richiesta.

37

ASYNC
LOAD IMAGE

ACCESS IMG

DOWNLOAD

ERROR

NEXT ISTR

DATA
READY

In che modo questo pu generare problemi?


Supponiamo di aver lanciato il gioco da client, a un certo punto il nostro codice istanzier la nostra
variabile background, e richieder il download dellimmagine, il download per particolarmente
lento, ci pu accadere per diversi motivi: sovraccarico del server, connessione di qualit scadente,
ecc.
Il download non ancora completato, quando il nostro codice, che gira lato client, e che ha
proseguito la sua esecuzione, arriva a eseguire per la prima volta il metodo draw, che ha al suo
interno listruzione drawImage(), che richiede che limmagine sia pronta, ma limmagine ancora in
download, cosa succede allora? Il risultato non prevedibile!
Quello che certo che ovviamente, questa situazione, crea una serie di problemi allinterno del
gioco!
Come si pu ovviare al problema?
Una delle tecniche pi adatte quella che prevede di effettuare il preloading degli sprites, ovvero
caricare le nostre sprites prima che esse vengano utilizzate.
Per fare ci, in javascript possibile assegnare una funzione di callback allattributo onload,
presente in qualsiasi oggetto di tipo Image.
Tale callback, come suggerisce il nome, verr chiamata non appena verr catturato levento di
caricamento completato dellimmagine.
A questo punto, quindi possiamo riscirvere il nostro codice, per gestire anche questo problema:
-

Definiremo una variabile: takeOffBgReady, booleana, che sar impostata di default a false,
e che indicher quando il nostro sfondo sar pronto per essere disegnato.
Assegneremo una funzione come callback allattributo onload del nostro sfondo, il cui unico
scopo sar settare a true la variabile definita prima.
38

Verificheremo lo stato della variabile takeOffBgReady per scegliere se disegnare o meno il


nostro sfondo.
Ecco il codice aggiornato del nostro costruttore:
var takeoffBgReady = false;
this.takeoffbg = new Image();
this.takeoffbg.src = "sprites/bg.jpg";
this.takeoffbg.onload = function(e){
takeoffBgReady = true;
}

E del metodo draw:


this.draw = function(self){
// FRAME DRAW LOGIC HERE
self.ctx.clearRect(0,0,window.innerWidth, window.innerHeight);
self.ctx.beginPath();
if(takeoffBgReady === true){
self.ctx.drawImage(self.takeoffbg ,0,0,
window.innerWidth, window.innerHeight);
}
}

Il risultato sar come quello visto in precedenza, ma adesso saremo protetti da eventuali problemi
dovuti a immagini non pronte.
A questo punto non ci resta che caricare unulteriore sprite per la mongolfiera e posizionarla nella
parte centrale inferiore della nostra scena in modo che sia pronta al decollo.
Il disegno della mongolfiera potrebbe essere affrontato in maniera analoga a quello dello sfondo,
ma in questo caso stiamo preparando un oggetto che verr fatto muovere sullo schermo dal
giocatore, chiamiamo questo oggetto per comodit GameObject, sar bene quindi pensare a una
nuova classe: Player, che fornisca propriet e metodi adatti a modificare posizione, scalatura ecc.
in maniera comoda allinterno del metodo update del nostro gioco, oltre che a disegnarlo
semplicemente
In particolare avremo bisogno di due propriet, o campi, pubblici che indichino la posizione del
nostro oggetto sul canvas, possiamo chiamarli pos_x e pos_y, facile intuire come variando nel
metodo update del nostro loop di gioco questi valori, e usando queste propriet allinterno del
metodo ctx.drawImage, si possa facilmente muovere a piacere la mongolfiera sullo schermo.
Vediamo la classe un po pi nel dettaglio:

39

function Player(x,y){
var balloonReady = false;
var balloon = new Image();
balloon.src = "sprites/balloon.png";
balloon.onload = function(){
balloonReady = true;
}
// Public properties
this.pos_x = x;
this.pos_y = y;
this.scale = .5;
// Public methods
this.draw = function(ctx){
// Draw only if sprite is ready
if(balloonReady === true){
// SAVING OLD CONTEXT
ctx.save();
// New draw
ctx.beginPath();
ctx.drawImage(balloon, this.pos_x, this.pos_y,
balloon.width * this.scale, balloon.height * this.scale);
// RESTORING OLD CONTEXT
ctx.restore();
}
}
}

Come possibile osservare, la sprite del giocatore viene gestita in maniera analoga a quella dello
sfondo, viene impostata una variabile balloonReady e inizializzata a false, viene richiesto il
caricamento dellimmagine, e solo a caricamento completato tale variabile viene settata a true.
La classe giocatore, cos come labbiamo definita, prende due parametri, ovvero le coordinate iniziali
su cui disegnare la nostra mongolfiera, fornisce inoltre:
-

Tre propriet pubbliche, pos_x, pos_y per modificare la posizione della mongolfiera in
runtime, e scale, per modificare in runtime anche la dimensione della mongolfiera.
- Un metodo per il disegno della mongolfiera, che prende come parametro la variabile
contenente il graphic context corrente: salva, il contesto attuale, disegna la nostra
mongolfiera, nella posizione desiderata e della dimensione corretta, dopo di che ripristina il
contesto.
Lutilizzo di questa classe particolarmente semplice, inseriremo nel costruttore del nostro gioco,
una variabile player che ospiter unistanza della nostra classe Player, e allinterno del nostro
metodo draw, chiameremo il metodo draw della classe Player, per disegnare il nostro giocatore.
this.player = new Player(0,0);

40

this.draw = function(self){
// FRAME DRAW LOGIC HERE
self.ctx.clearRect(0,0,window.innerWidth, window.innerHeight);
self.ctx.beginPath();
// Draw the background
if(takeoffBgReady === true){
self.ctx.drawImage(self.takeoffbg ,0,0,
window.innerWidth, window.innerHeight);
}
// Draw Player by calling its own draw method!
self.player.draw(self.ctx);
}

A questo punto la mongolfiera verr disegnata a partire dallangolo superiore sinistro del canvas, noi
vorremmo per posizionarla sul terreno al centro dello schermo, in modo che sia pronta a decollare.
Per fare ci dovrebbe essere sufficiente impostare le variabili pos_x e pos_y come segue:
player.pos_x = window.innerWidth / 2 - player.getWidth() / 2;
player.pos_y = window.innerHeight - player.getHeight() - 30;

Ci si propone per nuovamente un problema dovuto al caricamento asincrono della sprite della
mongolfiera.
Sapendo che i metodi getWidth() e getHeight() restituiscono la dimensione effettiva della nostra
sprite sullo schermo secondo la formula:
=
E
=
Nel caso in cui posizionassimo le istruzioni scritte sopra, subito dopo listruzione di creazione del
giocatore:
this.player = new Player(0,0);

Potremmo trovarci nella situazione in cui limmagine non sia ancora stata scaricata completamente
dal client, e quindi il valore SPRITE WIDTH non sia ancora stato settato, per tanto il valore restituito
dai nostri due metodi sarebbe 0, e la mongolfiera non verrebbe posizionata correttamente.
Bisogna quindi aspettare che limmagine sia pronta, prima di settare la posizione iniziale del
giocatore, il problema che si pone a questo punto quanto aspettare?
Potremmo monitorare, allinterno di un ciclo, una variabile che indica lo stato del giocatore
(POLLING), oppure utilizzare il sistema di eventi di javascript e lanciare un nuovo evento
personalizzato dalla callback che viene richiamata onload dello sprite, che abbiamo visto in
precedenza, in cui comunichiamo a un eventuale listener, la possibilit di utilizzare in sicurezza i
metodi delloggetto player.
stata adottata questa seconda opzione:

41

Per prima cosa necessario modificare come segue la funzione di callback chiamata al caricamento
completo dellimmagine:
var self = this;
balloon.onload = function(){
balloonReady = true;
var playerReadyEvent = new CustomEvent(
"playerReady",
{
detail: {
message: "Player is Ready",
player: self
},
bubbles: true,
cancelable: true
}
);
document.dispatchEvent(playerReadyEvent);
}

A questo punto, allinterno del costruttore della classe listener, sar sufficiente predisporre un
listener appropriato, che, al rilevamento dellevento playerReady chiamer una funzione che si
occuper di riposizionare la mongolfiera sullo schermo.
// Wait for the player to be ready, than set its position:
document.addEventListener("playerReady", function(e){
var player = e.detail.player;
player.pos_x = window.innerWidth / 2 - player.getWidth() / 2;
player.pos_y = window.innerHeight - player.getHeight() - 30;
}, false);

Abbiamo quindi descritto, come allinterno del gioco stato gestito il problema del preloading delle
sprites, e del caricamento asincrono dei contenuti, per qualsiasi altro elemento utilizzato allinterno
del gioco stato utilizzato il medesimo approccio.
Al momento abbiamo caricato la mongolfiera del giocatore, e labbiamo posizionata nella posizione
iniziale, ecco il risultato:

42

Quello che verr affrontato ora uno dei temi centrali dellintero progetto, la gestione dellinput
dellutente attraverso lo schermo touch screen del dispositivo utilizzato.

7.f - HOT AIR BALLOON RILEVARE E VALIDARE LINPUT DELLUTENTE


I problemi da affrontare a questo punto dello sviluppo del gioco sono sostanzialmente tre, dobbiamo
infatti essere in grado di:
-

Rilevare la posizione delle dita sullo schermo


Rilevare levento di pinch-in e pinch-out in utenti con mobilit ridotta, che quindi
difficilmente riusciranno a compiere movimenti ampi e facilmente distinguibili.
- Essere in grado di misurare a ogni istante la distanza: in cm, tra le dita attualmente sullo
schermo, questi dati saranno fondamentali, in seguito, per realizzare statistiche sulla qualit
del movimento effettuato.
Analizziamo queste problematiche una per volta:
RILEVARE LA POSIZIONE DELLE DITA SULLO SCHERMO
Il W3C definisce le specifiche per i touch events come un insieme di eventi a basso livello che servano
a rappresentare uno o pi punti di contatto tra una superficie touch-sensitive e gli elementi di una
pagina HTML rappresentati sotto di essa.
In javascript viene fornita unimplementazione a queste specifiche tramite una serie di oggetti ed
eventi catturabili a runtime.
Questi eventi sono:
-

Touchstart, scatenato nel momento in cui lutente tocca la superficie e genera almeno un
punto di contatto touch point, tali punti sono elencati allinterno delloggetto che
rappresenta levento generato.
Touchmove, scatenato quando un utente sposta, trascinandolo un touch point sulla
superficie.
43

Touchend, scatentato ogni qualvolta, lutente rimuove un touch point dalla superficie (ad
esempio solleva un dito)
- Touchcancel, da non confondere con touchend, scatenato quando uno dei punti di
contatto esce dal DOM della pagina: ad esempio il dito si spostato dalla pagina alla barra
degli strumenti del browser, oppure comparso un pop-up sullo schermo
- Touchenter, scatenato quando uno dei punti di contatto entra in un elemento specifico del
DOM
- Touchleave, scatenato quando uno dei punti di contatto esce da un elemento specifico del
DOM.
Quello che bisogner fare innanzi tutto allinterno del nostro gioco, al fine di rilevare le dita sullo
schermo, sar quindi associare gli opportuni handlers per gli eventi touchstart, touchmove e
touchend rilevati sul nostro canvas, contare il numero di dita rilevato e accedere alla loro posizione.
Recuperati questi dati, li passeremo al nostro gioco, che li utilizzer almeno in questa fase iniziale,
per mostrare dei pallini colorati in corrispondenza delle dita dellutente.
Cosa fondamentale, sar prevenire allinterno dei vari handlers definiti, eventuali comportamenti
che alcuni client possono avere a seguito del rilevamento di determinate gestures: ad esempio la
gesture di pinch che dovremo largamente utilizzare allinterno di questo gioco, su iPad, scatena
lingrandimento o lallargamento della pagina del browser visualizzata, mantenere questo
comportamento, renderebbe ovviamente il gioco inuilizzabile.
Fortunatamente javascript fornisce una comoda funzione per inibire questi comportamenti:
event.preventDefault();
Tralasciando la classe Finger, che si occupa di creare proceduralmente dei cerchi con il metodo
ctx.drawArc, e di disegnarli in maniera del tutto analoga al giocatore e allo schermo nellarea di
gioco, vediamo come rilevare gli eventi descritti sopra, e come gestire i dati ricevuti.
Per prima cosa bisogna verificare che il dispositivo corrente supporti gli eventi touch, per fare ci
sufficiente settare una variabile in questo modo:
var touchable = 'createTouch' in document;

Questa istruzione controlla lesistenza della stringa createTouch allinterno delloggetto della pagina,
restituendo true, se tale stringa presente, false altrimenti.
Tale stringa presente solo in dispositivi che supportano eventi touch, per tanto, sar sufficiente
effettuare un controllo su questa variabile per stabilire se possiamo o meno proseguire:

44

var touches = [];


if(touchable){
/* Using standard events to precisely detect finger position */
this.canvas.addEventListener("touchstart", function(e){
e.preventDefault();
});
this.canvas.addEventListener("touchmove", function(e){
e.preventDefault();
touches = e.touches;
});
this.canvas.addEventListener("touchend", function(e){
e.preventDefault();
touches = [];
});
}

Se ci troviamo su un dispositivo che supporta gli eventi touch, allora associamo un listener per gli
eventi di inizio, fine e movimento di un punto di contatto con lo schermo, ci assicuriamo di prevenire
comportamenti indesiderati con il metodo preventDefault(), e in caso di movimento salviamo la lista
e.touches allinterno di una variabile accessibile dalla nostra classe game.
e.touches una lista di punti di contatto o come vengono identificati in javascript: Touch, ogni
touch un oggetto che contiene un identificatore univoco, il target su cui stato scatenato levento:
nel nostro caso sar un puntatore al canvas, e una serie di coordinate prese da diversi punti di
riferimento:
- clientX,clientY: sono le coordinate misurate dallangolo superiore sinistro dellintera finestra del
browser
- pageX, pageY: sono le coordinate misurate dallangolo superiore sinistro della pagina visualizzata
- screenX, screenY: la posizione misurate dallangolo superiore sinistro dellintero schermo del
client.
Allinterno del progetto, le uniche coordinate utilizzate sono state screenX e screenY in quanto esse
sono le uniche che variano solo in caso di aggiunta di nuovi monitor, o cambio della risoluzione dello
schermo, il che rende il sistema di riferimento che esse generano, sempre consistente e affidabile.
Nel caso specifico dellevento touchend, il comportamento dellhandler semplicemente quello di
rimuovere lintero contenuto della lista touches.
A questo punto siamo in grado di accedere ai punti di contatto delle dita sullo schermo anche dal
nostro gioco, per tanto non ci resta che creare due istanze delloggetto Finger, che rappresenter
come dicevamo prima, un dito sullo schermo:
// Fingers
this.fing1 = new Finger();
this.fing2 = new Finger();

A questo punto allinterno del metodo update, non dovremmo fare altro che contare il numero di
dita rilevate sullo schermo, se esse sono due, sono sufficienti a rilevare la gesture di pinching, per
tanto possiamo fornire un feedback adeguato allutente, posizioniamo le dita nel punto di contatto
rilevato allinterno del metodo update del gioco:
45

// There can be a pinch only if there are two fingers on the screen
if(touches.length === 2){
self.fing1.show = true;
self.fing2.show = true;
self.fing1.pos_x = touches[0].screenX;
self.fing1.pos_y = touches[0].screenY;
self.fing2.pos_x = touches[1].screenX;
self.fing2.pos_y = touches[1].screenY;
}
else{
self.fing1.show = false;
self.fing2.show = false;
}

E le disegniamo nel metodo draw:


// Draw GUI elements
if(self.fing1.show === true){
self.fing1.draw(self.ctx);
}
if(self.fing2.show === true){
self.fing2.draw(self.ctx);
}

Come possibile intuire la variabile show, serve a stabilire se necessario disegnare o meno il pallino
sullo schermo, il risultato sar semplicemente la comparsa di pallini in corrispondenza delle dita
quando ci sono almeno due dita sullo schermo.

46

7.f i - RILEVARE LE GESTURES PINCH IN e PINCH OUT


Ora che siamo in grado di contare le dita sullo schermo e rilevarne la loro posizione, il prossimo
passo da seguire sar quello di riconoscere le gestures di pinching sia in apertura che in chiusura:
Analizziamo nel dettaglio il movimento: di norma il pinching consiste nellavvicinamento progressivo
delle dita dellutente, fino, idealmente, alla loro unione completa, ovviamente impossibile
richiedere a un paziente con scarsa mobilit della mano di riuscire ad unire completamente le dita:
ci gli sar fisicamente impossibile.
Per tanto nel gioco si intesa questa gesture come lavvicinamento progressivo delle dita, non fino
alla loro unione, ma fino a quando esse non abbiano raggiunto un distanza obiettivo:
min_finger_target_distance e le dita siano state staccate dallo schermo.
Ovviamente per il movimento opposto, il pinch-out il discorso analogo, le dita partono da una
distanza minima e si allargano fino a raggiungere una distanza richiesta:
max_finger_target_distance, quando le dita abbandonano la superficie viene scatenato levento
di pinch-out.

I valori di min / max finger distance, ovvero le distanze obiettivo che i pazienti dovranno
raggiungere con le loro dita, vengono stabilite in due modi:
- In fase di setup del gioco dal terapista tramite unopportuna interfaccia.
- Tramite un sistema di auto-calibrazione attivo nella fase iniziale del gioco.
Entrambi i metodi verranno discussi dettagliatamente in seguito.
Per quanto riguarda la gestione degli eventi pinch, pinch-out e pinching si deciso di utilizzare
una libreria di terze parti, specializzata a rilevare molte delle gestures tipiche operabili su dispositivi
touch-enabled, quali: tap, swipe, rotate, drag e ovviamente pinch.
La libreria in questione si chiama QuoJs, e per quello che riguarda il progetto della mongolfiera
in grado di rilevare e scatenare i seguenti eventi:
-

pinch: scatenato quando lutente ha effettuato un movimento di avvicinamento delle dita


per un certo numero di pixel e le ha staccate dallo schermo
- pinching: scatenato in continuazione durante il movimento di avvicinamento delle dita
- pinchOut: scatenato quando lutente ha effettuato un movimento di allontanamento delle
dita per certo numero di pixel e le ha staccate dallo schermo.
Nostro scopo, in questa fase, sar quindi catturare levento pinching e allinterno di esso
monitorare costantemente la distanza tra le dita del paziente a ogni aggiornamento. Rilevare gli
47

eventi pinch o pinch-out, scatenati a movimento completato, salvare i dati relativi al movimento
effettuato per poter preparare successivamente le statistiche relative, stabilire se il movimento
effettuato valido o meno, ovvero se esso ampio a sufficienza rispetto ai parametri impostati in
fase di calibrazione, e infine fare reagire il gioco di conseguenza mostrando un feedback negativo in
caso di errore, o facendo muovere la mongolfiera in caso di successo.
Vediamo nel dettaglio come tutto ci stato implementato:
Per prima cosa definiamo nel costruttore della classe principale del gioco tre variabili booleane:
var pinched = false;
var pinching = false;
var pinchout = false;

Queste variabili verranno settate a true dai vari handler relativi agli eventi corrispondenti; in questo
modo sar sufficiente controllare lo stato di queste variabili dal metodo update del nostro gioco per
sapere quale caso dovremmo gestire a ogni iterazione del game loop.
Ora catturiamo gli eventi che vengono scatenati dalla libreria QuoJs, precedentemente importata
nel file index.html, posizioneremo tali handler allinterno del controllo if(touchable) descritto
precedentemente, infatti non ha senso assegnare degli handler per degli eventi che non possono
essere scatenati dal client corrente:
/* Handlers: Using the QuoJS Library to detect gestures */
$$(this.canvas).on("pinch", function(e){
e.preventDefault();
pinched = true;
});
$$(this.canvas).on("pinching", function(e){
e.preventDefault();
pinching = true;
});
$$(this.canvas).on("pinchOut", function(e){
e.preventDefault();
pinchout = true;
});

Come possibile osservare gli handler si limitano a prevenire il comportamento di default associato
alla gesture rilevata: nel nostro caso lingrandimento o il rimpicciolimento della pagina, con il
metodo preventDefault(), e a impostare le variabili definite in precedenza a true:
N.B. Allinterno del metodo update, una volta gestito levento, dovremmo ricordarci di reimpostare
a false queste variabili per evitare di gestire i singoli casi pi di una volta!
A questo punto siamo in grado di predisporre il nostro metodo update per gestire i singoli eventi:
-

Verificheremo lo stato della variabile pinching, se essa true significa che lutente ha le dita
sullo schermo e sta effettuando il movimento, in questo caso dovremo solo monitorare la
distanza delle dita ed eseguire qualche animazione sulla mongolfiera
Se invece lo stato della variabile pinched a true, significa che lutente ha appena rilasciato
le dita dal dispositivo, e che ha completato il pinch, in questo caso dovremmo misurare la
distanza compiuta dalle dita, preparare il dato per essere inviato al server e infine muovere
di conseguenza la mongolfiera.
Se infine la variabile pinchout a essere true, bisogner comportarsi in maniera analoga ma
muovere la mongolfiera nella direzione opposta.
48

Ecco il codice del metodo update aggiornato:


this.update = function(self){
// There can be a pinch only if there are two fingers on the screen
if(touches.length === 2){
[...]
// Check if user is pinching
if(pinching === true){
// User is still pinching, simply monitor the finger distance
// and play some animation like flames under the balloon
console.log("Pinching");
pinching = false;
}
}
else{
self.fing1.show = false;
self.fing2.show = false;
// Check if user pinched, we check this here, because
// when the pinched event is triggered
// fingers are not on the screen anymore!
if(pinched === true){
// Pinch is complete: record finger distance and movement data
// than make the player climb
console.log("Pinched");
pinched = false;
}
// Check if user pinched-out
if(pinchout === true){
// Pinch-out is complete: record finger distance and movement data
// than make the player descend
console.log("Pinched-Out");
pinchout = false;
}
}
[...]
}

Come possibile intuire, il gioco non ancora cambiato nel suo aspetto, ma ora quando vengono
compiuti i movimenti richiesti, su console vengono stampate le scritte Pinching, Pinched e
Pinched-Out.
Prima di far muovere la mongolfiera ci rimane un problema da risolvere: quello della misurazione
della distanza tra le dita in cm.
7.f ii - MISURARE LA DISTANZA DELLE DITA SULLO SCHERMO
Essendo HotAirBalloon un gioco per la riabilitazione, abbiamo necessit di inviare, a fine sessione, al
terapista informazioni riguardo le prestazioni del paziente.
In un gioco in cui lunico movimento richiesto quello di avvicinamento e allontanamento delle dita,
la capacit di misurare in tempo reale la distanza fisica tra le dita in qualsiasi istante
fondamentale.
Vi per una sostanziale differenza, in un applicazione di questo tipo, tra distanza fisica e
virtuale tra due punti:

49

Per distanza fisica intendiamo la distanza effettiva reale tra le dita che stanno toccando la
superficie dello schermo.
- Per distanza virtuale intendiamo, invece, la distanza, in pixel, che viene calcolata tra i due
punti identificati allinterno del canvas.
facile intuire come mentre la distanza fisica sia una distanza assoluta tra due punti nello spazio
reale, la distanza virtuale dipenda strettamente dal dispositivo utilizzato:
Posizionando due dita alla medesima distanza fisica su due schermi a risoluzione differente
otterremo due distanze in pixel completamente diverse, in particolare il dispositivo a risoluzione
maggiore generer una distanza virtuale tra i due punti pi ampia di quello a risoluzione minore.
Dato fondamentale per la conversione tra distanza virtuale e distanza fisica, quello fornito dai DPI
(Dots Per Inch) dello schermo del dispositivo.
Come suggerito dal nome, questo valore ci comunica quanti punti, o meglio pixels sono racchiusi
nella distanza di un pollice (2.54 cm) sul dispositivo corrente.
Conoscendo questo dato, la formula per la conversione da distanza virtuale a distanza fisica tra
due punti immediata:
() = 2.54
Il problema che ci si pone a questo punto : come recuperare i DPI dello schermo del client in
unapplicazione web utilizzando javascript?
Purtroppo, non esiste una funzione comoda da poter richiamare per ottenere questo dato:
Una soluzione parziale ci fornita dalla possibilit di definire in CSS propriet quali la larghezza di un
elemento, come un <div>, uno <span> o unimmagine: <img>, in cm tramite lattributo width:
div{
width: 1cm;
}

Sapendo poi che la funzione jquery width(); restituisce la larghezza dellelemento selezionato in
pixel, sarebbe sufficiente scrivere una funzione getScreenDPI(); che crei proceduralmente un div
nascosto di dimensione 1cm o un 1in, legga il numero di pixel restituiti dalla funzione width() sul
nuovo div, elimini il div appena creato e restituisca il valore.
In questo modo potremmo sapere quanti pixel largo un div di 1inch, o di 1 cm, sostanzialmente
avremmo ottenuto il valore dei dpi dello schermo.
function getScreenDPI(){
var dpi = 0;
// Create an empty div 1 inch wide
var tmpDiv = document.createElement("div");
tmpDiv.style.display = "none";
tmpDiv.style.width = "1in";
// Get the div width in pixel
dpi = $(tmpDiv).width();
// Remove the div
$(tmpDiv).remove();
return dpi;
}

50

Provando questa funzione su schermi con DPI noti, si noter per che il valore restituito, non
corrisponder agli effettivi DPI dello schermo, generalmente la funzione ci fornir come valore o 72
o 96 DPI per qualsiasi dipositivo che utilizzeremo.
Stando alle specifiche fornite dal produttore, un tablet come liPad di prima generazione dotato di
uno schermo a 132 DPI e nuovi schermi come quelli di cui sono dati gli iPad di ultima generazione
ne hanno molti di pi!
Non esistendo ad oggi uno standard per definire le dimensioni di un oggetto html in cm o in pollici,
i browser, tendono a mentire sui reali DPI dello schermo, generalmente impostano un valore pari
a 96 DPI per i dispositivi con schermo standard e utilizzano un moltiplicatore per variare questo
valore, su schermi ad alto numero di DPI, come gli schermi retina dei nuovi iPad.
Questo significa, che in realt in HTML, quando si definisce la dimensione di un oggetto in cm, non
detto che ci che verr creato, sar effettivamente della dimensione richiesta!
In questo gioco, per, fondamentale poter misurare con accuratezza la distanza fisica delle dita del
paziente, per altro, si cercato di ovviare a questo problema, aggiungendo un campo, nella
schermata di configurazione del gioco utilizzata dal terapista che vedremo in seguito, in cui
possibile scegliere il dispositivo del paziente da una lista di dispositivi supportati (a ogni dispositivo
associato il corrispondente valore di DPI dello schermo, preso dalla scheda tecnica fornita dal
produttore).
Quando il file di configurazione verr letto dal gioco, in fase di avvio, verr impostato il valore DPI
corretto per il dispositivo utilizzato in un campo dedicato della nostra classe principale; lintero
processo di trasferimento dei dati di gioco dal pannello di configurazione al gioco verr trattato nel
capitolo 8.
Ora che siamo in possesso di questa importante informazione possiamo scrivere una funzione che
misuri la distanza attuale tra le dita del paziente, in funzione dellultimo valore registrato prima dello
scatenarsi dellevento pinched (ovvero levento che stabilisce quando un paziente ha staccato le
dita dallo schermo), potremo validare linput secondo i valori richiesti dal terapista.
7.f iii - VALIDARE LINPUT DELLUTENTE
Per prima cosa scriviamo una funzione che ci permetta di calcolare comodamente la distanza tra due
punti e la converta in centimetri, questo ci permetter di poter confrontare la distanza ottenuta con
la distanza richiesta dal terapista.
var INCH2CM = 2.54;
function pointDistance(x1,y1,x2,y2 dpi){
var d1 = Math.pow((x1 - x2),2);
var d2 = Math.pow((y1 - y2),2);
// This calculates the distance between two points in inches
// and convert it to centimetres
var dist = Math.sqrt(d1 + d2)/dpi * INCH2CM;
}

Il calcolo di questa distanza ottenuto applicando banalmente il teorema di Pitagora, ottenendo


cos la distanza in Pixel, che poi viene convertita in pollici dividendo per i DPI dello schermo, quindi
in cm moltiplicando per la costante 2.54.
A questo punto assumiamo di avere recuperato dal file di configurazione dellutente, che vedremo
in seguito come viene generato, i valori di apertura e chiusura massimi delle dita impostati dal
terapista.
51

Quello che dobbiamo verificare ora :


-

Se stato scatenato levento pinched, ovvero il pinch corretto terminato, e la distanza


iniziale delle dita nel pinch corrente >= della distanza di apertura massima richiesta e la
distanza finale delle dita <= della distanza di chiusura massima richiesta, allora il pinch
valido altrimenti non lo .
- In maniera analoga ma con valori opposti ci si comporta per levento pinchout: se la
distanza delle dita a inizio del movimento era <= della distanza di chiusura richiesta e la
distanza a fine movimento >= della distanza di apertura richiesta, allora il pinch-out valido.
Ecco questo controllo espresso nel codice del metodo update:
Per prima cosa dobbiamo registrare la distanza di partenza delle dita per fare ci abbiamo bisogno
di una variabile booleana, che chiameremo pinchStarted e due variabile di tipo float o double:
currPinchDistance, per mantenere in memoria a ogni iterazione la distanza attuale delle dita, e
currPinchStartDistance, per mantenere in memoria la distanza di partenza del pinch corrente:
var pinchStarted = false;
var currPinchDistance = 0;
var currPinchStartDistance = 0;

Ora allinterno del metodo update, se ci sono due dita sullo schermo, e la variabile pinching a true,
ovvero c una gesture in corso, dobbiamo verificare che il pinch non sia gi stato iniziato in un loop
precedente, se non lo stato: ovvero pinchStarted ancora a false, dobbiamo registrare la distanza
iniziale del pinch corrente:
this.update = function(self){
[...]
currPinchDistance = pointDistance(touches[0].screenX, touches[0].screenY,
touches[1].screenX, touches[1].screenY, dpi);
// Check if user is pinching
if(pinching === true){
if(pinchStarted === false){
currPinchStartDistance = currPinchDistance;
pinchStarted = true;
}
pinching = false;
}
[...]
}

Ricordiamo che touches una lista contenente tutti i punti di contatto con lo schermo nel frame
attuale!
Ora ci chiediamo: quando finisce una gesture?
Quando viene scatenato o un evento pinch o un evento pinchout.
Ora abbiamo quindi bisogno di altre due variabili:
var pinchEnded = true;
var currPinchEndDistance = 0;

52

Anche in questo caso, la prima serve a capire se abbiamo gi gestito o meno levento di pinch
concluso e la seconda serve a conservare temporaneamente la distanza di chiusura delle dita
registrata.
A questo punto sia allinterno dellif dedicato al controllo dello stato della variabile pinched, che
in quello dedicato al controllo della variabile pinchout, dovremo verificare lo stato della variabile
pinchEnded, se essa true, allora dovremo registrare il valore attuale di currPinchDistance in
currPinchEndDistance, impostare a false la variabile pinchEnded e reimpostare la variabile
pinchStarted per preparare il sistema a leggere il pinch successivo.
Vediamo il tutto nel caso pinched, per il caso pinchout il codice del tutto analogo:
this.update = function(self){
// There can be a pinch only if there are two fingers on the screen
if(touches.length === 2){
[...]
}
else{
[...]
// fingers are not on the screen anymore!
if(pinched === true){
// Pinch is complete: record finger distance and movement data
// than make the player climb
if(pinchEnded === false){
currPinchEndDistance = currPinchDistance;
pinchEnded = true;
pinchStarted = false;
currPinchDistance = 0;
}
pinched = false;
}
[...]
}

A questo punto abbiamo salvato in variabili temporanee i valori di apertura e chiusura del pinch
registrato: non ci resta che salvarli in un contenitore, un vettore, sia che essi siano ritenuti validi
(ovvero allinterno dei parametri impostati dal terapista), sia che non lo siano, per essere inviati a
fine sessione al server con lo scopo di generare le opportune statistiche da mostrare al terapista.
Dopo di che potremo confrontarli con i valori richiesti dallesercizio per stabilire se fare muovere la
mongolfiera, oppure tenerla ferma generando un opportuno feedback allutente.

53

// Save current pinch values, we will send all


// data to the server at the end of the session
pinchStartArray.push(currPinchStartDistance);
pinchStopArray.push(currPinchEndDistance);
if(currPinchStartDistance >= currPinchEndDistance){
// Then we had a pinch-in, we should make the balloon climb
if(currPinchStartDistance >= pinchRequiredStartDistance &&
currPinchEndDistance <= pinchRequiredStopDistance){
// Pinch is valid! Move the balloon up.
player.move("up");
}
else{
// Pinch is invalid! Notify user
[...]
}
}
else{
// We had a pinch-out, we should make the balloon descend
if(currPinchStartDistance <= pinchRequiredEndDistance &&
currPinchEndDistance >= pinchRequiredStartDistance){
// Pinch-out is valid! Move the balloon up.
player.move("down");
}
else{
// Pinch is invalid! Notify user
[...]
}
}

Il codice estremamente semplice in questo caso, si tratta semplicemente di confrontare i valori


registrati con i valori richiesti dal terapista per validare il movimento, da notare come per il pinchout il valore di partenza delle dita venga confrontato con il valore di chiusura impostato dal terapista,
questo viene fatto perch il movimento di allargamento delle dita parte, ovviamente a dita chiuse!

7.g - HOT AIR BALLOON STRUTTURA DEL GIOCO E GAMEPLAY


Quanto descritto finora servito a gestire correttamente linput dellutente, rilevando con
accuratezza la distanza tra le dita e salvando i dati ottenuti in opportune variabili.
Il gioco per cos ha ancora poco significato, abbiamo una mongolfiera che pu salire e scendere
sullo schermo, ma in realt non c nulla da fare: bene perci spendere qualche parola nel
descrivere come il gioco finale stato strutturato, sia dal punto di vista di calibrazione, registrazione
e salvataggio dei dati dellesercizio, sia dal punto di vista del gameplay vero e proprio, ovvero di ci
che lutente dovr fare per completare una partita.
Il gioco in s strutturato in 3 fasi.
FASE 1 DECOLLO E CALIBRAZIONE
Questa la fase iniziale del gioco, la mongolfiera si trova a terra nella parte centrale dello schermo,
e lutente dovr effettuare alcuni pinch sullo schermo per far iniziare a salire la mongolfiera, scopo
di questa fase far salire la mongolfiera tanto da superare il bordo superiore dello schermo.
Dietro le quinte, in questa fase, viene eseguito, su richiesta del terapista il sistema di autocalibrazione (di cui discuteremo meglio in seguito), utilizzato per stabilire la distanza massima e
minima di pinch richiesta nellesercizio nel caso in cui il terapista non labbia preimpostata nella
configurazione.
FASE 2 VOLO E DATA-COLLECTION

54

Questa la fase principale del gioco, ha durata variabile da 1 a 5 minuti, la lunghezza dellesercizio
impostabile dal terapista in fase di configurazione.
Lutente in questa fase si ritrova in una scena diversa, lo sfondo un cielo che scorre dando la
sensazione che la mongolfiera si stia spostando orizzontalmente.
Eseguendo dei movimenti di pinch-in la mongolfiera sale, mentre eseguendo movimenti di pinchout la mongolfiera scender, lo scopo di questa fase duplice: evitare degli uccellini che volano
incontro alla mongolfiera e raccogliere delle monetine che casualmente compariranno sullo
schermo.
Il punteggio viene assegnato in base al tempo di volo trascorso senza nessun impatto con un uccello,
ogni impatto porta a un decurtazione di 5 punti dal punteggio, mentre la collezione di una moneta
un bonus di 50 punti.
Durante questa fase, il sistema registra tutti i pinch effettuati in vettori dedicati, come mostrato nel
capitolo precedente, viene inoltre salvato il numero di pinch effettuati e la percentuale di pinch validi
e invalidi. I dati non verranno inviati al server fino al completamento dellesercizio e il conseguente
inizio della fase 3.
FASE 3 ATTERRAGGIO E INVIO DEI DATI DI SESSIONE AL SERVER
Questultima fase parte allo scadere del tempo di gioco impostato, la mongolfiera scende da sola
verso la parte inferiore dello schermo, la scena cambier nuovamente e scopo dellutente sar quello
di cercare di fare atterrare dolcemente la mongolfiera al suolo effettuando dei pinch per rallentarne
la caduta.
I tipi di atterraggi rilevati sono 3:
- Atterraggio duro: non vengono attribuiti punti bonus
- Atterraggio morbido: vengono assegnati 100 punti bonus
- Atterraggio molto morbido: vengono assegnati 200 punti bonus
A seguito dellatterraggio viene mostrato per qualche secondo il punteggio iniziale e viene
automaticamente rilanciato il gioco dalla fase 1 per una seconda sessione.
La fase di atterraggio, in realt una fase che maschera linvio asincrono dei dati di sessione rilevati
durante la fase 2 al server. Durante latterraggio il sistema infatti richiama una procedura il cui scopo
preparare i dati per linvio ed eseguire un HTTP Request verso una procedura server side, che si
occuper di salvare su file i dati della sessione.
Ora che abbiamo chiaramente delineato la struttura finale del gioco, possiamo ad analizzare gli
elementi chiave dello sviluppo che non sono ancora stati trattati, ovvero:
-

Gestione delle fasi di gioco.


Animazioni: in particolare Sprite Animation (per gli uccelli e le esplosioni da impatto) e
scrolling infinito dello sfondo. (per il cielo)
Collision Detection.
Feedback e suggerimenti allutente. (Ovvero come suggerire allutente dove deve arrivare
il suo pinch affinch sia ritenuto valido o come far comparire scritte e immagini sullo
schermo per descrivere determinati eventi).
Sistema di auto-calibrazione.
Invio dei dati di gioco al server.

55

7.h - HOT AIR BALLOON FASI DI GIOCO


Nella sezione precedente abbiamo descritto come il gioco sia stato strutturato in 3 fasi, ognuna di
esse prevede variazioni allinterno del gioco, sia grafiche, sia in termini di funzionalit e problemi da
gestire.
Ci serve per tanto un modo di distinguere le varie fasi, di rilevare il momento in cui eseguire la
transizione da una fase allaltra, gestire eventuali animazioni ecc.
Per fare ci, in questo progetto, ci si avvalsi di una variabile, di tipo stringa, visibile allinterno
dellintera classe Game.
Essa assume, per ogni sessione di gioco tre valori:
- takeoff, la fase 1 del capitolo precedente
- flying, la fase 2
- landing, la fase 3.
La classe Game, inizializza la variabile al valore takeoff nel costruttore, importante identificare i
momenti in cui effettuare la transizione da una fase allaltra.
Per quanto riguarda il passaggio da takeoff a flying, si deciso di controllare semplicemente la
posizione della mongolfiera, se essa supera il bordo superiore dello schermo (ovvero la posizione del
giocatore sullasse Y minore di 0), si passa alla fase flying.
La fase flying inizia con unanimazione che posiziona la mongolfiera nel punto pi comodo per
iniziare a giocare: vicino al bordo sinistro dello schermo, il pi lontano possibile dal luogo da cui
cominceranno a comparire gli uccellini da evitare, una volta in posizione, viene avviato il timer di
gioco.
Lultima fase la fase di landing, avviata allo scadere del timer di gioco, anche questo
cambiamento di fase provoca lavvio di unanimazione che sposta la mongolfiera verso il bordo
inferiore dello schermo e cambia sfondo, ad animazione terminata il controllo viene restituito
allutente per latterraggio.
Oltre alla variabile gamestatus, sar dunque essenziale unulteriore variabile, questa volta booleana,
per distinguere le animazioni dalle vere e proprie fasi di gioco.

takeoff

animation

flying

animation

landing

56

this.update = function(self){
if(animation === true){
// Handle animations here
animation = false;
}
else{
if(touches.length === 2){
[...]
}
else{
[...]
if(pinched === true){
console.log("Pinched");
if(gamestatus === "takeoff" || gamestatus === "landing"){
// Handle pinch in takeoff or landing status
}
if(gamestatus === "flying"){
// Handle pinch in flying status
}
pinched = false;
}
if(pinchout === true){
[...]
}
}
// Status transition: takeoff -> flying
if(player.pos_y < 0 && gamestatus === "takeoff"){
gamestatus = "flying";
animation = true;
}
// Status transition: flying -> landing
if(self.eta.getTime() === 0){
gamestatus = "landing";
animation = true
}
self.draw(self);
}
}
this.draw = function(self){
self.ctx.clearRect(0,0,window.innerWidth, window.innerHeight);
self.ctx.beginPath();
switch(gamestatus){
case "takeoff":
// Draw all elements from takeoff mode
if(takeoffBgReady === true){
self.ctx.drawImage(self.takeoffbg ,0,0,
window.innerWidth, window.innerHeight);
}
break;
case "flying":
// Draw all elements from flying mode
break;
case "landing":
// Draw all elements from landing mode
break;
}
[...]
}

57

Ovviamente controllando lo stato delle variabili gamestatus e animation allinterno dei metodi
update e draw della classe di gioco, possiamo decidere come fare comportare lapplicazione.
Come possibile osservare dal codice abbiamo inserito un macro-controllo allinterno del metodo
update, nel quale controlliamo, prima di qualsiasi altra cosa se al momento attiva un animazione
o meno.
possibile osservare, come ad esempio, in caso di animazione venga ignorato completamente
linput dellutent e come, nel caso di pinching, vengano distinti casi diversi a seconda dello stato di
gioco, per generare comportamenti diversi a seconda della situazione
Per quanto riguarda il metodo draw, stato aggiunto uno switch per il controllo del gamestatus,
allinterno dei singoli case vengono disegnati solo i componenti da mostrare nel singolo stato di
gioco, mentre fuori dallo switch sono presenti solo le istruzioni per disegnare elementi comuni a
tutti gli stati, come ad esempio il giocatore.

7.i - HOT AIR BALLOON ANIMAZIONI


Altro elemento fondamentale che caratterizza qualsiasi videogioco, sia esso bidimensionale o
tridimensionale, quello delle animazioni.
Nel nostro caso specifico, per animazione, intendiamo un qualsiasi movimento o effetto ottico su
un qualsiasi game object effettuato senza necessaria interazione dellutente da un qualsiasi
elemento di gioco:
- Il volo degli uccellini
- La rotazione su se stesse delle monetine
- Le esplosioni dovute allimpatto degli uccellini con la mongolfiera
- Lo scorrere automatico del cielo sullo sfondo.
- Il fuoco che viene emesso dal bruciatore della nostra mongolfiera a ogni pinch dellutente.
Per realizzare queste animazioni sono state utilizzate diverse tecniche a seconda del tipo di effetto
desiderato, analizziamole una per volta.

UCCELLI, MONETINE ED ESPLOSIONI SPRITESHEET ANIMATION


Per il movimento degli uccellini da evitare e delle monetine da prendere stata utilizzata una tecnica
di animazione denominata spritesheet animation.
Essa consiste nel generare, utilizzando un programma di photo-editing quali ad esempio Adobe
Photoshop o GIMP, delle spritesheets: ovvero delle immagini allungate divise in blocchi di larghezza
e altezza fissate, in cui viene disegnato il protagonista della nostra animazione nei vari frame che le
compongono, ecco un esempio:

58

Lanimazione, a questo punto teoricamente molto semplice e consiste nel disegnare a ogni frame
una porzione diversa della nostra spritesheet in modo tale che al primo frame disegnato, venga
disegnato il primo riquadro della spritesheet, al secondo frame il secondo, al terzo il terzo e cos via
fino allultimo, per poi ricominciare dallinizio, con una frequenza di aggiornamento
sufficientemente rapida, si dar lillusione allutente che il protagonista della nostra animazione stia
muovendosi, quando in realt stiamo semplicemente sostituendo la sua immagine molto
velocemente.
Vediamo quindi come a partire da uno Spritesheet di questo tipo si sia realizzata lanimazione degli
uccellini allinterno di HotAirBalloon:
Per prima cosa necessario definire una classe che rappresenti un uccellino sullo schermo, la
chiamiamo Bird, questa classe avr tutte le propriet della classe Player, quindi posizione,
scalatura, altezza, larghezza ecc. le variazioni principali riguarderanno il metodo draw:
Innanzi tutto analizziamo i due overload possibili del metodo drawImage della classe context
associata al nostro canvas:
-

Il primo, quello che utilizziamo nel caso di disegni semplici, in cui vogliamo disegnare
lintera immagine in una posizione stabilita allinterno del canvas:
ctx.drawImage(img, x, y, width, height);

Prende come parametri limmagine da disegnare, e il rettangolo entro cui limmagine dovr
essere disegnata, se limmagine ha dimensioni superiori o inferiori alla dimensione del
rettangolo specificato, allora verr deformata per rientrare in quelle dimensioni.
-

Il secondo overload quello che serve per realizzare spritesheet animations allinterno di un
canvas html:
ctx.drawImage(img, sx, sy, swidth, sheight, x, y, width, height);

In questo caso il metodo prende come primo parametro limmagine da disegnare, nel nostro
caso lintera spritesheet, sx e sy sono le coordinate da cui iniziare il disegno nellimmagine
sorgente, in parole povere le coordinate del riquadro dello spritesheet da disegnare al frame
corrente, swidth e sheight, le dimensioni del singolo riquadro, mentre x, y, width e height
stabiliscono posizione e dimensione del riquardo da disegnare sul canvas.
Come possibile osservare, con questo secondo overload del metodo drawImage possibile
disegnare singole porzioni di immagini, vediamo come utilizzarlo nel caso specifico degli uccellini.

59

Innanzi tutto bene conoscere alcune propriet dello spritesheet generato con il programma di
photo editing, in particolare vogliamo conoscere:
- Le dimensioni dellintero spritesheet
- Le dimensioni di un singolo riquardo allinterno dello spritesheet
- Il numero di riquadri per riga
- Il numero di righe e di colonne nel singolo spritesheet
- La direzione in cui la sprite va letta (da sinistra verso destra o da destra verso sinistra)
Salviamo queste informazioni allinterno di opportune variabili della classe Bird come mostrato di
seguito:
function Bird(spritesheet, width, height,
slides_row, slides_col, total_slides, sprite_dir){
[...]
// Calculate single slide width and height
var sprite_width = width / slides_row;
var sprite_height = height / slides_col;
var slide_counter = 1;
[...]
// Define two different draw methods depending on the direction
// from which the sprite should be read.
if(sprite_dir === "r"){
var curr_slide = 0;
var curr_col = 0;
this.draw = function(ctx){
[...]
}
}
else{
var curr_slide = slides_row - 1;
var curr_col = 0;
this.draw = function(ctx){
[...]
}
}
}

Come possibile osservare, vengono distinti due casi, se la spritesheet va letta da sinistra verso
destra -> sprite_dir === r allora definiamo un metodo draw, altrimenti ne definiamo un altro, i
metodi in realt sono speculari, ci che cambia la direzione del ciclo con cui si scorrono le singole
slides.
Per comodit osserviamo solo il primo caso, il secondo speculare:
-

Disegneremo innanzi tutto la porzione del nostro spritesheet in posizione:


( , )
La porzione della spritesheet da disegnare sara data dalle variabili sprite_width e
sprite_height.
60

E la dimensione dello sprite sul canvas sar data ancora dalle variabili sprite_width e
sprite_height per il fattore scale della classe Bird che ci permetter di ridimensionare
facilmente gli uccellini sullo schermo.
- Una volta disegnato il frame corrente, dovremmo preparare le variabili per disegnare il
prossimo frame:
o Incrementeremo il numero della slide corrente
o Verificheremo di non aver superato il numero di slide per riga, in caso affermativo
resetteremo il contatore a 0
o Verificheremo di non aver superato il numero di slide per colonna, anche in questo
caso resetteremo il contatore a 0
o Infine incrementeremo il numero di slide disegnate, e verificheremo di non aver
superato il numero di slide totali nella spritesheet, anche in questo caso dovremo
resettare tutte le variabili per riprendere a disegnare dallangolo superiore sinistro
della spritesheet.
Ecco, per completezza il codice del metodo draw della classe Bird:
this.draw = function(ctx){
ctx.save();
// Draw current slide
ctx.drawImage(this.spritesheet,
sprite_width * curr_slide, sprite_height * curr_col,
sprite_width, sprite_height,
sprite_width * scale, sprite_height * scale
);
// Move the counter forward
curr_slide += 1;
// Check if we reached the end of the row
if(curr_slide === slides_row){
curr_slide = 0;
curr_col += 1;
// Check if we reached the end of the column
if(curr_col === slides_col){
curr_col = 0;
}
}
slide_counter += 1;
// Check if we reached the end of the spritesheet
// We need to do this if the last row has not enough slides
if(slide_counter > total_slides){
slide_counter = 1;
curr_slide = 0;
curr_col = 0;
}
ctx.restore();
}

In modo analogo si generata anche lanimazione delle monetine e delle esplosioni sullo schermo.
Ecco una sequenza di screenshot tratta da una sessione di gioco per mostrare leffetto ottenuto.

61

ANIMAZIONI SFONDO SCORREVOLE


Ora che abbiamo visto come stata implementata la tecnica delle spritesheets animations in
HotAirBalloon, passiamo ad analizzare un altro tipo di animazione utilizzato: si tratta dellanimazione
dello sfondo.
Il concetto che sta dietro a questo tipo di animazione quello di dare allutente la sensazione di
movimento orizzontale, quando in realt la mongolfiera rimane ferma in un punto preciso dello
schermo, come si fa ad ottenere questo effetto?
Semplicemente utilizzando uno sfondo di dimensioni superiori a quelle del canvas su cui viene
utilizzato, e disegnando, a ogni frame, una porzione differente dellimmagine.
Ecco uno schema di ci che si vuole cercare di fare:

Come possibile osservare dallimmagine, i riquadri colorati rappresentano il nostro canvas, quello
che si fatto stato disegnare una porzione dellimmagine grande quanto il canvas attuale, a partire
da una posizione (lungo lasse X) diversa e incrementale a ogni frame, dando cos lillusione di
movimento.
Questo obbiettivo facilmente raggiungibile utilizzando una variabile di posizione sullasse X, che
chiameremo:
var frame_pos_x = 0;

E lo stesso overload del metodo drawImage utilizzato nelle spritesheet animations; eseguendo
quindi a ogni frame il seguente codice, otterremo il risultato voluto:
ctx.drawImage(this.sprite,
frame_pos_x, 20, ctx.canvas.width, ctx.canvas.height,
0,0,ctx.canvas.width, ctx.canvas.height);
frame_pos_x = frame.pos_x + 10;

Esiste per un problema: cosa succede quando incrementando di 10 px in 10 px il valore di


frame_pos_x ci ritroveremo a disegnare un box che parte da un punto tale per cui avremo:
+ >
o, in termini, pi semplici, quando il nostro riquadro superer la dimensione dellimmagine
sorgente?

62

In questo caso, la soluzione consiste, nello spezzare il disegno in due parti:


-

La prima parte sfrutter la porzione in fondo allimmagine sorgente, che ci rimasta da


disegnare, e verr disegnata dal punto (0,Y) al punto (0 + parte rimanente, Y) del canvas.
- La seconda parte, verr disegnata dal punto (0 + parte rimanente + 1, Y) alla fine del box, e
prender la porzione di immagine da disegnare, dal punto iniziale dellimmagine sorgente.
Di seguito ecco una rappresentazione grafica delleffetto che si cercato di ottenere in questo modo.

Ed il codice completo del metodo draw per la classe AnimatedBackground:

63

this.draw = function(ctx){
ctx.save();
if(reachingEnd === true){
/* The first part of the background is the last visible *
* part of the sprite */
var first_part_width = this.sprite.width - frame_pos_x;
var remaining_part = ctx.canvas.width - first_part_width;
ctx.drawImage(this.sprite,
frame_pos_x, 20, first_part_width, ctx.canvas.height,
0,0,first_part_width, ctx.canvas.height);
/* The last part is the first part of the sprite */
ctx.drawImage(this.sprite,
0, 20, remaining_part, ctx.canvas.height,
first_part_width, 0, remaining_part,
ctx.canvas.height);
}
else{
ctx.drawImage(this.sprite,
frame_pos_x, 20, ctx.canvas.width, ctx.canvas.height,
0,0,ctx.canvas.width, ctx.canvas.height);
}
if (this.loop === true){
frame_pos_x = frame.pos_x + 10;
}
/* If nex portion of image excedes the source sprite width,
*
* we need to draw both the end and the beginning of the sprite */
if(frame_pos_x + ctx.canvas.width > this.sprite.width){
reachingEnd = true;
}
/* If we reached the end of the sprite, we can restart *
* from the beginning */
if(frame_pos_x >= this.sprite.width){
frame_pos_x = 0;
reachingEnd = false;
}
ctx.restore();
}

Come possibile osservare dal codice, nel caso in cui reachingEnd settato a true, ovvero quando
la dimensione del riquadro da disegnare eccede la larghezza dellimmagine sorgente, effettuiamo
due chiamate al metodo drawImage, consecutive, la prima per disegnare la prima parte
dellimmagine, la seconda per la seconda parte.
Ovviamente quando la posizione frame_pos_x, ovvero la posizione del punto da cui iniziamo a
disegnare (in parole povere, langolo superiore sinistro del disegno) avr raggiunto o superato la
larghezza dellimmagine sorgente, potremo ricominciare ad eseguire una sola draw, ripartendo dal
punto con ascisse 0. Questo ci che viene controllato dallultimo if della funzione.
Degno di nota il controllo della propriet della classe loop: come possibile osservare, la posizione
del nostro box di riferimento viene incrementata, unicamente quando questa propriet settata a
true, ci limita lavvio o la fermata dellanimazione al semplice assegnamento di un valore diverso a
questa propriet.

64

7.j - HOT AIR BALLOON GAMEPLAY: MOVIMENTO DEI NEMICI; MONETE E


COLLISION DETECTION
Ora che abbiamo visto come sono state realizzate le animazioni principali allinterno del gioco e
come sia stata gestita lintroduzione di nuovi attori allinterno della scena, il momento di pensare
a rendere il gioco divertente ed analizzare il gameplay.
Vediamo quindi come vengono gestiti i nemici, le monete bonus, le collisioni tra il giocatore e questi
elementi, e come ci infici sul punteggio del giocatore.
Come abbiamo detto in precedenza, la fase centrale di gioco caratterizzata dalla mongolfiera del
giocatore posizionata vicino al bordo sinistro dello schermo, e i vari pinch le permettono di prendere
o perdere quota a seconda delle necessit.
Scopo del gioco evitare gli uccellini e prendere le monetine.
Iniziamo con i nemici, bisogner eseguire i seguenti passi:
-

Istanziare almeno un nemico allinizio della fase di volo.


Posizionarlo oltre il bordo destro dello schermo sullasse X, ma ad unaltezza casuale (asse
Y) in modo che non sia prevedibile il punto da cui esso comparir.
- Per differenziare il gioco potremo regolare in maniera casuale anche dimensione, colore e
velocit delluccellino.
- Rilevare il momento in cui luccellino sparisce dallo schermo, ci sono due possibili casi:
o Luccellino supera la mongolfiera del giocatore e vola dietro alla parte sinistra dello
schermo
o Luccellino impatta sulla mongolfiera facendo perdere punti al giocatore
In entrambi i casi dovremo gestire il respawn del game object.
- Per rendere la difficolt di gioco incrementale, bisogna escogitare un sistema che, in
funzione del gametime impostato dal terapista per il singolo esercizio, faccia incrementare
il numero di uccellini sullo schermo in maniera lineare.
Iniziamo da tutta la logica di inizializzazione:
var
var
var
var

birds = [];
max_birds_on_screen = 0; // Start with one bird
max_bird_number = 5; // Max 5 birds at the end of the exercise
bird_increase_frequency = 60000; // 1min

/* Preload birds */
for(var i = 0; i < max_bird_number; i++){
var bird = new Bird(
["sprites/red.png", "sprites/blue.png",
"sprites/green.png", "sprites/yellow.png",
"sprites/purple.png"],
918,506,5,3,14,"l");
birds.push(bird);
}

Come possibile osservare, viene definito un numero massimo di nemici possibili, il numero di
nemici sullo schermo e la frequenza con cui si devono aggiungere nemici. Viene definito un semplice
array in cui mantenere le istanze degli oggetti bird, quindi allinterno di un ciclo si precaricano gli
uccellini, in modo da poterli utilizzare quando necessario, senza doverli reistanziare.

65

Da notare come il cotruttore della classe Bird, prenda come primo parametro un vettore di stringhe,
ogni stringa rappresenta il percorso a una spritesheet di colore diverso, questo viene fatto, per poter
permettere di cambiare colore delluccellino a runtime, passando da una spritesheet ad unaltra.
Ora che abbiamo preparato i nostri uccellini, dobbiamo modificare il metodo update per farli
muovere.
this.update = function(){
[...]
for(var i = 0; i < max_birds_on_screen; i++){
var bird = birds[i];
if(bird.alive === true){
// This moves the bird to the left
bird.update("l");
// Check if bird is off screen
if(bird.pos_x <= bird.width * -1){
bird.alive = false;
}
// Check for collision
if(boxCollided(player, bird)){
bird.alive = false;
// Explosion animation
[...]
// Update points & feedback user...
[...]
}
}
else{
/* Respawn logic */
// Hide the bird beyond the right side of the screen
bird.pos_x = self.canvas.width + 100;
bird.pos_y = Math.floor(Math.random() * (self.canvas.height bird.width));
// Randomize size
bird.scale = Math.random() / 2 + 0.3;
// Randomize color
bird.spritesheet.src = bird.spritesheets[Math.random() *
bird.spritesheets.length << 0];
// Randomize bird speed
bird.speed_x = Math.floor(Math.random() * 10) + 5;
}
}
[...]
// Also remember to increase birds on screen number every
bird_increase_frequency seconds
setInterval(function(){
if(max_birds_on_screen < max_bird_number){
max_birds_on_screen++;
}
}, bird_increase_frequency);
[...]
}

Come possibile osservare dal codice, la prima cosa che facciamo ciclare sul vettore birds da 0 al
numero massimo di uccellini mostrabili sullo schermo al frame corrente (N.B. Non al numero di
uccellini nellarray, altrimenti ne avremmo il numero massimo fin dal primo frame, e partiremmo
dalla difficolt massima),
66

Per ogni bird, verifichiamo innanzi tutto la propriet alive, se vivo allora dovremmo farlo
muovere, se morto dovremmo riposizionarlo per ricomparire sullo schermo.
Analizziamo i casi uno per volta.
-

Alive a true, innanzi tutto spostiamolo sullo schermo in funzione della sua velocit
utilizzando il metodo update della classe Bird, che altro non fa che spostare, nella direzione
indicata dal parametro, nel nostro caso l: ovvero da destra verso sinistra luccellino in
posizione bird.pos_x bird.speed_x.
Una volta spostato verifichiamo due cose:
o Eventuali collisioni con il giocatore, trattandosi di sprites relativamente piccole, e
non essendo necessario, a questo livello un particolare livello di precisione si
deciso di utilizzare una funzione, boxCollided, che ritorna true, se i rettangoli
costruiti attorno al giocatore e alluccellino si sono intersecati, false altrimenti:
In caso di esito positivo, ci limitiamo a settare a false, la propriet alive del bird
corrente, a lanciare unanimazione di esplosione nella stessa posizione in cui si
rilevata la collisione e a decurtare un certo numero di punti al giocatore
o Verifichiamo la posizione del bird corrente, se essa minore di 0 significa
semplicemente che la sprite ha superato il bordo sinistro dello schermo, e non
quindi pi visibile allutente.
Anche questa volta settiamo a false la propriet alive, ma non eseguiamo alcuna
animazione ne azione particolare, questeventualit significa semplicemente che il
giocatore riuscito ad evitare luccellino.
Alive a false, pu succedere in vari casi:
o Ci troviamo alla prima esecuzione del metodo update dopo essere passati dalla fase
di decollo alla fase di volo.
o Lutente ha colpito luccellino allupdate precedente
o Lutente ha superato luccellino allupdate precedente
o trascorso un tempo superiore a bird_increase_frequency ed stato abilitato un
altro uccellino allinterno del vettore birds.
Qualunque sia la causa per la quale ci troviamo in questa condizione, lunica cosa da
effettuare il respawn delluccellino, ci significa riposizionarlo dietro al bordo destro
dello schermo, scegliere a caso una nuova posizione, scalatura, colore e velocit, e infine
settare a true nuovamente la propriet alive.

Ultima operazione da svolgere allinterno del metodo update, dopo il controllo della
propriet alive di tutti gli uccellini nel vettore, quella di eseguire a intervalli predefiniti una
funzione che abilita, incrementando il valore di max_birds_on_screen, il numero di uccellini
contemporaneamente visibili sullo schermo.
Questo viene fatto dalla funzione passata come parametro a setInterval, nellultima parte
del codice mostrato sopra.
Altri elementi importanti del gioco sono le monetine bonus: queste monetine compaiono
casualmente nella scena e provengono dalla stessa direzione da cui compaiono gli uccellini. Il
giocatore dovr cercare di toccare le monetine con la propria mongolfiera per ottenere 50 punti
bonus.
Scopo di queste monetine quello di stimolare lutente a muovere la mongolfiera invece che tenerla
il pi possibile ferma in un posto dove passano meno uccellini.
Il modo in cui vengono gestite le monetine simile a quello degli uccellini, ma molto pi semplice.

67

Innanzi tutto non abbiamo bisogno di un vettore di monetine, ma di una soltanto: per come stato
pensato il gioco, non potranno mai esserci due monetine contemporaneamente nella scena,
dovremmo per trovare un criterio, per stabilire quando e se disegnare una monetina sullo schermo.
var COIN_X_OFFSET = 100;
var COIN_SPRITE_HEIGHT = 40;
var FRAME_PER_MINUTE = 1500;
// Pick up a random frame to show the coin
var coinShowTime = Math.floor(Math.random() * FRAME_PER_MINUTE);
var frameCounter = 0;
var coin = new Coin(canvas.width + COIN_X_OFFSET, 0);
this.update = function(){
[...]
// Check if we should draw the coin
if(frameCounter === coinShowTime){
// Check if coin is not already onscreen
if(coin.show === false){
// Randomly choose a position and draw the coin
coin.pos_x = canvas.width + COIN_X_OFFSET;
coin.pos_y = Math.random() *
(canvas.height - COIN_SPRITE_HEIGHT) << 0;
coin.show = true;
// Pick up another random frame to show the coin
coinShowTime = Math.floor(Math.random() * FRAME_PER_MINUTE);
}
}
// Increase frame counter every frame
frameCounter++;
// If we passed FRAME_PER_MINUTE reset the counter
if(frameCounter > FRAME_PER_MINUTE){
frameCounter = 0;
}
[...]
}

Si scelto, come possibile osservare, un criterio del tutto casuale per mostrare una monetina sullo
schermo.
Il nostro scopo mostrare almeno una moneta al minuto: innanzitutto impostiamo nella costante
FRAME_PER_MINUTE, il numero di volte in cui viene chiamato il metodo update in un minuto di
gioco.
Nellesempio mostrato abbiamo un frame rate di 25 FPS (Frame al Secondo) per tanto in un minuto,
il metodo update verr chiamato:
2560 = 1500
Generiamo un numero casuale nellintervallo [0, FRAME_PER_MINUTE].
Il numero generato corrisponder al frame esatto in cui dovremo fare comparire la monetina sullo
schermo, lo salviamo nella variabile coinShowTime; non ci resta che contare il numero di update
effettuati, per fare ci, utilizziamo la variabile frameCounter, che conter i frame mostrati
dallinizio alla fine del minuto di gioco corrente, (superato il valore di FRAME_PER_MINUTE viene
resettato a 0).
68

La monetina viene quindi mostrata quando la variabile frameCounter ha lo stesso valore generato
nella variabile coinShowTime.
Una volta mostrata la monetina, viene impostato un nuovo valore per la variabile coinShowTime
generando casualmente un nuovo valore compreso tra [0,FRAME_PER_MINUTE].
Leffetto sar quello di avere la garanzia (100% di probabilit) che almeno una moneta compaia sullo
schermo in un minuto, senza che per sia prevedibile listante esatto in cui accadr.

7.k - HOT AIR BALLOON FEEDBACK E SUGGERIMENTI ALLUTENTE


Parliamo ora di messaggi allutente e feedback corretto, abbiamo visto, nella parte introduttiva,
come sia importante, specialmente per un gioco creato per la riabilitazione che ogni tipo di
movimento sia monitorato costantemente, e un eventuale movimento sbagliato venga corretto
immediatamente.
In un gioco cos semplice come Hot Air Balloon, verrebbe semplicemente in mente di far comparire
sullo schermo messaggi quali allarga di pi le dita, stringile di pi oppure semplicemente
bravo!
In questo caso, per, il gioco stato studiato per pazienti in una fascia di et molto bassa (dai 3 anni
in su), bisogna perci riflettere sul fatto, che molti pazienti, potrebbero non saper leggere!
Bisogna pertanto pensare a un sistema di feedback alternativo, almeno per ci che riguarda il
sistema di input, bisogner essere in grado di comunicare in maniera universalmente comprensibile
almeno le seguenti informazioni:
-

Se le dita sono state rilevate dal gioco.


Pinch eseguito correttamente
Pinch sbagliato
Distanza dove devono arrivare le dita affinch un pinch (sia in chiusura che in apertura) sia
ritenuto valido.
Per quanto riguarda il rilevamento delle dita del gioco, la soluzione trovata stata quella descritta
nel paragrafo sulla gestione dellinput, di mostrare dei pallini colorati in corrispondenza delle dita
per tutto il periodo in cui viene catturato levento pinching, ovvero per tutto il periodo in cui
abbiamo almeno due dita sullo schermo.
Parlando invece di pinch corretto o scorretto, si semplicemente pensato di sostituire scritte quali:
bravo! o sbagliato! con degli smiley colorati comunemente usati nelle applicazioni per bambini
molto piccoli:

Linformazione che invece ha richiesto un po pi di lavoro, stata quella relativa al mostrare la


distanza da raggiungere con le dita per eseguire il tipo di pinch desiderato.
I problemi da affrontare sono stati fondamentalmente due:
69

Come intuire dalla posizione iniziale delle dita quale tipo di pinch vuole effettuare con le
dita.
- Come indicare nella maniera meno invasiva possibile il punto dove fermarsi.
Per il primo problema, stato effettuato un controllo particolarmente elementare:
-

Se le dita al momento dellinizio del pinch si trovano a una distanza superiore o uguale alla
distanza di apertura massima delle dita richiesta dal terapista, allora assumiamo che lutente
stia per eseguire un pinch-in
Se le dita si trovano a una distanza inferiore o uguale alla distanza di chiusura delle dita
richiesta dal terapista, allora assumiamo che lutente voglia effettuare un pinch-out.
Infine se le dita partono da una posizione compresa tra i due valori impostati dal terapista,
allora sicuramente il pinch risultante sar invalido, pertanto comunichiamo fin da subito
lerrore al paziente, colorando i puntatori delle dita (i cerchi che vengono disegnati in
corrispondenza di essi) di rosso.

this.update = function(){
[...]
if(gamestatus === "flying"){
if(currPinchStartDistance >= fingerRequiredStartDistance){
/* Then user is probably trying to do a pinch-in */
// TODO: draw green marks
}
else if(currPinchStartDistance <= fingerRequiredTargetDistance){
/* Then user is probably trying to do a pinch-out */
// TODO: draw blue marks
}
else{
/* If we are here than finger position is invalid already */
// TODO: notify user (RED FINGERS)
}
}
[...]
}

Per risolvere invece il secondo problema si deciso di posizionare delle barrette colorate
perpendicolari al segmento che unisce i due punti di contatto con tra la superficie e le dita sullo
schermo.
Il colore delle barrette impostato a seconda del tipo di pinch che si vuole effettuare:
- Verde per il pinch-in
- Blu per il pinch-out
Il colore dei cerchi in corrispondenza delle dita inizialmente giallo: se lutente, trascinando le dita,
riesce a superare le barrette, allora i cerchi cambieranno colore assumendo lo stesso della barretta
superata e, staccando le dita dallo schermo si otterr un pinch valido.
Il problema pi grosso stato per quello del posizionamento delle barrette in funzione della
posizione delle dita allinizio del movimento.
Vediamo il codice utilizzato per risolvere questo problema nel caso del pinch-in, per il pinch-out
esattamente speculare:

70

if(currPinchStartDistance >= fingerRequiredStartDistance){


/* Calculate the difference between the current finger distance *
* and the required one */
var diff = currPinchStartDistance - fingerRequiredTargetDistance;
diff = diff / 2.54 * deviceDPI; // pixel to cm
// Calculate the angle between the two fingers on screen
// (angular coeff. of the segment between the fingers)
var deltaX = currPinchStartPos.p1.x - currPinchStartPos.p2.x;
var deltaY = currPinchStartPos.p1.y - currPinchStartPos.p2.y;
var angleInDegrees = Math.atan(deltaY / deltaX) * 180 / Math.PI;
// Setting markers position
if(currPinchStartPos.p1.x < currPinchStartPos.p2.x){
targetOpen1.pos_x = currPinchStartPos.p1.x + (diff / 2) *
Math.cos(angleInDegrees * Math.PI / 180);
targetOpen1.pos_y = currPinchStartPos.p1.y + (diff / 2) *
Math.cos(angleInDegrees * Math.PI / 180);
targetOpen2.pos_x = currPinchStartPos.p2.x - (diff / 2) *
Math.cos(angleInDegrees * Math.PI / 180);
targetOpen2.pos_y = currPinchStartPos.p2.y - (diff / 2) *
Math.cos(angleInDegrees * Math.PI / 180);
}
else{
// Same as before but with opposite signs
[...]
}
// Now rotate the markers by 90 degrees so that they are
// perpendicular to the segment between fingers
targetOpen1.angle = angleInDegrees + 90;
targetOpen2.angle = angleInDegrees + 90;
[...]
}

Come possibile osservare, per prima cosa, calcoliamo la differenza tra la posizione in cui sono
partite le dita nel pinch corrente, e dove il terapista vuole che arrivi: noi vogliamo che il movimento
sia il pi simmetrico possibile, per cui le barrette di riferimento, che dora in poi chiameremo
markers, andranno posti a una distanza di diff / 2 dal punto in cui iniziato il movimento.
Ora che sappiamo quanto i marker distano dalla posizione inziale, non ci resta che calcolare il
coefficiente angolare del segmento che unisce i due punti iniziali delle dita, per sapere esattamente
in quale punto dello schermo disegnare i marker.
Per fare questo calcoliamo la differenza tra i punti X della posizione delle dita, e la salviamo in deltaX
e la differenza tra i punti Y delle dita e la salviamo in deltaY.
Fatto ci, il coefficiente angolare, in gradi ci sar dato dalla formula:
tan1 (

180
)

Salviamo questo valore nella variabile angleInDegrees.


A questo punto la posizione dei marker ci sar data dalle formule dentro lif, per esempio la posizione
del marker di sinistra sar il punto di coordinate:

= ( ) + (
) cos( /180)
2

= ( ) + (
) sin( /180)
2
71

Ragionamento analogo viene fatto per il marker di destra.


Come ultima cosa, il marker viene ruotato su se stesso di 90 gradi, in quanto non lo vogliamo
parallelo al segmento che unisce le due dita, ma perpendicolare!
Di seguito un semplice schema per comprendere il lavoro svolto:
x
f1
angle_in_degrees
diff/2

diff/2

f2
y

Ed ecco il risultato finale ottenuto per il pinch-in (sopra) e il pinch-out (sotto):

72

7.l - HOT AIR BALLOON SISTEMA DI AUTO-CALIBRAZIONE


Ora che abbiamo visto come suggerire allutente quanto ampio debba essere il movimento da
effettuare per ottenere un qualche tipo di risposta dal gioco, prima di passare alla comunicazione
dei dati di sessione al server che si preoccuper di generare tutte le informazioni da mostrare al
terapista, bene riprendere un discorso legato allinput dellutente che non stato affrontato finora.
Abbiamo visto come, per ottenere informazioni quali la distanza minima da percorrere con le dita
sullo schermo, oppure il punto da cui un pinch corretto debba partire, ci si sia affidati
completamente ai parametri impostati dal terapista in fase di configurazione tramite una schermata
che vedremo nel capitolo successivo.
Si per voluto dare allutente, con lo scopo di testare comodamente leffettiva capacit del
paziente ad utilizzare il gioco, la possibilit di giocare senza che il terapista fosse obbligato a
effettuare misurazioni particolari e a calibrare precisamente lesercizio sul paziente.
Per fare ci stato ideato un sistema di auto-calibrazione, attivabile sempre dal pannello di
configurazione del gioco, il quale cerca, durante la FASE 1 della sessione, ovvero quella di decollo, di
impostare i parametri di gioco automaticamente in funzione di alcuni pinch eseguiti liberamente
(senza vincoli particolari di distanza).
Attenzione, bene ribadire che questo sistema non stato pensato per sostituire la pi precisa
configurazione ad hoc dellesercizio effettuabile dal terapista, ma per fornire uno strumento rapido
per testare velocemente la possibilit fisica, di un determinato paziente, ad utilizzare HotAirBalloon.
Di seguito verr descritto il funzionamento del sistema di auto-calibrazione.
Il concetto alla base di questo sistema molto semplice:
-

Si fanno effettuare alcuni pinch al paziente liberamente, senza verificare cio che raggiunga
determinate misure preimpostate.
- Si registra la distanza delle dita allinizio e alla fine del movimento.
- Si conta il numero di pinch effettuato in questa fase.
- I valori minimi da raggiungere nei pinch della fase di volo vengono, quindi settati come la
media campionaria dei valori rilevati per ogni pinch effettuato in fase di decollo, pi o meno
un certo valore di tolleranza impostato dal terapista in fase di configurazione.
In caso di utilizzo del sistema di auto-calibrazione, a fine sessione, verranno inviati al terapista anche
informazioni quali gli effettivi valori di finger distance ottenuti dal sistema e la cardinalit del
campione, in modo che egli possa valutare la qualit delle rilevazioni effettuate dal sistema.

73

La cardinalit del campione viene stabilita in funzione della forza di gravit esercitata in fase di
decollo sulla mongolfiera: pi forte, pi sar difficile far superare il bordo superiore dello
schermo alla mongolfiera per passare alla fase di volo e pi pinch saranno richiesti al paziente.
Vediamo come tutto ci stato implementato:
Per prima cosa, allinterno del metodo update verifichiamo di essere nella fase 1 di gioco e che sia
stato effettivamente richiesto lutilizzo del sistema di auto-calibrazione, questo pu essere fatto
controllando semplicemente lo stato delle variabili gamestatus e autocalibration:
if(gamestatus === "takeoff"){
if(autocalibration === true){
[...]
}
}

Abbiamo ora bisogno di due variabili che puntino a due vettori il cui unico scopo sar mantenere le
distanze di inizio e chiusura dei pinch effettuati:
var startDistArray = [];
var endDistaArray = [];

Per comodit, in questa fase, consideriamo unicamente i pinch-in e non i pinch-out, infatti, scopo
del giocatore in questa fase unicamente quello di fare salire la mongolfiera oltre il bordo superiore
dello schermo, inutile accettare comandi per accelerare la discesa.
Quello che dobbiamo fare, a questo punto per ogni pinch effettutato correttamente inserire i valori
di pinchStartDistance e pinchEndDistance nei vettori definiti sopra. Per cui avremo:
if(gamestatus === "takeoff"){
if(autocalibration === true){
/* Only pinch-in are registered */
if(currPinchStartDistance > currPinchEndDistance){
startDistArray.push(currPinchStartDistance);
endDistaArray.push(currPinchEndDistance);
}
}
}

A questo punto non ci resta che attendere leffettivo superamento della fase di decollo per calcolare
e impostare i valori adatti per le variabili fingerRequiredStartDistance e fingerRequiredStopDistance
normalmente impostate dai terapisti.
Per cui quando si passa alla fase di volo, verifichiamo se autocalibration abilitato e calcoliamo la
media dei pinch effettuati, fatto ci non ci resta che convertire il valore di tolleranza su pinch non
eseguito correttamente dal valore espresso in percentuale dal terapista a un valore espresso in
millimetri.
La formula per fare ci la seguente:
= | |
Dove tollerancePercentage un valore compreso tra 0 e 1.
A questo punto i valori di pinch richiesti saranno dati da:
74

=
E
= +
Ecco quanto descritto sopra, espresso in codice in questo estratto del metodo update di gioco:
if(gamestatus === "flying"){
[...]
if(autocalibration === true){
var sum = 0;
for(var i=0; i < startDistArray.length; i++){
sum += startDistArray[i];
}
// Calculate average start distance
avgStart = sum / startDistArray.length;
sum = 0;
for(var i=0; i < endDistArray.length; i++){
sum += endDistArray[i];
}
avgEnd = sum / endDistArray.length;
// Number of pinches caught during calibration
calibrationPinchNumber = endDistArray.length;
// Calculate tollerance using parameters set by the therapist
tollerance = Math.abs(avgStart - avgEnd) * pinchErrorTollerance;
// Then set required distance variables
fingerRequiredStartDistance = avgStart - tollerance;
fingerRequiredTargetDistance = avgEnd + tollerance;
}
[...]
}

Ed infine ecco un estratto dalla pagina di statistiche della sessione presentata al terapista, che
mostra i dati generati dal sistema di auto-calibrazione:

75

8 - REALIZZAZIONE DELLA PIATTAFORMA PER I TERAPISTI


Ora che abbiamo visto come stato realizzato il gioco da presentare ai pazienti, e abbiamo analizzato
nel dettaglio tutti gli aspetti che ne hanno caratterizzato lo sviluppo, quali implementazione del loop
di gioco in un canvas HTML, la realizzazione di animazioni, la gestione dellinput, lautocalibrazione
dei dispositivi ecc.
Possiamo passare ad analizzare lultimo aspetto importante del lavoro svolto: la realizzazione della
piattaforma di configurazione e analisi dei dati per i terapisti.
Nel capitolo introduttivo di questa tesi, stata presentata in breve la piattaforma REWIRE a cui
lintero progetto fin qui descritto si , in piccolo, ispirato: ecco, se il gioco vero e proprio descritto
finora insieme al dispositivo utilizzato dal paziente, ovvero il suo tablet personale, corrispondono
alla PATIENT STATION, quella che verr descritta nei paragrafi successivi sar lequivalente della
HOSPITAL STATION.
Partiamo con la nostra analisi dagli aspetti relativi ai requisiti della piattaforma.

8.a - REQUISITI E NECESSITA DEI TERAPISTI


Abbiamo visto come il gioco utilizzi una serie di parametri impostati dai terapisti per validare linput
e personalizzare lesercizio in funzione delle abilit fisiche del bambino.
stato dunque necessario pensare a una comoda interfaccia che permettesse al terapista di gestire
i pazienti e generare, per ognuno di essi una configurazione ad hoc.
Le necessit emerse, riguardo la configurazione dellesercizio da svolgere, a seguito degli incontri
effettuati sono le seguenti:
-

Possibilit di gestire pi pazienti contemporaneamente.


Possibilit di impostare lampiezza del movimento desiderato.
Possibilit di decidere quanto dovesse durare una singola sessione di gioco.
Possibilit di abilitare o disabilitare la funzione di auto-calibrazione del gioco
Possibilit di scegliere il dispositivo in possesso dal paziente da una lista quanto pi ampia
possibile. (Maggiore il supporto di dispositivi, maggiore la base di utenza soddisfatta a
basso costo: pi probabile, cio, che il paziente possieda il dispositivo).
Oltre a configurare il gioco, fondamentale, per il terapista, poter analizzare i dati rilevati durante
le singole sessioni di gioco, in modo da poter apprezzare gli eventuali miglioramenti effettuati dal
paziente e adeguare la difficolt dellesercizio per adattarsi alle sue condizioni.
Durante gli incontri, quindi emersa la necessit di predisporre, sulla Hospital Station, una pagina
di analisi delle sessioni in cui emergessero, per ogni singolo paziente:
-

Lo storico delle sessioni di gioco effettuate con, media per ogni sessione della posizione di
inizio e fine di ogni singolo pinch, e ovviamente, un dato riguardante il numero di sessioni
giocate.
Informazioni riguardanti la configurazione iniziale, che riportasse, per ogni sessione di gioco,
i dati relativi alla configurazione effettuata dal terapista riguardo alla distanza delle dita in
fase di pinching oppure ai dati generati dal sistema di auto-calibrazione del gioco.
Il numero o la percentuale di pinch ritenuti validi o invalidi in una singola sessione di
gioco.
76

E il dettaglio della distanza delle dita allinizio e fine di ogni movimento di pinching,
effettuato allinterno della singola sessione selezionata.
Essendo stato lintero gioco sviluppato con tecnologie web-based, si deciso di implementare
lintera piattaforma per i terapisti utilizzando le stesse tecnologie utilizzate per il gioco (HTML 5 e
Javascript).
In questo modo la piattaforma risieder sullo stesso server che ospiter il gioco utilizzato dai
pazienti, e sar accessibile via web da qualsiasi terminale con accesso a internet (di cui lospedale
per il quale stato creato lintero progetto, era gi provvisto). Vedremo poi nel paragrafo finale di
questo capitolo come sia stato ristretto laccesso alla hospital station unicamente al personale
autorizzato.
Ora addentriamoci pi nei dettagli, iniziando a vedere come stata realizzata la schermata di
configurazione del gioco e dellesercizio.

8.b - IL PANNELLO DI CONFIGURAZIONE DEL GIOCO


Iniziamo la nostra analisi dando uno sguardo a uno screenshot dellintera pagina di configurazione
finita, commenteremo poi le varie parti e vedremo come i dati, rilevati ottenuti dai form presenti in
questa pagina, vengano preparati per essere inviati al gioco.

Come possibile osservare, il pannello di configurazione si presenta come una normalissima pagina
web, essa divisa sostanzialmente in 3 parti:
-

Selezione e configurazione del paziente in alto: in questa sezione possibile selezionare


pazienti gi precedentemente inseriti nel sistema, aggiungerne di nuovi, o resettare la
password relativa al paziente correntemente selezionato.
Configurazione della piattaforma al centro: in questa sezione possibile selezionare il
dispositivo che il paziente utilizzer per svolgere il proprio esercizio e se utilizzare o meno il
sistema di auto-calibrazione del gioco.
Configurazione dellesercizio in basso: in questa sezione possibile personalizzare la
configurazione dellesercizio impostando manualmente la distanza obbiettivo delle dita
(apertura e chiusura delle dita), il margine di tolleranza e il tempo di gioco.
77

Tralasciando la parte relativa alla selezione e configurazione dei pazienti che verr analizzata meglio
nel paragrafo finale del capitolo, vediamo come stato strutturato il form di input della
configurazione, come funzionano i pulsanti e slider presenti in questa pagina e come i dati acquisiti
vengano preparati per linvio al gioco.
Per quanto riguarda il paziente corrente, ci basta sapere in questa fase, che il suo nominativo
univoco mantenuto nella sessione corrente, allinterno della variabile globale:
window.patient = "PXXX";

Per quanto riguarda, invece laspetto grafico dellintera pagina stato utilizzato il framework
bootstrap sviluppato da Twitter Inc. lazienda che sviluppa e mantiene lomonimo social network.
Si tratta sostanzialmente di una collezione di classi CSS e qualche modulo javascript utile da applicare
ai comuni elementi che caratterizzano una normale pagina web, quali bottoni, selettori, form, ecc.
con lo scopo di ottenere unaspetto grafico piacevole e coerente anche in assenza di web-designer
esperti che si occupino dellaspetto grafico della propria pagina.
Per quanto riguarda invece gli slider visibili nello screenshot qu sopra, stato utilizzato un plugin di
bootstrap open-source rilasciato da uno sviluppatore indipendente denominato bootstrapslider.js
Scopo di tale slider quello di semplificare limpostazione della misura delle dita durante il
movimento del pinch, o della percentuale di errore consentita al terapista:
Se analizziamo ad esempio nel dettaglio lo slider relativo alla finger distance, possiamo osservare
come spostando lo slider di sinistra si regoli la distanza richiesta dal terapista per quanto riguarda la
chiusura minima del pinch e regolando quello di destra si imposti, in maniera intuitiva la distanza
massima richiesta in apertura.
Tutti i valori impostati tramite slider o selettori vanno a riempire automaticamente i campi di un
form nascosto.
Andiamo, per, a osservare nel dettaglio come vengono trattati i dati impostati in questa schermata,
per fare ci dobbiamo seguire ci che succede alla pressione dei pulsanti pi importanti di questa
schermata, quelli situati nella parte inferiore destra:
-

Restore Defaults - Alla pressione di questo pulsante sia gli slider che i campi del form
nascosto vengono reimpostati ai valori predefiniti.
- Save Settings - Questo pulsante prepara i dati contenuti nel form ad essere inviati al server
per poter essere sfruttati dal gioco.
Per il primo pulsante limplementazione molto semplice, al rilevamento dellevento click (o tap
nel caso di dispositivi dotati di schermo touchscreen) sul pulsante, non si fa altro che accedere alla
propriet value di ogni singolo elemento del form e ripristinare il valore di default,
Il plugin bootstrap-slider utilizzato fornisce un metodo setValue molto comodo per eseguire questa
operazione, ecco un estratto dal codice per quanto rigurada proprio lo slider relativo alla distanza
tra le dita:

78

function restoreDefaults(){
[...]
// RESET SLIDER POSITION
$('#sl1').slider("setValue", [5,10]);
// ALSO RESET LABELS TEXT
$('#values_sl1').html("<p><b>5.0</b> cm
<i class=\"icon-resize-horizontal\"></i> <b>10.0</b> cm</p>");
[...]
}

Stessa cosa, ovviamente non verr descritta nel dettaglio, accade per tutti gli altri elementi presenti
nel form.
Da notare come la funzione restoreDefaults() non salvi il ripristino ai valori di default sul server, ma
effettui il cambiamento unicamente lato client: sar necessaria la pressione del pulsante Save
Settings per confermare le modifiche; questo rende possibile un annullamento delle modifiche
effettuate tramite il semplice reload della pagina e limita quindi il rischio di commettere errori da
parte dellutente.
Ben pi interessante ci che accade alla pressione del pulsante Save Settings, in questo caso
dovremmo preparare i dati per essere inviati al server ed effetture un opportuna HTTP request per
linvio vero e proprio.
bene spendere qualche parola nel descrivere lo standard utilizzato per la gestione dei file di
configurazione e delle sessioni di gioco allinterno della piattaforma.
Si pensato di utilizzare dei file JSON (JavaScript Object Notation), creati a runtime dalle varie
applicazioni che costituiscono la piattaforma, per il trasferimento dei dati dalla piattaforma per i
terapisti al gioco e viceversa.
Un file JSON sostanzialmente un file contenente dati strutturati secondo la notazione utilizzata
dagli oggetti javascript, eccone un esempio:
{
"glossary": {
"title": "example glossary",
"GlossDiv": {
"title": "S",
"GlossList": {
"GlossEntry": {
"ID": "SGML",
"SortAs": "SGML",
"GlossTerm": "Standard Generalized Markup Language",
"Acronym": "SGML",
"Abbrev": "ISO 8879:1986",
"GlossDef": {
"para": "A meta-markup language, used to create markup
languages such as DocBook.",
"GlossSeeAlso": ["GML", "XML"]
},
"GlossSee": "markup"
}
}
}
}
}

79

La stessa collezione di informazioni mantenuti in un file XML apparirebbe come segue:


<!DOCTYPE glossary PUBLIC "-//OASIS//DTD DocBook V3.1//EN">
<glossary><title>example glossary</title>
<GlossDiv><title>S</title>
<GlossList>
<GlossEntry ID="SGML" SortAs="SGML">
<GlossTerm>Standard Generalized Markup Language</GlossTerm>
<Acronym>SGML</Acronym>
<Abbrev>ISO 8879:1986</Abbrev>
<GlossDef>
<para>A meta-markup language, used to create markup
languages such as DocBook.</para>
<GlossSeeAlso OtherTerm="GML">
<GlossSeeAlso OtherTerm="XML">
</GlossDef>
<GlossSee OtherTerm="markup">
</GlossEntry>
</GlossList>
</GlossDiv>
</glossary>

Il grosso vantaggio derivato dallutilizzo di questa sintassi allinterno di unapplicazione come quella
realizzata in questo progetto dovuto principalmente alla possibilit di accedere a tutti i campi che
caratterizzano il file JSON, senza bisogno di generare o utilizzare un parser gi pronto, in quanto
assegnando lintero contenuto di un file JSON a una variabile, potremmo accedere a tutti i campi del
file come faremmo con un normale oggetto javascript.
Nel caso specifico della configurazione dellesercizio il file che vorremo andare a generare estraendo
i dati dal nostro form sar qualcosa di simile a questo:
{
"device_dpi":132,
"override_autocalibration":0,
"fingerRequiredTargetDistance":5,
"fingerRequiredStartDistance":10,
"pinchRequiredFreq":1,
"gameTime":1,
"tollerance":10
}

Il nostro primo obiettivo sar quindi quello di ottenere tutte le informazioni dal form HTML presente
nella pagina:

80

function saveConfig(){
// If no patient is selected
if(window.patient === undefined || window.patient === ""
|| window.patient === "none"){
return;
}
// Prepare variables
var device_dpi = parseInt($('#device_dpi').val());
var override_autocalib = document.getElementById('override_auto').checked;
override_autocalib = (override_autocalib === true) ? 1 : 0;
var minFingerDist = parseFloat($('#sl1').slider('getValue').val()
.split(',')[0]).toFixed(1);
var maxFingerDist = parseFloat($('#sl1').slider('getValue').val()
.split(',')[1]).toFixed(1);
var frequency = parseFloat($('#sl2').slider('getValue').val()).toFixed(2);
var gameTime = parseInt($('#sl3').slider('getValue').val());
var errorTollerance = parseInt($('#sl4').slider('getValue').val());
/* Set default values if necessary */
if(minFingerDist === 'NaN') {
minFingerDist = 5.0;
minFingerDist.toFixed(1)
};
if(maxFingerDist === 'NaN') {
maxFingerDist = 10.0;
maxFingerDist.toFixed(1)
};
if(frequency === 'NaN') { frequency = 1.0; frequency.toFixed(2); };
if(gameTime === 'NaN' || gameTime === "") { gameTime = 1; };
if(errorTollerance === 'NaN') { errorTollerance = 10; };
/* Send post request to generate the config file */
[...]
}

Come possibile osservare, si fa uso intenso della libreria jQuery per selezionare i vari elementi del
form; tutti gli elementi vengono presi e salvati semplicemente in opportune variabili.
8.b i - INVIO DELLA CONFIGURAZIONE DAL SERVER AL GIOCO
A questo punto non ci resta che inviare i dati al server che si occuper di generare il file di
configurazione che verr letto dal gioco, in fase di inizializzazione, prima di mostrare linterfaccia
allutente.
Per fare ci effettuiamo in fondo alla funzione saveConfig() una POST request sul server richiedendo
lesecuzione di un semplice script PHP il cui unico scopo sar creare il file config.json in una
directory dedicata allutente:

81

$.post("../HotAirBalloon/callbacks/generateConfig.php", {
patient : window.patient,
dpi : device_dpi,
override : override_autocalib,
minFinger : minFingerDist,
maxFinger: maxFingerDist,
frequency: frequency,
gameTime: gameTime,
tollerance: errorTollerance
}).done(function(){
$('#ok').fadeIn(500);
setTimeout(function(){$('#ok').fadeOut(500);},3000);
}).fail(function(){
$('#error').fadeIn(500);
setTimeout(function(){$('#error').fadeOut(500);},3000);
});

Per effettuare la POST request al server stata utilizzata la funzione $.post(callback, post_data) della
libreria jquery:
una semplice funzione che prende come parametro il path dello script lato server da richiamare e
gli argomenti da passare a tale script, in questo caso trasparente al programmatore listanziazione
delloggetto XMLHttpRequest, la preparazione dei dati e lesecuzione della chiamata SEND, secondo
lo standard definito per il protocollo HTTP.
Vediamo il codice della callback chiamata da questa funzione:
<?php
/* Retrieve POST data */
$data = array(
"device_dpi" => $_POST['dpi'],
"override_autocalibration" => $_POST['override'],
"fingerRequiredTargetDistance" => $_POST['minFinger'],
"fingerRequiredStartDistance" => $_POST['maxFinger'],
"pinchRequiredFreq" => $_POST['frequency'],
"gameTime" => $_POST['gameTime'],
"tollerance" => $_POST['tollerance']
);
/* Open config file and write config */
$fp = fopen('../config/'.$_POST['patient'].'/config.json', w);
print($fp);
fwrite($fp, json_encode($data));
fclose($fp);
?>

Come possibile osservare, il codice PHP non fa altro che preparare in un array il contenuto della
variabile $_POST, ovvero i dati ricevuti dal form.
Dopo di che crea un file nella directory dedicata al paziente denominato config.json in modalit di
scrittura.
A questo punto utilizzando la funzione fwrite scrive sul file il contenuto dellarray convertito
automaticamente in formato json dalla funzione della libreria standard php json_encode.

82

Il file a questo punto stato generato, da notare come non sia prevista una politica di
aggiornamento del file particolare: a ogni esecuzione dello script PHP il file viene semplicemente
riscritto da zero.
Quello che rimane da osservare, a questo punto, osservare come il gioco recuperi a sua volta il file
di configurazione generato poco fa:
Il codice per fare ci viene eseguito in fase di inizializzazione della classe game, a seguito del login
dellutente, che analizzeremo nella parte finale del capitolo, avremo accesso anche nella classe game
alla variabile globale window.patient, contenente lid relativo al paziente corrente.
Una volta in possesso di questo dato, per recuperare i dati relativi allultima configurazione salvata,
non ci resta che accedere al file config.json nella cartella del paziente ovvero:
/path/to/game/root/config/patient/config.json
Possiamo recuperare il file utilizzando, anche in questo caso, una comoda funzione della libreria
jQuery, la funzione getJSON(path), il cui scopo recuperare, tramite unHTTP request di tipo GET
il file JSON presente sul server e specificato come argomento.
window.patient = patient;
$.getJSON("config/"+window.patient+"/config.json", function(data){
therapistConfig = data;
/* since ajax loads data asynchronously and we want to pass
* therapistConfig to the game, we
* need to pack the whole game starting logic in a function
* and call that function only once
* we have all the required data
launchGame("myCanvas", therapistConfig);
});

*
*
*
*
*/

I dati ottenuti dalla funzione getJSON, essendo in formato JSON sono gi racchiusi allinterno di un
oggetto javascript valido, che possiamo passare tranquillamente al launcher del gioco, il quale
istanzier loggetto della classe game.js che al suo interno avr istruzioni di questo tipo:
[...]
this.devicePPI = therapistConfig.device_dpi;

/* IPAD 1st Gen 132 PPI */

/* Parameters used by the auto-calibration system */


if(therapistConfig.override_autocalibration === "1"){
this.autocalibration = false;
this.avgStart =
parseFloat(this.therapistConfig.fingerRequiredStartDistance);
this.avgEnd =
parseFloat(this.therapistConfig.fingerRequiredTargetDistance);
}
/* Parameters that can be set by the therapist, NOTE: "need a setting
interface for this..." */
this.pinchRequiredFreq =
parseFloat(this.therapistConfig.pinchRequiredFreq) * 1000;
this.fingerRequiredStartDistance =
parseFloat(this.therapistConfig.fingerRequiredStartDistance);
this.fingerRequiredTargetDistance =
parseFloat(this.therapistConfig.fingerRequiredTargetDistance);
this.allowedGameTime = parseInt(this.therapistConfig.gameTime);
this.pinchErrorTollerance = parseFloat(this.therapistConfig.tollerance) / 100;
[...]

83

Come possibile osservare, si accede ai singoli campi ottenuti dal file json come si accederebbe ai
campi di un normale oggetto javascript (Dot-Notation, oppure con parentesi quadre).
La stessa tecnica per il recupero del file json, viene utilizzata allinterno del pannello di
configurazione stesso quando, tramite lopportuno menu a tendina, il terapista seleziona un
paziente dalla lista, viene recuperato, se esiste, lultimo file di configurazione generato per quel
paziente, e automaticamente vengono impostati, per comodit tutti gli slider ai valori impostati in
precedenza per quel paziente.
Questo viene fatto per agevolare il lavoro del terapista che deve unicamente effettuare una semplice
modifica, magari su un unico parametro della configurazione, senza dover reimpostare tutti i valori
da zero.

8.c - IL PANNELLO DELLE STATISTICHE DEI PAZIENTI


Descriviamo ora lultimo elemento chiave dellintera piattaforma: abbiamo sottolineato pi volte la
necessit, da parte del terapista, di accedere, per quanto riguarda applicazioni di questo tipo, ai dati
relativi alle prestazioni dei pazienti durante le loro sessioni di gioco.
Lanalisi di tali dati fondamentale per poter valutare la qualit dellesercizio assegnato al paziente,
valutarne i progressi o gli eventuali peggioramenti e capire se, per un determinato individuo,
lesercizio proposto dal gioco valido oppure no.
Per questi motivi si deciso di generare una schermata, nella quale fosse possibile analizzare i dati
di sessione di ogni singolo paziente:

Come possibile osservare dallimmagine, anche questa schermata divisa in 3 macroaree:

84

Selezione del paziente in alto.


Storico sessioni, con media delle distanze delle dita sia in apertura che in chiusura per ogni
sessione di gioco registrata.
- Dettagli ultima sessione con:
o Dati di calibrazione.
o Numero di pinch validi/invalidi registrati.
o Distanza di apertura e chiusura delle dita per ogni pinch rilevato nella sessione
(compreso quelli invalidi).
o Media delle distanze tra le dita in apertura, chiusura e distanza percorsa, dove la
distanza percorsa semplicemente data da:
=
Per la realizzazione dei grafici stata utilizzata la libreria google charts che fornisce API javascript
per la gestione e realizzazione di grafici dinamici allinterno delle pagine web.
Analizziamo il tutto partendo dal recupero dei dati di sessione dal gioco:

8.c i - INVIO DEI DATI DI SESSIONE DAL GIOCO AL SERVER


Abbiamo visto, nel capitolo 7, come la fase centrale di gioco in HotAirBalloon sia la fase in cui
effettuiamo le rilevazioni e validazioni su tutti i pinch effettuati dallutente:
Per la costruzione dei nostri grafici nella schermata di statistiche mostrata sopra abbiamo bisogno
di trasferire alcune informazioni dal gioco al server, dobbiamo per rispondere ai seguenti quesiti:
- Di quali informazioni abbiamo bisogno?
- Quando trasferiamo queste informazioni?
- Come le trasferiamo?
Per rispondere alla prima domanda diamo unulteriore sguardo alla schermata di statistiche,
osservandola bene ci rendiamo conto che per generarla avremo bisogno di:
-

Dati di calibrazione: siano essi ottenuti dal sistema di auto-calibrazione o impostati


manualmente dal terapista, utile comunicare quali erano gli obbiettivi da raggiungere nella
sessione che si sta visualizzando, per cui dovremo trasferire:
o Cardinalit del campione analizzato dal sistema di auto-calibrazione (se stato
usato)
o Distanza minima delle dita che il paziente doveva raggiungere idealmente
o Distanza massima in apertura che il paziente doveva raggiungere
o Il valore di tolleranza impostato dal terapista
- Per lo storico delle sessioni distanza media (minima e massima) delle dita raggiunta in ogni
sessione analizzata
- Per la sessione visualizzata:
o Numero di pinch totale (numero di pinch validi + numero di pinch invalidi)
o Per ogni pinch: distanza delle dita allinizio del movimento e distanza delle dita alla
fine del movimento
o Ancora una volta media delle distanze raggiunte in tutti i pinch di sessione.
Tutti questi dati sono gi disponibili come variabili pubbliche allinterno della nostra classe di gioco,
lo vedremo fra qualche riga.

Quando trasferiamo le informazioni?


85

Abbiamo due possibili opzioni:


-

Inviare un aggiornamento al server per ogni pinch effettuato (questo richiede una
connessione costante col server per effettuare lo streaming dei dati, pu essere pesante per
il server e genera problemi in caso di invio asincrono delle informazioni: pu accadere ad
esempio che arrivi al server il secondo aggiornamento, mentre il primo ancora in corso).
- Inviare un unico pacchetto di informazioni a fine sessione (nel momento di transizione tra la
fase di volo e la fase di atterraggio della mongolfiera).
Vista la semplicit della piattaforma in questo stadio iniziale di sviluppo e la quantit esigua delle
informazioni da inviare, si preferito adottare questo secondo metodo.
Rimane da rispondere alla domanda, come trasferiamo le informazioni?
Il metodo che si deciso di utilizzare del tutto analogo a quello utilizzato per trasferire il file di
configurazione generato dal terapista dal server al gioco:
Per ogni paziente, si generata unulteriore cartella sul server, denominata stats la quale contiene
una collezione di file JSON (uno per ogni sessione giocata) in cui vengono mantenuti tutti i dati
necessari a generare la pagina di statistiche.
Vediamo come:
-

Per prima cosa verifichiamo, allinterno del metodo update, di aver concluso la fase di volo
il timer di gioco, contenuto nella variabile self.eta deve essere 0, quando ci troviamo in
questo caso dobbiamo chiamare una funzione che prepari i dati necessari alla generazione
del file json e passi in modalit volo:

[...]
if(self.eta.getTime() === 0){
/* The interesting part of the game is finished... while the
* user plays the landing mode we pack all the interesting data
* and send them to the server, so that the therapist can
* analyze the results

*
*
*
*/

sendSessionStats(self);
/* Switch to landing mode */
self.gamestatus = 'landing';
}
[...]

A questo punto non ci resta che analizzare la funzione sendSessionStats() che come
possiamo osservare dal codice prende come parametro listanza corrente delloggetto
game potendo cos accedere a tutti i suoi campi pubblici.

86

function sendSessionStats(game){
$.post("callbacks/generateStats.php", {
patient : window.patient,
calibNumber : game.calibrationPinchNumber,
avgStart : game.avgStart,
avgEnd : game.avgEnd,

// Pinch sample cardinality


// Sample Average Finger Start
// Distance
// Sample Average Finger Stop
//Distance

tollerance : game.therapistConfig.tollerance,
pinchOkNumber : game.validPinchNumber,
// Number of valid pinches
pinchBadNumber : game.invalidPinchNumber,
// Number of invalid pinches
startPinches : game.pinchStartArray,
// Array of pinches start
// distance
stopPinches : game.pinchStopArray
// Array of pinches stop
// distance
});
}

Anche in questo caso, possibile vedere come come la generazione dellopportuno file json venga
demandata a uno script PHP che viene eseguito server-side:
<?php
/* Retrieve POST data */
$data = array(
"calibNumber" => $_POST['calibNumber'],
"avgStart" => $_POST['avgStart'],
"avgEnd" => $_POST['avgEnd'],
"tollerance" => $_POST['tollerance'],
"pinchOkNumber" => $_POST['pinchOkNumber'],
"pinchBadNumber" => $_POST['pinchBadNumber'],
"startPinches" => $_POST['startPinches'],
"stopPinches" => $_POST['stopPinches']);
/* Open config file and write config */
$fp = fopen('../stats/'.$_POST['patient'].'/latest_session.json', w);
print($fp);
fwrite($fp, json_encode($data));
fclose($fp);
$fp = fopen('../stats/'.$_POST['patient'].'/'.time().'.json', w);
print($fp);
fwrite($fp, json_encode($data));
fclose($fp);
?>

Da notare come lo script, in questo caso generi non uno ma due file, uno denominato
latest_session.json e il secondo TIMESTAMP.json dove per TIMESTAMP si intende la stringa
rappresentante listante di tempo corrente ritornato dalla funzione time() di PHP.
Il primo file singolo e viene aggiornato a ogni sessione: contiene i dati relativi allultima sessione
giocata, mentre il secondo ha un nome sempre diverso ad ogni esecuzione dello script, e serve per
mantenere in memoria, sul server i dati relativi a tutte le sessioni giocate.
Passiamo quindi alla costruzione della pagina di statistiche:
Per prima cosa la pagina deve importare le librerie esterne fornite da google per poter accedere alle
API di costruzione dei grafici e inizializzarle.

87

<!-- Load google javascript API -->


<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
// Load the Visualization API and the piechart package.
google.load('visualization', '1.0', {'packages':['corechart', 'controls']});
// Set a callback to run when the Google Visualization API is loaded.
google.setOnLoadCallback(drawChart);
[...]
</script>

Come possibile osservare dallultima istruzione, viene richiesta lesecuzione della funzione
drawChart una volta caricate, da remoto, le librerie di google. La funzione drawChart non esiste
ancora, bisogna crearla essa non far altro che accedere ai dati generati durante la sessione di
gioco e li elaborer creando i vari grafici, vediamo per esempio, come stato creato il grafico dello
storico delle sessioni, ovvero quel grafico che mostra media di apertura e chiusura delle dita per ogni
sessione giocata dal paziente selezionato:
-

Per prima cosa otteniamo le medie dei pinch eseguiti in ogni sessione registrata nella cartella
stats dellutente corrente:

// Callback that creates and populates a data table,


// instantiates the charts, passes in the data and
// draws them.
function drawChart() {
[...]
// Retrieve data from json file
/* Get average pinch data, from every recorded session */
$.get("../HotAirBalloon/callbacks/getPatientHistory.php?patient="+window.patient
,function(data){
// Handle response
data = JSON.parse(data);
[...]
});
[...]
}

La funzione eseguita in seguito a una chiamata andata a buon fine sulla callback si aspetta, anche in
questo caso un elemento di tipo json che bisogner restituire:
Vediamo, dunque, anche cosa viene eseguito lato server analizzando lo script getPatientHistory:
-

Lo script dovr scansionare tutti i file di sessione del paziente corrente.


Per ognuno di questi file estrapolare le informazioni di sessione e calcolare la media di tutte
le distanze delle dita misurate.
Salvare il tutto in una opportuna struttura dati
Convertire la struttura dati in formato json, in modo che sia facilmente utilizzabile dal nostro
script lato client.
Restituire il risultato

Ecco il codice dello script:

88

<?php
$patient = $_GET['patient'];
$stat_path = "../stats/".$patient."/";
$sessions = scandir($stat_path);
$result = array();
// Scan all file in the patient directory
foreach ($sessions as $s) {
// Skip unnecessary files
if($s == "." || $s == ".." || $s == ".DS_Store" || $s ==
'latest_session.json'){
continue;
}
// For each session calculate avg finger distance and save it to $result
$json = file_get_contents($stat_path.$s);
$arr = json_decode($json, true);
$timestamp = explode('.', $s)[0];
$sum_open = 0;
$sum_closed = 0;
$sum_dist = 0;
for($i = 0; $i < count($arr['startPinches']); $i++){
$sum_open += floatval($arr['startPinches'][$i]);
$sum_closed += floatval($arr['stopPinches'][$i]);
$sum_dist += (floatval($arr['startPinches'][$i])
floatval($arr['stopPinches'][$i]));
}
$average_start_pinch = $sum_open / count($arr['startPinches']);
$average_stop_pinch = $sum_closed / count($arr['stopPinches']);
$average_dist_pinch = $sum_dist / count($arr['startPinches']);
array_push($result, array("timestamp" => $timestamp, "avg_start" =>
$average_start_pinch, "avg_stop" => $average_stop_pinch, "avg_dist" =>
$average_dist_pinch));
}
// Echo the json_encoded version of the $result array
echo json_encode($result);
?>

Ora vediamo come la funzione drawChart disegner il grafico sullo schermo:


Innanzi tutto il nostro grafico sar dotato di un sistema controllo del range da visualizzare, in questo
modo sar possibile filtrare i dati mostrati dal grafico in funzione della data:
sar cio possibile scegliere se mostrare lo storico delle sessioni di gioco da un giorno X a un giorno
Y oppure da unora X a unora Y
Predisponiamo innanzi tutto un div adatto ad ospitare il nostro grafico nel codice html della pagina:
<div id="history_chart" class="span12">
<div id="chart0"></div>
<div id="control0"></div>
</div>

Il div history_chart conterr il grafico e il range filter di controllo i due componenti verranno
distribuiti sui div pi interni:
- Chart0 per il grafico vero e proprio
- Control0 per il range filter.
Secondo le API di google chart la coppia grafico + strumenti di controllo si chiama dashboard per
questo la nostra funzione dovr predisporne una come segue:
89

var dashboard = new


google.visualization.Dashboard(document.getElementById('history_chart'));

A questo punto generiamo il sistema di controllo del range da visualizzare, si tratta di un semplice
grafico a linea con due cursori trascinabili col mouse per limitare la zona da visualizzare nel grafico
principale:

Ecco il codice per generarla


var control = new google.visualization.ControlWrapper({
'controlType' : 'ChartRangeFilter',
'containerId' : 'control0',
'options' : {
// Filter by the number of pinches axes
'filterColumnIndex' : 0,
'ui' : {
'chartType' : 'LineChart',
'chartOptions' : {
'chartArea' : {'width' : '90%', 'height': '20%'},
'hAxis' : {'baselineColor' : 'none'},
},
'chartView':{
'columns' : [0,1]
},
//'minRangeSize' : 5
},
},
'state' : {
'range' : {
'start' : range_start_date,
'end' : new Date(parseInt(data[data.length - 1].timestamp) * 1000)
}
}
});

Fatto ci predisponiamo il grafico principale, si tratta di un semplice grafico a colonne, ogni elemento
del grafico ha associata la data della sessione il range di apertura massima delle dita e il range di
chiusura minima:

Ecco il codice:

90

var chart = new google.visualization.ChartWrapper({


'chartType' : 'ColumnChart',
'containerId' : 'chart0',
'options' : {
'title' : "Multiple session patient results",
'chartArea' : {'height' : '80%', 'width' : '90%'},
'legend': {'position': 'none'},
'colors': ['#3366CC', '#79CDCD']
}
});

Ora, sempre sfruttando le API di google e i dati restituiti dal nostro script PHP che abbiamo eseguito
server-side, prepariamo i dati da visualizzare nel grafico:
var prepared_data = [['Session',
'Average session fingers open (in cm)',
'Average session fingers closed (in cm)']];
for(var i = 0; i < data.length; i++){
var curr_element = data[i];
var tmpArray = [
// Converting UNIX time to javascript date
new Date(parseInt(curr_element.timestamp)*1000),
parseFloat(parseFloat(curr_element.avg_start).toFixed(2)),
parseFloat(parseFloat(curr_element.avg_stop).toFixed(2))];
prepared_data.push(tmpArray);
}
var historyData = google.visualization.arrayToDataTable(prepared_data);

Non ci resta che collegare grafico e rangeFilter assieme e disegnare il grafico corrispondente ai dati
preparati:
dashboard.bind(control,chart);
dashboard.draw(historyData);

Ecco il grafico generato

stato mostrato come stato disegnato il grafico pi complesso della pagina, ovviamente la
funzione drawChart si comporter in modo analogo per generare tutti gli altri grafici presenti:
-

Eseguir uno script PHP server-side per il recupero dei dati dai differenti file JSON presenti
Li trasformer in dati utilizzabili dalla piattaforma Google Chart
Disegner il grafico pi opportuno per il tipo di dati da mostrare al terapista.
91

8.d - GESTIONE UTENTI E SICUREZZA


Avendo la necessit di provare la piattaforma su un diverso numero di pazienti, e avendo ogni
paziente necessit di configurazione diversa, si pensato di rendere lintero sistema multi-utente.
Questo aspetto stato fino ad ora soprasseduto dichiarando semplicemente lesistenza di una
variabile window.patient, la quale conteneva il nome del paziente correntemente autenticato.
Vediamo per come stato affrontato questo problema:
Innanzi tutto abbiamo due categorie di utenza a cui pensare:
-

I pazienti: ogni paziente ha due cartelle dedicate sul server, una per il mantenimento dei file
di configurazione e unaltra per mantenere tutti i file JSON relativi alle sessioni giocate nella
sua storia. Ogni paziente deve poter essere identificato univocamente, e deve poter
accedere al gioco tramite una password, in modo da non poter giocare con la configurazione
di un altro paziente il cui nome noto.
- I terapisti: questi utenti hanno accesso alla Hospital Station, quindi allelenco dei pazienti,
alle loro configurazioni e alle statistiche di ogni paziente.
Iniziamo con lautenticazione dei pazienti.
Trattandosi lintero progetto, una piattaforma sperimentale e non un prodotto finito, si deciso di
mantenere il massimo grado di riservatezza dei pazienti nel modo pi semplice possibile, ovvero
limitando al minimo necessario il mantenimento di dati sensibili sul server.
Per fare ci si deciso di assegnare a ogni paziente un identificativo anonimo: PXXX dove al posto di
XXX viene sostituito dal sistema un numero a tre cifre univoco e incrementale. Sar cura
dellospedale associare a ogni identificativo il nome del paziente corrispettivo come meglio crede.
Un paziente pu essere creato da un terapista in qualsiasi momento, attraverso il pannello di
configurazione del gioco cliccando sul pulsante ADD NEW PATIENT.
Verr generato lid univoco del paziente e mostrato il seguente form da compilare con la password
scelta dallutente e la conferma di questultima.

92

Alla pressione del tasto di conferma verr validato il form controllando leffettiva compilazione e
uguaglianza dei campi password e confirm password dopo di che, verr richiamata uno script PHP
lato server che creer lutente vero e proprio, vediamo come:
$('#newpatient-form').submit(function(){
var patient = $("#newuser_patientId").html();
var pass = $("#inputPassword").val();
var confirm = $("#inputConfirmPassword").val();
if(pass.length === 0){
// All fields are required
[...]
return false;
}
if(confirm.length === 0){
[...]
return false;
}
if(pass != confirm){
// Password mismatch
[...]
return false;
}
/* POST call to create user */
$.post("../HotAirBalloon/callbacks/createNewUser.php", {user: patient,
passwd: pass})
.done(function(data){
var template = '<option value="{val}">{name}</option>';
var html = template.replace("{val}",
patient).replace("{name}",patient);
$('#patient_select:first-child').after(html);
$('#patient_select').val(patient);
$('#newpatient-modal').modal("hide");
window.patient = patient;
});
});

La sequenza di if iniziale controlla leffettiva compilazione dei campi obbligatori del form, una volta
superati i controlli viene eseguita una richiesta HTTP di tipo POST al server richiedendo lesecuzione
dello script PHP createNewUser.php, il cui compito sar creare le cartelle atte ad ospitare i file di
configurazione e i dati di sessione per il nuovo utente. Inoltre dovr aggiungere le credenziali di
accesso scelte in un file che consulteremo in seguito per loggare lutente nel gioco.
<?php
$patient = $_POST['user'];
$pass = sha1($_POST['passwd']);
/* Adding patient to users file: using 'a' instead of 'w' because we want *
the file pointer to point at the end of the file and not at the
*
beginning since we want to append data
*/
$fp = fopen("../users", 'a');
$string_to_append = $patient . " " . $pass . "\n";
fwrite($fp, $string_to_append);
fclose($fp);
mkdir("../config/".$patient);
mkdir("../stats/".$patient);
?>

93

Tralasciando la creazione delle directory dellutente che risolta con la banale chiamata alla
funzione php mkdir(path), parliamo un secondo del file users che come abbiamo detto, mantiene
al suo interno lelenco dei pazienti registrati con le loro password:
Si tratta di un semplice file di testo dove per ogni paziente viene aggiunta una riga (da notare con la
funzione fopen venga chiamata con modalit a, ovvero append).
Ogni riga contiene a sinistra il nome dellutente secondo la convenzione adottata dallintero sistema
PXXX seguito da uno spazio e la password elaborata utilizzando la funzione crittografica di hash
sha1.
Caratteristica di questa famiglia di funzioni di hash quella di essere in grado di generare una stringa
di dimensioni fisse e univoca a partire da una stringa di lunghezza variabile. Caratteristica particolare
di questo processo quello di non essere reversibile, non per tanto possibile risalire al messaggio
originale conoscendo unicamente la stringa restituita dalla funzione di hash.
Il file risultante sar qualcosa di simile a questo:
# LOGIN PASSWORD
P000 89e495e7941cf9e40e6980d14a16bf023ccd4c91
P001 89e495e7941cf9e40e6980d14a16bf023ccd4c91
P002 9d4e1e23bd5b727046a9e3b4b7db57bd8d6ee684
P003 9d4e1e23bd5b727046a9e3b4b7db57bd8d6ee684

Ora che sappiamo come i pazienti vengono aggiunti al sistema, vediamo come si possono autenticare
per utilizzare il gioco.
Prima dellavvio della sessione di gioco stato aggiunto un form da compilare per autenticare
lutente.

94

A questo punto, alla pressione del tasto Sign in dovr essere autenticato il paziente, settata la
variabile window.patient e avviato il gioco come descritto in precedenza.

Analizziamo il processo di login pi nel dettaglio:


$('#login-form').submit(function(e){
e.preventDefault();
/* First check if input is valid */
var username = $('#inputUsername').val().toUpperCase();
var passwd = $('#inputPassword').val();
if(username.length === 0){
/* Username is empty */
$('#inputUsername').parent().parent().addClass('error');
$('#empty_fields_error').fadeIn(500);
setTimeout(function(){$('#empty_fields_error').fadeOut(500)}, 5000);
return false;
}
var regex = /P\d\d\d/i;
if(regex.test(username) === false){
/* Username is not valid */
$('#inputUsername').parent().parent().addClass('error');
$('#patient_id_error').fadeIn(500);
setTimeout(function(){$('#empty_fields_error').fadeOut(500)}, 5000);
return false;
}
if(passwd.length === 0){
/* Password is empty */
$('#inputPassword').parent().parent().addClass('error');
$('#empty_fields_error').fadeIn(500);
setTimeout(function(){$('#empty_fields_error').fadeOut(500)}, 5000);
return false;
}
/* Attempt login */
$.post("callbacks/userLogin.php", {user:username, pass:passwd})
.done(function(result){
if(result === "LOGGED"){
/* Start the game */
startGame(username);
/* Dispose Modal */
$('#login-modal').modal("hide");
}
else{
/* Invalid login attempt */
$('#inputUsername').parent().parent().addClass('error');
$('#inputPassword').parent().parent().addClass('error');
$('#login_error').fadeIn(500);
setTimeout(function(){$('#login_error').fadeOut(500)}, 5000);
return false;
}
});
});

Alla pressione del tasto Sign In, viene innanzi tutto verificata leffettiva immissione dei dati in tutti
i campi del form e leffettiva validit del nome utente inserito (tramite lespressione regolare
/P\d\d\d/i, verifichiamo che il nome utente sia composto da una P seguita da 3 cifre).

95

Una volta eseguiti questi controlli preliminari, si passa nuovamente al codice eseguito lato server:
viene chiamato uno script PHP userLogin.php a cui vengono passati via POST i parametri
rappresentanti nome utente e password dellutente che sta tentando di effettuare il login.

Osserviamo la callback nel dettaglio:


<?php
$result = "INVALID";
$user = $_POST['user'];
$pass = sha1($_POST['pass']);
/* Try to match user & pass in the users file */
$fp = fopen('../users','r');
while(!feof($fp)){
$parts = fscanf($fp,"%s %s\n");
/* Match POST data with current string */
if($parts[0] == $user && $parts[1] == $pass){
$result = "LOGGED";
break;
}
}
fclose($fp);
echo $result;
?>

da notare, innanzi tutto, come la password venga immediatamente passata alla funzione sha1, se
essa sar identica alla password impostata in fase di creazione dellutente, allora la stringa restituita
dalla funzione sar la medesima.
A questo punto lo script apre il file users, che abbiamo mostrato in precedenza, in modalit di sola
lettura.
Viene letto il file riga per riga allinterno del ciclo while; essendo ogni riga strutturata nella stessa
maniera della precedente: nome_paziente \spazio password \nuova_riga, particolarmente
comodo usare la funzione fscanf per leggere da file.
La funzione restituir un vettore di stringhe, in cui il primo elemento sar il nome del paziente nella
riga corrente, e il secondo la password.
Non ci resta che confrontare i valori estratti dal file con quelli ricevuti via POST dal client, nel caso in
cui trovassimo una corrispondenza, non dovremmo fare altro che restituire la stringa LOGGED.
Tornando al client, non appena esso riceve la risposta dallo script userLogin, esso verifica di aver
ricevuto la stringa LOGGED come risposta:
-

In caso affermativo, richiama la procedura di lancio del gioco, attraverso la funzione


startGame passando come parametro il nome del paziente corrente.
- In caso negativo, comunica lerrore allutente e ripropone il form di login.
Questo il modo in cui viene gestita lautenticazione degli utenti, passiamo a quella dei terapisti.
96

Per quanto rigurada questa tipologia di utenti, che a differenza dei pazienti, ha permessi diversi,
accesso a elementi diversi della piattaforma, e sono generalmente in numero costante ( pi raro
che si debba aggiungere un terapista alla piattaforma, piuttosto che un paziente), si deciso di
sfruttare una delle funzionalit integrate nel webserver utilizzato per ospitare la piattaforma
(Apache 2) per quanto riguarda lautenticazione dei terapisti.
Stiamo parlando del modulo auth_basic e dellapplicazione htpasswd.

Il concetto che sta alla base dellutilizzo di questo modulo quello di:
-

Spostare tutti i file che costituiscono larea ad accesso riservato in una cartella dedicata
allinterno del webserver.
- Definire le credenziali degli utenti che potranno accedere a questarea
- Comunicare al webserver di restringere laccesso ai soli utenti indicati in precedenza.
Nel nostro caso i file da spostare nella directory dedicata ai terapisti, sono quelli che mostrano il
pannello di configurazione e quello di statistiche, stata perci creata sul server una directory
therapist in cui sono stati spostate le due pagine e i file da cui esse dipendono.
A questo punto stato abilitato il modulo auth_basic e riavviato il webserver:
sudo a2enmod auth_basic
sudo apachectl restart

Per creare il file contenente lelenco dei terapisti accettati dal sistema stato utilizzato il comando
htpasswd: esso funziona in maniera del tutto analoga al comando passwd dei sistemi operativi Unix
based, ecco un estratto dal manuale ufficiale di apache che ne spiega lutilizzo:

Il file degli utenti, per questioni di sicurezza stato salvato sul server in una posizione non
raggiungibile da remoto: in /etc/apache2/users ovvero nella cartella in cui risiedono i file di
configurazione del webserver, ecco il comando per laggiunta del primo terapista alla piattaforma:
sudo htpasswd -c users nomeutente

Con lopzione c specifichiamo la necessit di creare il file users, tale opzione verr omessa per la
creazione di tutti i terapisti successivi al primo.
A questo punto non ci resta che salvare un file nascosto allinterno della directory riservata ai
terapisti nominato .htaccess che verr letto dal webserver prima di mostrare il contenuto richiesto
dal client, in questo file esprimiamo la necessit di autenticare gli utenti per visualizzare il contenuto
della cartella:
AuthUserFile /etc/apache2/users
AuthType Basic
AuthName "RESTRICTED ACCESS! Use therapist credential to login ..."
Require valid-user

97

A questo punto il webserver richieder allutente di autenticarsi prima di mostrare il contenuto della
directory.
Ecco la schermata proposta dal sistema:

98

9 - CONCLUSIONI
Con la descrizione della piattaforma per i terapisti, abbiamo concluso la descrizione di tutti gli
elementi che costituiscono il progetto realizzato per la riabilitazione della mano.
Il gioco HotAirBalloon e la piattaforma realizzata sono, ovviamente, ancora a uno stadio preliminare
di sviluppo, ma lintero pacchetto stato installato su un WebServer virtuale predisposto su una
macchina dedicata presso il laboratorio AISLab del dipartimento di Informatica dellUniversit degli
Studi di Milano e reso disponibile al reparto di chirurgia e riabilitazione della mano dellospedale San
Giuseppe con cui abbiamo collaborato, per ulteriori test su alcuni dei pazienti volontari ritenuti pi
adatti dal personale clinico.
A seguito del rilascio della piattaforma, si sono tenuti ulteriori incontri in cui si cercato di
individuare le problematiche relative allutilizzo della piattaforma e ai possibili miglioramenti.
Tuttora si stanno effettuando modifiche alla piattaforma per renderla il pi utilizzabile possibile.

9.a - RISULTATI OTTENUTI CON HOT AIR BALLOON


Per quanto riguarda il gioco HotAirBalloon, abbiamo avuto modo di testare una versione preliminare
del gioco sulla piccola paziente della quale avevamo assistito alla sessione di riabilitazione durante
fase di progettazione della piattaforma.
La bambina ha avuto qualche difficolt nel comprendere la meccanica di gioco, probabilmente per
la giovane et, per tanto la sessione giocata di fronte a noi stata molto breve, nonostante ci
abbiamo potuto osservare la capacit del gioco di rilevare le dita sullo schermo e il conseguente
movimento.
Abbiamo per osservato leffettiva necessit di effettuare qualche sessione di training sui pazienti
con i terapisti per fare comprendere, specie a bambini cos piccoli, come strutturato il gioco.
Il personale clinico si preso carico di effettuare test su una base di pazienti pi ampia, di et
differente e a diversi stadi del processo di riabilitazione.
Ad oggi sulla piattaforma sono registrati 6 pazienti attivi sulla piattaforma, dai dati ottenuti tramite
i pannelli di statistiche delle varie sessioni stato possibile osservare come, per il paziente medio,
sia ovviamente difficoltoso, ma non impossibile, effettuare movimenti di pinching validi (ovvero
allinterno dei parametri impostati dal terapista o dal sistema di configurazione), daltra parte il
sistema si , finora, dimostrato in grado di ottenere dati veritieri sulle distanze percorse dalle dita
dei pazienti sia per pinch validi e invalidi.
Per migliorare la giocabilit del gioco, stato introdotto, in una fase successiva di sviluppo, il
parametro di tolleranza, descritto nel capitolo 7 e 8, che permette un margine di errore pi ampio
nella fase di calibrazione (sia da parte del terapista che da parte del sistema di auto-calibrazione),
stato scritto un manuale per la configurazione dei parametri di gioco ed stato consegnato ai
terapisti per garantire una configurazione dellapplicazione adatta ai singoli pazienti, siamo
attualmente in attesa di conoscere ulteriori sviluppi.

99

9.b - POSSIBILI SVILUPPI FUTURI


Nonostante i vari problemi di immaturit della piattaforma, il personale clinico dellospedale San
Giuseppe si dimostrato entusiasta delle possibilit offerte dalla piattaforma. Si sta, pertanto,
pensando a eventuali sviluppi della piattaforma e progetti futuri.

NUOVI GIOCHI WEB-BASED


Innanzi tutto si sta pensando alla capacit di dispositivi come tablet e smartphone di tracciare altre
tipologie di gestures delle dita: come ad esempio il tap, lo swipe ecc. Tramite le librerie javascript
utilizzate in HotAirBalloon e i touch event previsti dallo standard W3C sar, in futuro, possibile
generare altri videogiochi web-based, quindi costruiti utilizzando la stessa architettura usata nel
progetto qui descritto, che possano sfruttare lo stesso backend per i terapisti e al contempo fornire
un set di esercizi diversi e che coprano uno spettro pi ampio della routine riabilitativa assegnata
normalmente ai pazienti.
NUOVI STRUMENTI
Inoltre, ora il laboratorio AISLab sta lavorando alla produzione di semplici giochi sensorizzati, in
grado di misurare la forza impressa sugli stessi e inviare segnali a dispositivi quali smartphone o
tablet in grado di elaborarli e utilizzarli di conseguenza: lo scopo quello di realizzare giocattoli fisici
con cui fare giocare i bambini ottenendo dei dati che, oltre ad essere inviati al terapista per
opportune analisi sullo stato del paziente, possono essere utilizzati per interagire con giochi virtuali,
lidea quella che un azione sul gioco reale provochi una reazione anche sul gioco virtuale,
trasformando cos il gioco fisico in una sorta di controller per il gioco virtuale.

Questo tipo di approccio, dovrebbe facilitare la capacit di acquisire dati su pazienti molto giovani
che non sono ancora in grado di giocare con videogiochi strutturati pensati per tablet o console,
come quelli in cura presso il reparto con cui abbiamo potuto lavorare.
NUOVE TECNOLOGIE
Oltre a questi elementi, altri spunti per sviluppi futuri del progetto ci vengono offerti da nuovi device
e tecnologie rese disponibili in questi mesi, due fra tutte:
-

Microsoft Kinect 2, secondo quanto dichiarato dal produttore, e secondo quanto si potuto
osservare in applicazioni commerciali gi rilasciate con luscita di Microsoft XBOX One
sembra essere in grado di tracciare con un alto livello di precisione le mani degli utenti
davanti al dispositivo. Questo permetterebbe di sviluppare giochi pi complessi, che
comprendano movimenti pi articolati e che si possano giocare insieme ad altri.

100

Leap Motion Controller, ne abbiamo gi parlato in questo documento, ma dalla


realizzazione del progetto ad oggi, la casa produttrice del dispositivo ha rilasciato in
versione BETA la nuova SDK che introduce un sistema innovativo di tracciamento della
mano, basato sulla rilevazione dello scheletro: questo ha permesso di scrivere API in grado
di risolvere alcuni problemi che ne rendevano difficoltosa la programmazione nella prima
versione:
o Si possono ora rilevare nativamente gestures quali il Pinching o il Grabbing
o Pu essere misurata la qualit del rilevamento della mano: se ad esempio le mani si
sovrappongono, il valore di confidence sui dati rilevati scende da 1 a 0
o Ora possibile distinguere le mani rilevate, destra o sinistra
o Identificare un dito precisio nellaria scansionata (ad esempio: indice della mano
sinistra)
o Identificare la posizione delle ossa

Tutti questi elementi potranno migliorare la qualit dei rilevamenti delle mani dei pazienti
permettendo di realizzare e analizzare esercizi con movimenti sempre pi complessi.

101

10 BIBLIOGRAFIA
1. J. Schell J. The Art of Game Design: Book of Lenses. Elsevier, 2008.
2. Mainetti R, Sedda A, Ronchetti M, Bottini G, Borghese NA. (2013) Duckneglect: videogames based neglect rehabilitation. Technology and Health Care 21 97111 97. DOI
10.3233/THC-120712 IOS Press.
3. NA Borghese, M Pirovano, PL Lanzi, S Wuest and ED de Bruin (2013), Computational
Intelligence and Game Design for effective home-based stroke at Home Rehabilitation.
Games for Health Journal. April 2013, Vol. 2, No. 2: 81-88.
4. M.Pirovano, P.L. Lanzi, R.Mainetti and N.A. Borghese (2013), IGER: A Game Engine
Specifically Tailored to Rehabilitation, Games for Health, Proc. of 3rd Conf. on Gaming
and Playful Interaction in Health Care, B. Schouten, S. Fedtke, T. Bekker, M. Schijven, A.
Gekker Eds., Springer Vieweg.
5. Michele Pirovano, Iuri Frosio, Carl Yuheng Ren, Pier Luca Lanzi, David Murray, N. Alberto
Borghese (2013), Robust Silhouette Extraction from Kinect data, Proc. ICIAP2013,
Springer-Verlag.
6. M. Pirovano, R. Mainetti, G. Baud-Bovy, P.L. Lanzi, N.A. Borghese (2012), Self-Adaptive
Games for Rehabilitation at Home, Proc. IEEE Conference on Computational Intelligence
and Games, 978-1-4673-1194-6/12/$31.00 2012 IEEE pp. 179-186.

ARTICOLI
7. Nunzio Alberto Borghese, Michele Pirovano, Renato Mainetti, Pier Luca Lanzi Seline West
and Eling D. de Bruin - Artificial Intelligence and Game Design for effective at Home
Rehabilitation.
8. N. Alberto Borghese, Renato Mainetti, Michele Pirovano, Pier Luca Lanzi - Artificial
Intelligence and Game Design for effective at Home Rehabilitation.

SITI WEB E MANUALI ONLINE


9. Panda3D official manual: https://www.panda3d.org/manual/
10. Leap Motion Controller SDK documentation for the Python Programming Language:
https://developer.leapmotion.com/documentation/python/
11. W3School Javascript tutorial: http://www.w3schools.com/js/
12. W3C touch events document page: http://www.w3.org/TR/touch-events/

102

RINGRAZIAMENTI
Per concludere, desidero ringraziare tutte le persone che mi hanno supportato tecnicamente e
psicologicamente durante la stesura della tesi e lintero periodo di lavoro, sollevandoli, ovviamente,
dalla responsabilit di eventuali errori presenti in questo documento.
A partire dal mio relatore, prof. Nunzio Alberto Borghese, che mi ha accolto allinterno del suo team
con entusiasmo, trattandomi alla pari dei veterani del laboratorio, prendendo in ampia
considerazione i miei suggerimenti, e fornendomene a sua volta di preziosi per riuscire a lavorare al
meglio.
Il mio correlatore, il dott. Renato Mainetti, che ha fornito un supporto tecnico straordinario,
aiutandomi a sbloccarmi nei momenti di difficolt, ragionando con me su algoritmi, tecniche e
approcci migliori da seguire nello sviluppo del progetto e nella scrittura di questa tesi.
Tutti i ragazzi, colleghi tesisti e ormai amici dellAISLab, con una menzione particolare per il dott.
Michele Pirovano, per i numerosi consigli e aiuti fornitomi.
Un ringraziamento particolare va anche al team del reparto di chirurgia e riabilitazione della mano
dellOspedale San Giuseppe di Milano, in particolare il Prof. Giorgio Pajardi e le dott.sse Erica Cavalli
ed Elena Mancon, grazie a cui stato possibile realizzare il concept del gioco e dellintera
piattaforma; e ai giovani pazienti e alle loro famiglie che hanno testato, e stanno ancora testando la
piattaforma per aiutarci a evolverla e migliorarla.
Infine, ultimi, ma non per importanza, vorrei ringraziare i miei amici e la mia famiglia, in particolare
mia madre, mio padre e mia sorella per gli stimoli, i consigli e il prezioso sostegno morale (e non
solo), fornitomi in questi anni di carriera universitaria.

103

Potrebbero piacerti anche