\articolo{Introduzione alle problematiche relative allo sviluppo di avventure grafiche}{Vittorio Ferrari}
\index{Ferrari!articolo di}

\paragrafo{Premessa}

Il mercato moderno ci mostra che il pubblico sta progressivamente perdendo
interesse nei giochi d'azione bidimensionali (quali platform e
shoot'em-up), per orientarsi sempre pi\`u su due generi: i giochi d'azione
tridimensionali (come ``Quake'' e ``Wing Commander'') e le avventure grafiche
(quali ``Day of the Tentacle'' e ``Space Quest'').

Se a questa considerazione di mercato si aggiunge il fatto che su \A di
titoli di questo genere ne escono decisamente pochi (e se diamo un'occhiata
ai listini questo vale in modo particolare per le avventure grafiche), ci
si rende conto di quanto sia importante sviluppare in questo senso.

Questo articolo vuole appunto spingere gli sviluppatori in questa
direzione, presentando le principali problematiche che ruotano attorno allo
sviluppo di un'avventura grafica su \A e cercando di illustrare qualche
possibile soluzione. L'articolo non ha la pretesa di coprire ogni aspetto
di questo immenso tema n\'e quello di fornire complete ed esaustive
spiegazioni risolutrici di ogni dubbio, ma piuttosto di fornire qualche
buono spunto e soprattutto di contribuire a svegliare l'interesse degli
sviluppatori.

La presentazione e le soluzioni proposte sono basate sull'esperienza
accumulata durante la programmazione di Vega\index{Vega}, un sistema di sviluppo e
conversione di avventure grafiche basate sul modello LucasArts.

Vega \`e la nostra risposta alla mancanza di avventure grafiche per \A.
Questo programma permette di creare giochi di buona qualit\`a tecnica in
breve tempo (si veda il paragrafo {\em Vega: presente e futuro} per
ulteriori informazioni).


\paragrafo{Le avventure grafiche}

Prima di passare all'analisi delle problematiche \`e molto utile
specificare che in questo articolo per avventura grafica s'intende un gioco
sul modello classico della LucasArts (``Day of the Tentacle'' \`e un
esempio perfetto). Le caratteristiche fondamentali di questo tipo di giochi
sono:

\begin{enumerate}
\item Il display \`e diviso in tre parti orizzontalemente:
      \begin{itemize}
      \item l'area di gioco, che visualizza graficamente la locazione in cui
            si trova correntemente l'eroe;
      \item il parser, con le azioni possibili e l'inventario;
            (questa fascia del display viene sostituita dalla lista delle frasi
            possibili durante un dialogo dell'eroe con uno dei personaggi del
            gioco);
      \item la barra di stato, posta tra le prime due parti, contiene una riga di
            testo indicante cosa si sta facendo.
      \end{itemize}

\item L'interfaccia utente \`e costituita dal puntatore del mouse e dal
parser: per far compiere un'azione all'eroe \`e necessario cliccare sul
verbo corrispondente (situato nel parser), poi sull'oggetto/i sul quale
l'azione va compiuta. Per spostare l'eroe da un punto all'altro della
locazione \`e sufficiente cliccare sul punto di destinazione; il gioco
trova una via attorno agli eventuali ostacoli e mostra l'animazione
dell'eroe che cammina verso il punto selezionato.

\item L'eroe pu\`o essere visualizzato a un livello differente di zoom in
ogni punto della locazione; inoltre sia lui che altri personaggi
eventualmente presenti nel gioco, possono passare davanti o dietro a
determinati oggetti (come ad esempio un tavolo), dipendentemente del punto
in cui si trovano (e quindi della prospettiva). Sullo sfondo della
locazione possono essere visualizzate animazioni, sia indipendenti che
influenzate dalle azioni del giocatore, a una frequenza non necessariamente
uguale a quella dell'eroe (p. es.: l'eroe si muove a 50 fps, ma una ventola
in sottofondo gira a soli 10 fps).

\item Le reazioni ottenibili operando su un oggetto, o combinandone due,
sono completamente libere; questo significa che, compiendo un'azione
qualsiasi su un oggetto, si pu\`o causare la trasformazione dell'oggetto in
un altro, attivare un personaggio esterno, aprire una porta, eccetera.

\item La musica d'accompagnamento \`e influenzata dalle azioni del giocatore.
\end{enumerate}

Teniamo particolarmente a fare presente che tutti i punti di vista, le
soluzioni proposte e le affermazioni, spesso decise, contenute
nell'articolo, sono strettamente legate alla struttura interna di VEGA, e
quindi non sono da considerare n\'e perfette n\'e definitive, ma solo il
frutto di una singola esperienza e il conseguente tentativo di esporla con
la maggiore chiarezza possibile.

Dove non \`e diversamente specificato tutte le considerazioni prendono come
base hardware l'\A 1200 non espanso; questo significa che alcune
implementazioni protrebbero non rivelarsi ottimali su una macchina dotata
di un processore pi\`u veloce.


\paragrafo{L'avventura dal punto di vista del programmatore}

Chiunque abbia giocato almeno una volta a un'avventura grafica della
LucasArts non pu\`o non ricordare la grande sensazione di libert\`a che
questo genere di giochi sa dare (spesso dovuta alla possibilit\`a di
provare a fare un po' di tutto e al gran numero di locazioni accessibili).
Inoltre i giocatori ricordano e amano sicuramente le complesse situazioni
che venivano a crearsi, con oggetti che si trasformavano e combinavano
ripetutamente, nonch\'e la comodit\`a dell'interfaccia utente, apprezzata
soprattutto quando si doveva muovere il proprio eroe in punti poco
accessibili della locazione.

Dal punto di vista dello sviluppatore offrire queste e tutte le altre
meraviglie di un'avventura grafica presenta molteplici problematiche che
sorgono da tre fonti fondamentali:

\begin{itemize}
\item La necessit\`a di una forte generalizzazione: un'avventura grafica
\`e composta da diverse decine di locazioni, in ognuna delle quali si pu\`o
trovare praticamente ogni oggetto del gioco, che pu\`o subire
un'innumerevole quantit\`a di operazioni da parte del giocatore; tutto
questo rende a dir poco sconveniente la programmazione di ogni singola
locazione, di ogni singola situazione, direttamente nel codice, e impone al
programmatore la creazione di forti astrazioni, nonch\'e un'impostazione
del programma aperta e generalizzata.

\item La necessit\`a di trovare una vantaggiosa rappresentazione in memoria
degli elementi fondamentali del gioco (locazioni, oggetti, dialoghi,
uscite, personaggi e animazioni) e un agevole metodo per trattarli in modo
che possano correttamente realizzare le situazioni pi\`u disparate, che
siamo abituati a vedere nelle avventure.

\item L'implementazione tecnica; questa \`e capeggiata dalla problematica
tipica dei videogiochi: quella della velocit\`a in rapporto alla memoria
occupata (problematica importante soprattutto quando si vuol far girare il
tutto a 50 fps). In particolare occorre implementare la ricerca del
percorso tra due punti, il display grafico (e il relativo scrolling), lo
zoom del personaggio, le animazioni di sfondo, l'interfaccia utente, il
passaggio dell'eroe dietro ai cosiddetti ``backobject'' (si veda paragrafo
relativo), eccetera.

\end{itemize}


\paragrafo{Generalizzazione e organizzazione dei dati}

Al fine di riuscire a gestire la quantit\`a e complessit\`a delle
informazioni che definiscono un'avventura \`e bene attenersi ad alcune
semplici idee. La prima \`e quella di evitare di porre nel programma dati
relativi al gioco, ma solo le strutture necessarie per accoglierli. Questo
vale per tutte le informazioni riguardanti l'avventura, quindi non solo per
gli oggetti e le locazioni, ma anche per la risoluzione, il puntatore, i
nomi dei verbi del parser e tutto quanto \`e informazione.

Un'altra semplice ma efficace proposta \`e quella di porre i dati in
moltissimi piccoli file in modo che al momento opportuno il gioco possa
caricare l'informazione necessaria nelle zone di memoria preparate in base
alle strutture predefinite nel programma.

Esistono vari tipi di datafile, ognuno legato al tipo di elemento
dell'adventure che descrive (per esempio un datafile che descrive
un'animazione); una mossa necessaria per facilitare di molto lo sviluppo
del programma \`e quella di separare chiaramente tre famiglie di datafile:

\begin{enumerate}
\item Quelli che contengono informazioni relative ad una singola locazione;
      questi file, caricati all'entrata in ogni locazione, comprendono:
      \begin{itemize}
      \item definizioni dei backobject (si veda il paragrafo {\em Backobject});
      \item dati necessari al sistema di ricerca del percorso (si veda il paragrafo
            {\em Ricerca del percorso});
      \item definizioni dei livelli di zoom (trattato al paragrafo {\em
            Zoom});
      \item definizioni delle uscite dalla locazione (sia quelle attive che
            quelle inattive).
      \end{itemize}

\item Quelli pi\`u assoluti, che riguardano elementi che possono cambiare
      locazione

      \begin{itemize}
      \item Oggetti
      \item Dialoghi 
      \item Animazioni
      \end{itemize}

     Occorre utilizzare uno o pi\`u datafile per ogni oggetto, dialogo o animazione,
     contenenti grafica e descrizione delle caratteristiche (vedi paragrafo successivo).

\item Quelli contenenti le definizioni generali dell'avventura; questi file
vengono caricati tutti all'inizio del gioco; \`e importante descrivere
accuratamente la lista di tutte le locazioni esistenti, il parser (compreso
le coordinate dei pulsanti e delle celle dell'inventario), le fonti, le
costanti (come l'altezza della barra di stato, la velocit\`a dello
scrolling, eccetera) e soprattutto la lista dei nomi di tutti i datafile.

\end{enumerate}

Oltre a queste famiglie, \`e una buona idea utilizzare anche delle liste
che indichino in che locazione si trovano gli elementi assoluti all'inizio
del gioco; per esempio esiste una ObjectList che associa a ogni oggetto la
locazione e lo stato in cui si trova all'inizio del gioco (riferirsi al
paragrafo seguente per il concetto di stato di un oggetto). In analogia
esistono anche la DialogList, la AnimList e due liste puramente destinate a
indicare lo stato booleano delle linee dei dialoghi e delle uscite delle
locazioni (LinesList, ExitList). Questo \`e molto comodo perch\'e permette
di cambiare la situazione degli elementi assoluti del gioco senza cambiare
i loro datafile, n\'e i dati in memoria. Un altro vantaggio \`e
rappresentato dal fatto che per salvare una partita non occorre salvare
tutti i datafile ma solo le liste.

Nella mia proposta le unit\`a fondamentali sono le locazioni; l'entrata in
una nuova locazione provoca il caricamento di tutti i suoi file relativi,
il controllo delle liste e il successivo caricamento dei datafile
riguardanti gli elementi assoluti che la riguardano al momento. In questo
modo \`e possibile caricare solo quando l'eroe cambia locazione
(concentrare i tempi di caricamento riduce le interruzioni e quindi aumenta
la giocabilit\`a) e quindi ottimizzare la memoria occupata, ottenere un
sistema flessibile e generale. Per esempio, se un'operazione fatta dal
giocatore dovesse attivare la presenza di un dialogo in un'altra locazione,
basterebbe cambiare in memoria la Dialoglist; infatti se l'eroe arrivasse
nella locazione interessata, il programma esaminerebbe la lista e
caricherebbe di conseguenza i dati relativi a quel dialogo.

\paragrafo{Rappresentazione e trattamento di oggetti}

Gli oggetti sono gli elementi principali di un'avventura, ed \`e su di essi
che il giocatore compie quasi tutte le azioni che lo portano alla
soluzione.

Un singolo oggetto \`e composto fondamentalmente dalle sue propriet\`a
(posizione, grafica, nome, eccetera), tra le quali vi \`e la lista di tutte
le possibili reazioni alle mosse del giocatore. Queste ultime sono chiamate
``azioni'' in questo articolo e corrispondono ai verbi presenti nel parser.
La versatilit\`a degli oggetti rende molto complessa la loro
rappresentazione ed implementazione, di cui ci occupiamo in questo
paragrafo.

Osserviamo l'esempio seguente:

\noindent il personaggio prende una scatola (che passa quindi
nell'inventario), la apre e vi trova un coltellino. In seguito a ci\`o il
giocatore osserva che il personaggio ha espresso il suo stupore con
un'esclamazione e che il coltellino \`e apparso nell'inventario.

Nella locazione seguente usa il coltellino con una leva incastrata,
sbloccandola, e ottenendo cos\`{\i} il cambiamento sia del nome che delle
propriet\`a dell'oggetto ``leva bloccata". Come ultima mossa il giocatore
usa la leva; il gioco mostra l'animazione della leva che si muove e di una
porta che si apre; ora il giocatore pu\`o uscire dalla locazione grazie
all'uscita che \`e appena stata attivata.

Questo esempio mostra chiaramente le caratteristiche fondamentali degli
oggetti:

\begin{itemize}
\item Esistono vari tipi di eventi in reazione ad un'azione compiuta su un
oggetto (il personaggio comincia a parlare, un'uscita si attiva, parte
un'animazione, si aggiunge una riga ad un dialogo, eccetera). In
particolare \`e possibile che un oggetto modifichi le propriet\`a di
un altro o addirittura di se stesso.

\item La reazione ad un'azione pu\`o essere cosituita da una intera lista
di eventi di lunghezza indefinita. Gli eventi della lista possono essere di
diverso tipo e posti in qualsiasi sequenza.

\item Sugli oggetti possono essere compiute molte azioni differenti, ma nessuna
ha una rezione tipica; ogni azione pu\`o portare a qualsiasi tipo di
reazione (lista di eventi).

\item Ogni azione pu\`o avere uno o due oggetti come parametro. Questo
pu\`o variare, in funzione degli oggetti e dell'azione, piuttosto liberamente.
Un'azione che combina due oggetti pu\`o dare diverse reazioni in funzione
delle propriet\`a dei due oggetti.

\item Vi possono essere differenti reazioni ad una medesima azione in funzione
   delle propriet\`a attuali dell'oggetto.
\end{itemize}

Per realizzare degli oggetti che riescano a soddisfare tutte queste
esigenze \`e quantomai importante scegliere una buona rappresentazione
delle informazioni che li compongono e buoni metodi per trattarli. In
particolare occorre tenere presente che gli oggetti possono modificare le
loro propriet\`a e quelle di qualunque altro oggetto in qualsiasi momento.

Una prima analisi del problema potrebbe suggerire come soluzione quella di
memorizzare tutti i cambiamenti che vanno effettuati in ognuna delle
possibili reazioni alle azioni del parser, e di applicare tali cambiamenti
ai dati dell'oggetto in memoria al momento opportuno. Questo per\`o
significherebbe creare strutture molto complesse per memorizzare gli
oggetti, problemi enormi nel controllare se le propriet\`a attuali sono
quelle giuste per attivare la reazione (p.~es.: per aprire una porta questa
deve essere chiusa), nonch\'e l'impossibilit\`a pressoch\'e totale di
ridare all'oggetto le propriet\`a passate (cosa molto frequente nelle
avventure). Per meglio comprendere il problema, basta immaginare cosa
bisognerebbe memorizzare per realizzare la situazione seguente: una leva
premuta, in particolari condizioni genera una reazione che modifica se
stessa nonch\'e il proprio nome e quello di un quadro, che si trova nelle
vicinanze. Ora vogliamo che, ripremendo la leva, il quadro riassuma il nome
originale.

Una semplice idea che per\`o facilit\`a enormemente le cose, permettendo la
gestione di situazioni molto complesse velocemente \`e il concetto di
stato: le propriet\`a (comprese le reazioni) sono memorizzate in molte
copie, una per ogni differente situazione (stato appunto) dell'oggetto nel
gioco. Quando un oggetto cambia le sue propriet\`a (soprattutto se cambia
una reazione) nulla viene riscritto in memoria, ma solo il numero del suo
stato viene aggiornato grazie ad una veloce modifica alla ObjectlList. In
questo modo \`e molto facile sia organizzare differenti reazioni in
funzione delle propriet\`a, sia controllare se una reazione deve avere
luogo, nonch\'e avere implementazioni ricorsive (in cui un oggetto modifica
se stesso).

Per garantire inoltre la possibilit\`a di avere ogni tipo di reazione ad
ogni tipo d'azione \`e necessario mettere tutte le azioni sullo stesso
piano, eliminando ogni caratteristica imposta dal nome e trasformandole
tutte in semplici ``potenziali contenitori'' di liste di reazioni
(praticamente non c'\`e pi\`u alcuna differenza interna tra i verbi del
parser).

Vediamo ora di riassumere e applicare queste idee, scrivendo un modello di
organizzazione di un oggetto:


\begin{smallcodestyle}
 Header
   Contenente:
   nome del file grafico dell'oggetto
   altri dati molto generali, e indipendenti
   dallo stato

 Definizione primo stato:
    Propriet\`a dell'oggetto: nome, posizione
      (numero della locazione), coordinate,
      punto di interazione, flag vari
    Lista di reazioni alla prima azione
     ...
    Lista di reazioni all'ultima azione
      (corrispondente all'ultimo verbo del
      parser)

 Definizione secondo stato:
    Propriet\`a dell'oggetto
    Liste di reazioni

 ...

 Definizione ultimo stato
\end{smallcodestyle}


Ogni lista di reazioni pu\`o contenere un numero imprecisato di comandi
seguiti dai relativi parametri, come nell'esempio seguente, che mostra la
reazione al tentativo di aprire una porta da parte del giocatore
(ammettettiamo che la porta sia il primo oggetto del gioco). Prego di
notare che la sintassi \`e scelta solo a scopo didattico.

\begin{smallcodestyle}
 Say "Questa porta sembra quella di casa mia"
   ; Comanda al personaggio di dire la frase
   ; racchiusa tra le virgolette
 Modifica 01 02
   ; Forza il primo oggetto a passare al secondo
     stato
 Anim 01
   ; Provoca l'attivazione immediata della prima
   ; animazione della AnimList (animazione della
   ; porta che si apre)
\end{smallcodestyle}

Se la porta deve essere apribile solo grazie a una chiave (oggetto numero
2) e mandare invece un messaggio di errore se si tenta di aprirla con un
cacciavite (oggetto numero 3), basta inserire nella lista delle reazioni
all'azione ``apri'' della porta nel primo stato, un comando di controllo
che permetta l'esecuzione della reazione solo in caso di combinazione con
l'oggetto ``chiave'' nel giusto stato (cio\`e presa, e quindi presente
nell'inventario, in un ipotetico secondo stato):

\begin{smallcodestyle}
Combina 02 02
   ; quello che segue va eseguito solo se il
   ; secondo parametro ; di "apri" \`e la
   ; chiave nel secondo stato
 Say "Si apre !"
 Modifica 01 02
 Anim 01
Combina 03 01
   ; quello che segue \`e riservato alla 
   ; combinazione col cacciavite nel primo stato
 Say "Non si apre !"
\end{smallcodestyle}

Dal momento che l'utente sceglie il verbo ``apri'', poi l'oggetto porta, il
gioco, analizzando la lista di reazione, vede subito che \`e necesario un
secondo parametro, cos\`{\i} si risolve anche il problema della
variabilit\`a del numero dei parametri (infatti un altro oggetto che non
usi il comando ``Combina'' verr\`a subito identificato e il programma non
aspetter\`a la selezione di un secondo oggetto).

Vediamo infine un esempio completo di come potrebbe apparire il file
contente la definizione di un oggetto del genere:

\begin{smallcodestyle}
01 / Porta.gfx
   ; header, con numero dell'oggetto 
   ; e file grafico

 Stato 1
    ; dichiarazione d'inizio del primo stato
  "Porta Chiusa" ; 01 ; 100, 250
    ; propriet\`a: nome, locazione in
    ; cui \`e presente, coordinate
  Act 1: Say "Bella porta!"
    ; reazione alla prima azione del parser
    ; (p. es: "esamina"), nel primo stato
  Act 2: Combina 02 02
         Say "Si apre !"
         Modifica 01 02
         Anim 01     ; reazione all'azione "apri"
        Combina 03 01
         Say "Non si apre !"
  Act 3: Sound 12
    ; se compi la terza azione (p. es: "Tira")
    ; il gioco reagisce con un effetto sonoro

 Stato 2
  "Porta aperta" ; 01 ; 100, 250
  Act 1: Say "\`e aperta!"
\end{smallcodestyle}

A titolo di ulteriore esempio guardiamo la soluzione al problema posto
prima, in cui c'era interazione reciproca tra una leva e un quadro.
Definiamo la leva come primo oggetto e il quadro come secondo; inoltre
poniamo che l'azione 1 corrisponda a ``premi'' e l'azione 2 a ``esamina".

\begin{smallcodestyle}

01 / Leva.gfx

 Stato 1
  "Leva" ; 01 ; 100, 100
  Act 1: Modifica 02 02
         Modifica 01 02
         Say "Ho cambiato lo stato della leva e
         del quadro, e, ci\`o che conta di pi\`u,
         anche la reazione all'azione di premere
         la leva"
 Stato 2
  "Leva premuta" ; 01 ; 100,105
  Act 1: Modifica 02 01
         Say "Ho ripristinato il nome originale
         del quadro"


02 / Quadro.gfx

 Stato 1
  "Quadro" ; 01 ; 150, 100
  Act 2: Say "Il mio nome \`e quadro"

 Stato 2
  "Soqquadro" ; 01 ; 150, 100
  Act 2: Say "La leva \`e stata premuta !"
\end{smallcodestyle}


L'implementazione di tal idea nel programma \`e piuttosto semplice: si
tratta solo di caricare queste informazioni (meglio se compilate in una
forma pi\`u compatta) appena l'eroe entra nella locazione contenente la
porta e di modificare gli stati degli oggetti coinvolti in base alle
reazioni, semplicemente scrivendo nell'area di memoria in cui \`e custodita
l'ObjectList.

Successivamente, per decidere quale reazione generare, il programma non
dovr\`a far altro che leggere l'ObjectList; in questo modo durante tutta la
partita \`e possibile tenere conto di tutte le operazioni fatte dal
giocatore semplicemente guardando le liste, mentre i dati in memoria possono
comodamente essere eliminati appena usciti dalla locazione. Questa
caratteristica \`e molto comoda: quando rientro nella locazione dopo aver
aperto la porta rileggo lo stesso file di dati precedente, ma trovo la
porta in uno stato differente (aperta) grazie alla ObjectList.

Queste idee sono applicabili molto facilmente anche ai dialoghi, lavorando
per analogia: basta considerare le pagine come gli stati, l'attivazione di
ogni linea come una propriet\`a e come azione il fatto di selezionarla.
Naturalmente la selezione di una linea deve permettere la libert\`a di
reazione di cui godono anche gli oggetti, cos\`{\i} che, per esempio,
dicendo una frase a un personaggio sia possibile far apparire un oggetto
nell'inventario, simulando il fatto che mi sia stato dato.


\paragrafo{La ricerca del percorso}

Uno degli elementi pi\`u importanti per la giocabilit\`a di un'avventura
\`e la presenza di un sistema che calcoli il percorso da far compiere al
personaggio quando il giocatore clicca su un punto non raggiungibile
direttamente (cio\`e muovendo il personaggio in linea retta).

Questo problema \`e probabilmente il pi\`u interessante, sia dal punto di
vista teorico che da quello dell'implementazione, che si presenta durante
lo sviluppo di un'avventura grafica.

Le caratteristiche sono semplici a dirsi: in qualsiasi punto il giocatore
possa cliccare, e in qualsiasi punto il personaggio si trovi, questo deve
andare esattamente dove richiesto; se il punto in questione \`e
completamente irragiungibile (p. es. su un muro), allora deve almeno
avvicinarvisi il pi\`u possibile.

Sebbene vi siano algoritmi per realizzare ci\`o, sono tutti piuttosto lenti
e spesso imprecisi (il personaggio arriva ``nelle vicinanze'' del punto
richiesto). Nelle righe seguenti presento l'algoritmo sviluppato
appositamente per Vega, che permette di risolvere praticamente tutte le
situazioni in un tempo accettabile e con un'assoluta precisione (sebbene
consumi una grossa quantit\`a di memoria; si veda il paragrafo {\em Memoria
e velocit\`a} per ulteriori considerazioni).

Nelle righe che seguono vediamo come preparare i dati per l'algoritmo e poi
affronteremo il suo funzionamento e una proposta di implementazione nel
programma.

L'algoritmo che presento si basa su un concetto di geometria molto
semplice: dato un poligono convesso, e dati due punti qualsiasi all'interno
di esso, il segmento di retta che li congiunge \`e sempre completamente
contenuto nel poligono.

La prima cosa da fare \`e quella di ben definire l'area percorribile dal
giocatore. Per l'algoritmo l'approccio pi\`u semplice consiste nel
tracciare un grosso poligono che rappresenti l'insieme di tutti i punti in
cui l'eroe pu\`o camminare (p. es.: se c'\`e un tavolo in mezzo alla
locazione, vi sar\`a un buco in mezzo al poligono). Per decidere se un
punto \`e percorribile o meno si valuti se l'hot spot dell'immagine
dell'eroe (tenendo conto dello zoom), pu\`o esistere in quel punto. Per
semplificare le cose suggerisco di fissare come hot spot il centro
dell'ultima linea dell'immagine del personaggio (cos\`{\i} si potra
semplicemente riflettere se l'eroe pu\`o ``camminare in quel punto").

Ora \`e necessario suddividere il grosso poligono (che sar\`a quasi
sicuramente concavo) in tanti altri pi\`u piccoli e convessi (questo \`e
matematicamente sempre possibile).

Si definisca ora un punto per ogni poligono (detto ``centro"), in modo che
il centro di ognuno possa essere collegato con una line retta al centro di
tutti i poligoni a lui adiacenti, senza mai uscire dal grosso poligono
iniziale.

Ora che tutto il materiale \`e pronto passiamo a descrivere il
funzionamento di una prima semplice versione dell'algoritmo. Definiamo come
$(X_i,Y_i)$ il punto di partenza, e come $(X_f,Y_f)$ il punto d'arrivo. Lo scopo \`e
trovare una lista di punti che, se collegati da linee rette, traccino una
strada tra $(X_i,Y_i)$ e $(X_f,Y_f)$ senza mai uscire dall'area percorribile.

La lista \`e generata dalle tappe seguenti:


\begin{enumerate}
\item il primo punto \`e $(X_i,Yi)$;
\item il secondo punto \`e il centro del poligono in cui giace $(X_i,Y_i)$.
      Questo punto pu\`o sicuramente essere collegato con un segmento di
      retta a $X_i,Y_i)$ siccome il poligono \`e convesso;
\item il prossimo punto \`e il centro di uno dei poligoni adiacenti al
      precedente. Per la precisione si tratta del poligono il cui centro \`e
      pi\`u vicino a quello del poligono in cui giace il punto d'arrivo (poligono
      finale);
\item ripetere il punto tre fino a quando ci si ritrova nel poligono finale;
\item l'ultimo punto \`e il punto d'arrivo (infatti questo \`e sicuramente
      collegabile al centro del poligono finale per il solito principio di
      geometria).
\end{enumerate}

Sebbene scegliendo accuratamente i poligoni e i centri questo algoritmo
funzioni per la grande maggioranza dei casi, \`e ancora piuttosto
primitivo, e spesso mostra strade poco naturali (il personaggio fa delle
curve inutili, ritorna su se stesso, eccetera).

Vediamo quindi le principali migliorie che possiamo applicare:

\begin{itemize}
\item Permettere il taglio della seconda tappa: se il punto iniziale \`e
collegabile con una linea retta al centro del secondo poligono, non \`e
necessario passare dal centro del primo, risparmiando cos\`{\i} un punto.
Per analogia \`e a volte possibile eliminare il passaggio dal centro del
poligono finale. Basta inserire un controllo tra la prima e la seconda
tappa (nonch\`e tra la penultima e l'ultima) per realizzare quest'idea che
spesso rende il movimento molto pi\`u naturale.

\item Estendere il concetto di poligono adiacente in modo che, dato un
poligono, siano considerati a lui adiacenti tutti i poligoni i cui centri
sono collegabili, anche se molto distati e disgiunti. Quest'idea \`e molto
semplice da applicare e spesso accorcia di molto la via, ma pu\`o avere
conseguenze inattese e anche impedire il successo dell'algoritmo, nel caso
in cui non si siano scelti pi\`u che attentamente i centri dei poligoni.

\item Impedire che l'eroe ritorni al poligono precedente. Questo \`e
capitale soprattutto nel caso in cui la via da trovare sia piuttusto
contorta, per ``forzare'' l'eroe nella giusta direzione.

\item Permettere il taglio totale del percorso: se il punto iniziale \`e
collegabile direttamente col finale, \`e opportuno evitare l'algoritmo e
semplicemente muovere l'eroe in linea retta. Quest'eventualit\`a si presenta
pi\`u spesso di quanto si possa pensare.

\item Nel caso in cui il punto finale si trovi fuori dall'area percorribile
(il giocatore ha cliccato su un muro, o altro), prima della prima tappa
occorre ridefinire il punto d'arrivo in modo da intuire in quale punto
dell'area percorribile il giocatore preferirebbe posizionare il
personaggio. Intuitivamente questo \`e il punto, facente parte dell'area
percorribile, pi\`u vicino a quello scelto dal giocatore. L'esperienza
mostra per\`o che \`e comunque preferibile non cercare in tutte le
direzioni ma essenzialmente verso il basso, siccome spesso il giocatore
preme sui punti in cui vorrebbe portare la testa del personaggio
(p.~es. quadri, o altre cose da guardare), mentre l'area percorribile, come
detto, \`e disegnata in funzione del centro dei piedi. Se non si trova
nessun punto adatto sotto a quello selezionato, allora conviene cercare
contemporaneamente nelle due direzioni orizzontali, e solo se anche questo
fallisce, anche nella verticale (\`e infatti molto improbabile che il
giocatore intenda posizionare i piedi del personaggio sopra al punto in cui
ha cliccato).

\item Imporre una lunghezza massima della lista di punti, in modo che, se
sorpassata, l'algoritmo si arresti. In questo modo \`e possibile evitare
cicli infiniti e conseguenti tilt del sistema. Questa lunghezza pu\`o
corrispondere a qualche unit\`a in pi\`u del numero di poligoni esistenti.

\end{itemize}

Applicando queste migliorie e scegliendo con attenzione i poligoni e i
rispettivi centri, l'algoritmo \`e in grado di risolvere la stragrande
maggioranza delle situazioni che si presentano in un'avventura.

Ora che abbiamo visto la teoria, non ci resta che passare
all'implementazione del metodo nel programma. La via pi\`u semplice (che da
anche i risultati pi\`u veloci) \`e quella di basarsi sui tre elementi
seguenti, che descrivono completamente la situazione:

\begin{itemize}
\item una bitmap contente l'immagine dell'area percorribile, gi\`a
suddivisa in poligoni convessi, ma senza i relativi centri (questo per
evitare possibili confusioni in futuro). Questa pu\`o essere generata
facilmente a partire dall'immagine con i centri (che va tracciata
manualmente). Nel tracciare l'immagine \`e molto importante usare un colore
diverso per ogni poligono, evitare l'uso di bordi, e usare sempre lo stesso
colore per i centri. Seguendo queste direttive sar\`a molto facile
identificare i poligoni e assegnarvi un numero (p. es il numero del colore
in cui sono tracciati, informazione velocemente ottenibile), nonch\'e
operare sui centri.

\item una lookup table che indichi le distanze tra i centri di tutte le
coppie possibili di poligoni (anche questa \`e facilmente generabile
tramite un breve algoritmo).

\item un file contenente la lista delle coordinate di tutti i centri
(generabile a partire dall'immagine iniziale).

\item un file contenente una tabella booleana che indichi, per tutte le
coppie di poligoni possibili, se sono adiacenti o no (il concetto di
adiacenza pu\`o variare a seconda della versione dell'algoritmo che si
intende implementare).
\end{itemize}

Naturalmente le informazioni contenute negli ultimi due ultimi file
potrebbero essere rigenerate ogni volta che il personaggio entra in una
locazione, ma questo richiederebbe molto tempo; \`e quindi consigliabile
effettuare i calcoli una volta per tutte (tramite un programma di
generazione automatica) e limitarsi a caricare i tre file ad ogni cambio di
locazione.

Una volta che questi dati sono posti nelle apposite strutture in memoria (3
lookup table e una bitmap), l'algoritmo, che si riduce a un paio di cicli,
pu\`o operare molto velocemente; infatti per sapere qual'\`e il numero del
poligono in cui si trova il punto di partenza basta leggere il colore del
punto corrispondente sulla bitmap contenente l'area percorribile.
Analogamente \`e ottenibile il numero del poligono in cui giace il punto
d'arrivo. Inoltre per conoscere le distanza, le condizioni di adiacenza e
la posizione dei centri basta accedere alle lookup table.

Come accennato l'unico svantaggio di questa implementazione \`e
l'occupazione della memoria relativamente elevata: occorre infatti aprire
una bitmap delle stesse dimensioni dell'area di gioco e di profondit\`a
pari al logaritmo in base 2 del numero di poligoni presenti (cio\`e del
numero di colori necessari).


\paragrafo{Backobject}
\index{backobject}

Le locazioni di un'avventura non sono composte solo da un'immagine che fa
sempre da sfondo al personaggio: vi sono oggetti e parti del disegno
concepiti per trovarsi in primo piano, altre in posizione intermedia e
cos\`{\i} via. Dei tipici esempi sono: statue in mezzo a una piazza,
mobili, oggetti in controluce posti in primissimo piano, eccetera.

La presenza di questi elementi (detti ``backobject'' in Vega) fornisce alla
locazione una maggiore profondit\`a aumentando il realismo del gioco.

In molti casi il personaggio pu\`o venire a trovarsi davanti o dietro a un
backobject a seconda della sua posizione in relazione alla prospettiva (p.
es: se c'\`e un tavolo posto in mezzo alla locazione il personaggio vi
passer\`a davanti se si trover\`a, dal punto di vista delle coordinate
bidimensionali, pi\`u basso di esso, mentre vi passer\`a dietro in caso
contrario).

Da questa prima introduzione, possiamo subito comprendere che la
caratteristica di un backobject \`e essenzialmente la libert\`a: esso pu\`o
infatti trovar posto ovunque e trovarsi dietro/davanti al personaggio senza
regole ben definite, o meglio dipendenti dalla prospettiva, che per\`o
pu\`o cambiare notevolmente da locazione a locazione e da avventura a
avventura.

Una semplice soluzione \`e quella di rappresentare il backobject in memoria
con i dati seguenti:

\begin{enumerate}
\item Una piccola bitmap contenente l'immagine del backobject (se il
backobject \`e un tavolo, questa bitmap conterr\`a l'immagine del tavolo
``ritagliata'' fuori dalla locazione).

\item Quattro coordinate indicanti l'area (rettangolare) d'attivazione:
l'entrata del personaggio in quest'area informa il gioco che esso si trova
dietro al backobject e che quindi quest'ultimo deve venir attivato. Questo
significa praticamente che il backobject viene ridisegnato sopra al
personaggio (grazie all'immagine del primo punto). Queste coordinate
possono facilmente essere determinate disegnando un rettangolo sopra
l'oggetto in questione, meglio se con un programma apposito.

\item Le coordinate alle quali va disegnata l'immagine del backobject in
caso di attivazione (queste possono essere trovate facilmente da un
programma che compari l'immagine del punto uno con la locazione, cercando
in quest'ultima una parte completamente identica alla prima e ritornando
poi le sue coordinate).
\end{enumerate}

Grazie a questa rappresentazione \`e possibile determinare facilmente e
velocemente se il personaggio si trova davanti al backobject (e quindi
nulla debba accadere) o meno (e quindi il backobject debba essere
ridisegnato sopra al personaggio).

Sebbene la fase di ridisegnamento non ponga problemi di sorta (un semplice
accesso al blitter \`e sufficiente) la determinazione di quali backobject
siano attivi pu\`o essere effettuata in almeno due modi:

\begin{enumerate}
\item Un ciclo che passi in rassegna tutti i backobject della locazione,
confrontando tutte le coordinate dei rettangoli di attivazione con quelle
del personaggio. Questo metodo (attualmente applicato in Vega) \`e molto
economico in termini di memoria ma pu\`o risultare piuttosto lento nel caso
in cui vi siano moltissimi backobject nella locazione (pi\`u di 30).

\item Aprire una bitmap delle stesse dimensioni della locazione e
disegnarvi sopra i rettangoli di attivazione di tutti i backobject
(basandosi sulle coordinate del punto 2) in modo che ognuno sia tracciato
col colore corrispondente alla sua posizione nella lista di tutti i
backobject della locazione. Ora \`e sufficiente controllare il colore del
punto in cui si trova il personaggio per sapere dietro a quale backobject
si trova (o eventualmente se non si trova dietro a nessun backobject).

Questo metodo \`e molto pi\`u veloce, ma non permette di avere backobject
le cui aree di attivazione sono sovrapposte e costa relativamente molta
memoria. Consiglio di applicarlo solo se i backobject sono veramente molti,
e se la macchina su cui gira il gioco pu\`o comodamente permettersi il
sacrificio di memoria.
\end{enumerate}

Considerata la quantit\`a media di backobject in una locazione (intorno ai
7, al massimo una decina), il primo metodo \`e quasi sempre consigliabile,
sebbene una soluzione ottimale sarebbe quella di far decidere al programma
al momento dell'entrata in ogni locazione.

Nel caso in cui una locazione contenga un grosso backobject (un lungo
muretto per esempio) \`e decisamente consigliabile dividerlo in due o pi\`u
parti e trattarlo come pi\`u backobject differenti; questo evita di dover
ridisegnare parti inutili di grafica, e quindi di rischiare di rallentare
il gioco.


\paragrafo{Animazioni}
Esistono fondamentalmente due tipi di animazioni: quella del personaggio
principale e quelle di decorazione (tra le quali animazioni di sfondo,
dialoghi coi personaggi, eccetera). Per il programmatore, la differenza
principale tra le due consiste nel fatto che il personaggio \`e sempre
presente sul display, mentre le seconde possono venir attivate e
disattivate.

Vediamo molto rapidamente quali sono le condizioni necessarie per
l'animazione del personaggio principale, che, avendo la pi\`u grande
importanza, influenzano fortemente l'implementazione delle animazioni del
secondo tipo:

\begin{itemize}
\item Per garantire la qualit\`a dell'animazione del personaggio s'impone
l'implementazione di un display di gioco in double buffer nonch\'e la
scelta di una frequenza d'alternanza delle due bitmap piuttosto elevata. Il
valore ideale di questa frequenza \`e di 50 volte al secondo, in modo da
sincronizzarsi perfettamente col refresh hardware del display e quindi
guadagnare facilmente molta qualit\`a. Nelle righe seguenti questa
frequenza verr\`a chiamata ``frequenza principale".

\item La presenza dello sfondo richiede l'inserimento nel ciclo
      d'animazione di una fase di salvataggio di questo in due buffer di memoria
      (prima di disegnare il personaggio) e un'ulteriore fase di ripristino
      (quando il personaggio si deve spostare).

      Quindi il processo di disegno di un frame si compone essenzialmente di tre
      parti:

      \begin{enumerate}
      \item cancellamento del vecchio frame, ottenuto sovrapponendo ad esso
            l'immagine dello sfondo che \`e custodita in un buffer di
            memoria;
      \item salvataggio della parte di sfondo dove sar\`a posto il nuovo
            frame nel buffer di memoria corrispondente alla bitmap
            correntemente visualizzata;
      \item blitting del nuovo frame, nella nuova posizione e nella nuova
            immagine.
\end{enumerate}

\end{itemize}


Trattare qui in dettaglio le tecniche di base d'animazione sarebbe troppo
dispendioso in termini di spazio e potrebbe risultare noioso per i lettori
pi\`u esperti; per ulteriori dettagli rimando a un mio altro articolo,
presente sul CD di IPISA~'96.

Tenendo presente che il personaggio deve sempre poter essere mosso risulta
che \`e pressoch\'e obbligatorio mantenere un double buffer a frequenza
costante ed elevata per tutta la durata del gioco.

Nelle righe seguenti si indicher\`a come ``bitmap attuale'' quella non
visualizzata, dove tutte le operazioni di blitting hanno luogo, mentre con
``bitmap visualizzata" l'altra.

Questo fattore ci permette di separare le animazioni del secondo tipo in
due gruppi:

\begin{enumerate}
\item Le animazioni che vanno mostrate a una frequenza identica a quella
principale. L'implementazione di queste animazioni non crea troppi problemi
dato che \`e possibile visualizzarle semplicemente inserendo nuove
istruzioni per il blitter nel ciclio principale (quello che disegna il
personaggio) e salvando lo sfondo negli stessi buffer di memoria utilizzati
dal personaggio. In questo modo otterremo un aggiornamento parallelo del
personaggio e dell' animazione in questione, che \`e appunto ci\`o che
desideriamo.

\item Le animazioni che vanno mostrate ad una frequenza inferiore a quella
principale (sono quindi dette animazioni asincrone). Sebbene sarebbe
possibile trasformare queste animazioni in casi del primo gruppo
semplicemente ridisegnando pi\`u volte gli stessi frame d'animazione,
questo rappresenterebbe uno spreco delle risorse (infatti il blitter
sarebbe costretto a ridisegnare pi\`u volte la stessa immagine
inutilmente). Tutti i problemi generati da questo tipo d'animazioni sorgono
dall'impossibilit\`a di seguire la frequenza principale.
\end{enumerate}

L'implementazione reale del secondo punto pone qualche problema, che
cercheremo di affrontare e risolvere nelle righe successive.

Il primo problema \`e quello di decidere quando \`e il caso di aggiornare
l'animazione asincrona. Una semplice soluzione \`e quella di determinare la
sottofrequenza alla quale disegnare i frame dell'animazione, che esprima il
rapporto tra la frequenza desiderata e quella principale. Ad esempio: se il
display \`e aggiornato a 50 fps e voglio un'animazione a 5 fps il rapporto
cercato \`e di un decimo, cio\`e 0,1. Questo rapporto esprime quindi ogni
quanti cicli principali devono essere inserite le istruzioni per il disegno
del prossimo frame dell'animazione (0,1 indica un frame ogni dieci cicli).
Quindi, per disegnare il prossimo frame al momento giusto \`e sufficiente
inserire un accumulatore nel ciclo principale al quale venga ogni volta
aggiunto il valore della sottofrequenza; quando l'accumulatore avr\`a
raggiunto o sorpassato il valore di 1 verr\`a detto al blitter di disegnare
il prossimo frame (inoltre, verr\`a azzerato l'accumulatore).

Il secondo problema che si pone \`e quello dell'impossibilit\`a di
utilizzare i buffer del personaggio principale poich\'e questo
comporterebbe il ripristino dello sfondo sotto all'animazione ogni 50esimo
di secondo, provocando fastidiosi effetti di flickering (infatti, nella
zona d'animazione, si alternerebbero fasi in cui si pu\`o vedere lo sfondo
a fasi, meno frequenti, in cui \`e mostrato il frame d'animazione attuale).
S'impone quindi la separazione dei momenti di ripristino dello sfondo del
personaggio e dell'animazione asincrona. Questo \`e ottenibile
semplicemente memorizzando gli sfondi in buffer di memoria differenti,
permettendo cos\`{\i} di copiarne il contenuto sulla bitmap in momenti
differenti.

Il terzo e pi\`u complesso problema risiede nel fatto che il processo di
disegno di un frame tocca soltanto una bitmap alla volta (questo \`e
infatti il senso e lo scopo del double buffer). Questo va bene fintanto che
la bitmap pi\`u aggiornata \`e sempre mostrata sul display mentre quella da
aggiornare \`e sempre tenuta nascosta, cosa che accadrebbe se l'animazione
fosse sincronizzata con la frequenza principale. Dato che questa condizione
non \`e verificata si ha il fenomeno seguente:

\begin{enumerate}
\item Appena l'accumulatore arriva a 1, la bitmap attuale viene aggiornata
      in modo da mostrare il prossimo frame d'animazione e l'accumulatore
      viene azzerato.
\item Nel ciclo successivo viene mostrata la bitmap appena aggiornata ma
      NON viene aggiornata la bitmap attuale poich\'e l'accumulatore \`e
      inferiore a uno. Fin qui, questo \`e esattamente ci\`o che volevamo
      realizzare.
\item Nel ciclo ancora successivo subentra il problema: le bitmap vengono
      nuovamente scambiate e sul display viene posta una bitmap NON
      aggiornata, contenente ancora l'immagine dell'animazione com'era
      prima che l'accumulatore arrivasse a 1.
\item Nel ciclo successivo viene nuovamente mostrata la bitmap aggiornata.
\item Le fasi 3 e 4 si alternano fino a quando l'accumulatore ritorna a 1;
      questo significa che nella zone dell'animazione si osservera un
      continuo alternarsi di due immagini differenti (cosiddetto
      flickering) per la durata di diverso tempo (tanto maggiore quanto \`e
      bassa la sottofrequenza).
\item Si ritorna al punto 1, quindi vi sar\`a un breve periodo in cui sullo
      schermo sar\`a mostrata l'immagine giusta (fase 2) per poi ricadere nel
      problema citato.
\end{enumerate}

Sebbene questo errore di display sia piuttosto complesso da descrivere
nonch\`e da scovare vi \`e una soluzione piuttosto semplice: fare in modo
che ogni aggiornamento provochi la modifica delle due bitmap. Questo
naturalmente non pu\`o accadere contemporaneamente, altrimenti si corre il
rischio di produrre scatti e flickering sulla bitmap correntemente
visualizzata (che appunto non va mai toccata). Ci si deve quindi limitare
ad aggiornare la bitmap attuale e rinviare al ciclo successivo le atre
operazioni. Nel caso di pi\`u animazioni asincrone \`e quindi consigliabile
tenere una lista delle operazioni da fare nel prossimo ciclo sulla bitmap
correntemente visualizzata.

Il metodo proposto realizza dunque le animazioni a bassa frequenza su un
display a frequenza superiore, ma al prezzo di raddoppiare il numero di
accessi al blitter e quindi il costo in termini di risorse. Questo
significa che un'animazione a 25 fps che gira su un display a 50 fps,
sfruttando il metodo proposto consuma tanto quanto consumerebbe se girasse
a 50 fps: infatti ogni due cicli si renderebbe necessario aggiornare due
bitmap (una subito, una il ciclo successivo), il che caricherebbe il
sistema esattamente tanto quanto l'aggiornamento di una sola bitmap per
ogni ciclo. Se per\`o consideriamo un'animazione a 5 fps (sempre su un
display a 50 fps), ci rendiamo conto che il metodo richiede l'aggiornamento
di due bitmap ogni 10 cicli, con un risparmio netto di $8\times5=40$ cicli
ogni secondo, durante i quali non \`e necessario preoccuparsi
dell'animazione.

In conclusione, il metodo \`e dunque vantaggioso solo per le animazioni a
frequenza inferiore alla met\`a della principale. Sebbene questo possa
sembrare escludere moltissimi casi e quindi ridurre l'utilit\`a del metodo,
\`e sufficiente riflettere sul fatto che la grande maggioranza delle
animazioni pu\`o girare a una frequenza molto inferiore ai 50 fps, che
invece \`e necessaria per dare un movimento di qualit\`a al personaggio;
questo significa che si crea un distacco piuttosto notevole tra la
frequenza principale e quella della maggioranza delle animazioni,
condizione che appunto moltiplica l'utilit\`a del metodo e ne giustifica
l'applicazione.

Dal punto di vista della rappresentazione e del trattamento delle
informazioni, un'animazione \`e definita dagli elementi seguenti:

\begin{enumerate}
\item la lista dei frame che la compone.
    Ogni elemento della lista \`e una tripletta del tipo:
    coordinata $x$, coordinata $y$, immagine
    (quest'ultima pu\`o essere un semplice numero che
    indichi un'immagine dell'insieme descritto al punto 2).
\item L'insieme delle immagini grafiche utilizzate dall'animazione.
\item La sua frequenza.
\end{enumerate}

In principio quindi, potremmo descrivere un'animazione con due semplici
file e marcare nella AnimList a quale locazione appartiene. Cos\`{\i}, ogni
volta che l'eroe entra in una locazione, il programma pu\`o sapere quali
file di definizione sono necessari, caricarli, riempire una lista delle
animazioni correnti (che potr\`a poi essere sfruttata dal ciclo principale
del programma) e passare alla loro visualizzazione (durante il ciclo
principale).

Limitarsi a questo per\`o significa ridurre di molto la versatilit\`a delle
animazioni che ammiriamo in un'avventura moderna; sarebbe quindi molto
utile inserire un header davanti alla lista dei frame che determini alcuni
parametri utili per aumentare la classe di situazioni rappresentabili.
Alcune idee per questi parametri sono:

\begin{itemize}
\item flag di modo: indica se la lista di frame va eseguita solo in una
      direzione, o se se s'intende successivamente eseguirla anche al
      contrario (cosiddetto effetto ping-pong).
\item numero di ripetizioni: indica quante volte consecutivamente deve
      essere ripetuta la lista di frame (con la possibilit\`a di entrare in
      loop infinito, per esempio indicando $-1$).

\item ritardo di ripetizione: indica la quantit\`a di cicli da attendere
      prima di una ripetizione (\`e possibile indicare che si vuole
      ottenere un numero casuale ogni volta diverso compreso tra due
      limiti).

\item offset: indica che tutte le coordinate della lista di frame sono
      relative al punto indicato (anche qui \`e interessante la
      possibilit\`a di indicare delle coordinate casuali).

\item ritardo d'attivazione: indica la quantit\`a di cicli da attendere prima di
      iniziare l'animazione dal momento in cui \`e stata attivata (evento
      in reazione ad un oggetto, dialogo, eccetera).
\end{itemize}

Permettendo combinazioni di questi parametri \`e possibile ottenere
animazioni molto interessanti senza doverle direttamente implementare nel
codice, il che chiaramente aumenta la potenza del programma che si sta
sviluppando.

Un buon esempio \`e quello di un cielo stellato animato: potremmo definire
una stella come l'animazione di un flare che, dal nulla, sfuma verso il
massimo della luminosit\`a, compilare la lista dei suoi frame, indicare che
si vuole un effetto ping-pong, che si vogliono infinite ripetizioni tutte
separate da un ritardo casuale compreso tra 4 e 40 secondi e che si vuole
che appaia in un punto a caso della parte superiore dello schermo (grazie
al parametro offset). Attivando venti di queste animazioni si ottiene
qualcosa di molto vicino all'idea cercata.

L'implementazione di queste opzioni non presenta grandi difficolt\`a dato
che \`e sufficiente scrivere un sistema di caricamento che tenga conto dei
vari parametri e intervenire a livello del ciclo principale del programma
inserendo variabili e controlli che operino le varianti al momento di
iniziare ogni singola sequenza d'animazione.

Ricordiamo che la presenza di un'animazione in una locazione e il momento
dell'inizio della sua visualizzazione dipendono spesso dalle reazioni alle
operazioni sugli oggetti e dalle frasi dette in un dialogo (p. es.: tiro una
leva, e vedo una botola che si apre); per questo \`e necessario
implementare un evento di reazione che modifichi la animlist e un altro che
provochi l'attivazione di un'animazione (come accennato nel paragrafo {\em
Oggetti}).


\paragrafo{Zoom}

Un metodo decisamente spettacolare per dare profondit\`a alle locazioni \`e
quello dello zoom del personaggio principale.

Purtroppo per\`o lo zoom di molte immagini bitmap (di cui \`e composta
l'animazione del personaggio) \`e un'operazione molto costosa in termini di
calcolo. Per questo, a seconda delle condizioni della macchina in cui gira
il gioco \`e necessario prendere una decisione:

\begin{enumerate}
\item Calcolare ad ogni ciclo l'immagine del personaggio necessaria. A
prescindere dall'algoritmo utilizzato questo metodo si rivela in molti casi
decisamente lento, specialmente se vi sono parecchie animazioni di sfondo,
su macchine piccole e se si vuole una velocit\`a di gioco intorno ai 50 fps
(purtroppo queste tre condizioni hanno la pessima abitudine di presentarsi
contemporaneamente molto spesso).

\item Tenere in memoria tutti i frame d'animazione, in tutti i gradi di
zoom possibili nella locazione corrente. Sebbene sembri un sacrificio di
memoria inaccettabile, questo metodo \`e a volte da considerare su macchine
dotate di un processore relativamente lento, ma con memoria
sufficientemente grande (l'A1200 inespanso \`e l'esempio perfetto).

\item Tenere in memoria solo un certo numero di gradi di zoom senza
appesantire troppo il sistema (quindi lasciando spazio agli altri elementi
dell'avventura) e calcolare i mancanti soltanto quando necessario. I
calcoli devono essere svolti durante i ``tempi morti'', quando il
processore \`e poco caricato. Eventualmente questi tempi morti possono
essere appositamente inseriti nell'avventura come una semplice demo
contente un dialogo). Questa soluzione \`e probabilmente la migliore,
soprattutto perch\'e tiene conto delle risorse della macchina su cui gira
l'avventura.
\end{enumerate}

un altro problema che si pone al momento dell'implementazione dello zoom in
una locazione \`e quello di determinare con precisione quale grado di zoom
deve assumere il personaggio in ogni punto dell'area percorribile, nonch\'e
di trovare un metodo per passare questa informazione al gioco il pi\`u
velocemente possibile.

Se vogliamo implementare una totale libert\`a di zoom, in cui in ogni punto
dell'area percorribile il personaggio pu\`o assumere qualsiasi grado di
zoom, allora occorrer\`a rendere disponibile una grande quantit\`a di
informazioni dato che a ogni punto della bitmap dovr\`a corrispondere un
numero indicante il grado di zoom.

Nel caso si scelga di dare al programma tutta questa libert\`a pu\`o essere
una buona idea tenere in memoria una bitmap identica a quella dell'area
percorribile, ma tracciata in modo che il colore di ogni punto indichi
direttamente il grado di zoom. Una tale bitmap avr\`a dunque una
profondit\`a pari a: $log_2$TotGrades, dove TotGrades \`e il numero totale
di gradi di zoom possibili nell'avventura (spesso 32 sono pi\`u che
sufficienti). In ogni ciclo del programma il ``calcolo'' del grado di zoom
del personaggio si riduce quindi a leggere il colore del punto in cui esso
si trova sulla bitmap citata.

I vantaggi di una tale rappresentazione sono fondamentalmente la
grandissima velocit\`a di recupero dell'informazione e la comodit\`a di
poter editare lo zoom di una locazione letteralmente disegnando la sua
distribuzione sull'area percorribile. Naturalmente occupa una quantit\`a
relativamente alta di memoria, ma non ce la potremmo comunque cavare con
molto meno: siccome vogliamo che ogni punto possa avere un grado di zoom
completamente differente da ogni altro \`e obbligatorio in ogni caso
memorizzare un numero per ogni punto dell'area percorribile.

Se invece riduciamo un po' la libert\`a della distribuzione dello zoom ci
accorgiamo subito che \`e possibile ottenere grossi risparmi sulla
quantit\`a di informazioni da memorizzare. Infatti potremmo scegliere tra
tanti metodi per calcolare matematicamente il grado di zoom del punto in
cui giace l'eroe, a seconda di come vogliamo limitare la distribuzione.

Una prima idea (spesso molto utile) \`e quella delle linee di zoom:
immaginiamo di avere una scala davanti al nostro eroe, e che egli, salendo,
debba progressivamente diminuire di dimensione passando dal decimo grado di
zoom (base della scala) al ventiseiesimo (in cima alla scala). Per sapere
quale grado il personaggio deve assumere in ogni punto della scala basta
memorizzare il grado di zoom di due punti (uno posto sulla linea di fondo e
uno sulla linea in cima); il programma, una volta informato dell'esistenza
di una linea di zoom tra i due punti, potr\`a facilmente calcolare il grado
di zoom di ogni punto intermedio grazie alla formula seguente:

  $$
    \frac{Z\ped{max}-Z\ped{min}}{Y\ped{max}-Y\ped{min}}
    (Y\ped{act}-Y\ped{min}) + Z\ped{min}
  $$

  Dove $Z\ped{max}$ e $Z\ped{min}$ sono rispettivamente il grado massimo e minimo di zoom.
  $Y\ped{max}$ e $Y\ped{min}$ sono le altezza massime e minime e $Y\ped{act}$
  \`e l'altezza del punto in cui si vuole calcolare lo zoom.

Questo metodo occupa pochissima memoria ed \`e piuttosto veloce (sebbene
non tanto quanto il precedente) ma limita molto la libert\`a della
distribuzione dello zoom, imponendo delle variazioni in linea retta.

un altro modo per regolare matematicamente la distribuzione dello zoom \`e
quello degli offset. Questo si basa sulla restrizione che tra due punti
adiacenti il grado di zoom non pu\`o cambiare pi\`u di una unit\`a (o
comunque non pi\`u di un limite prefissato). Occorrerebbe quindi
memorizzare per ogni colonna di punti che forma l'area percorribile le
seguenti informazioni: il valore del grado di zoom del suo primo punto, un
valore intero compreso tra $-1$ e 1 indicante la differenza tra il grado di
zoom nel secondo punto e quello del primo, un altro valore tra $-1$ e 1,
questa volta riguardante la differenza tra il terzo punto e il secondo e
cos\`{\i} via, fino alla fine della colonna.

Nel ciclo principale del gioco, per calcolare il grado di zoom del
personaggio in un punto occorrerebbe sommare tra di loro tutti i valori
memorizzati per quella colonna, dal primo (che contiene un valore reale),
fino all'altezza del punto in questione (gli altri valori sommati sono
degli offset).

Questo metodo ha il vantaggio di riuscire a gestire moltissime situazioni
reali, avvicinadosi molto alla libert\`a offerta dal primo; esso inoltre
richiede calcoli semplici che non rallentano troppo il ciclo principale e
permette un risparmio notevole di memoria (sebbene un metodo puramente
matematico occuperebbe decisamente ancora meno; questo metodo richiede
tuttora l'equivalente di una bitmap a 2 bitplane delle stesse dimensioni
dell'area percorribile). Gli unici nei stanno nella scomodit\`a di
editamento (problema che pu\`o essere risolto comunque con un programma di
conversione) e nel fatto che si tratta di un compromesso tra tutto e quindi
toglie un po' di libert\`a, un po' di velocit\`a e un po' di memoria,
invece che affliggere soltanto uno dei tre campi in modo pi\`u grave (come
facevano i due metodi proposti in precedenza).

Qualsiasi sia il metodo scelto per ottenere il grado di zoom del punto in
cui si trova l'eroe occorre ora stabilire quale immagine grafica in memoria
occorra disegnare. Una proposta \`e quella di organizzare i frame in
pacchetti caratterizzati dallo stesso grado di zoom (ogni pacchetto
contiene quindi i frame per tutte le direzioni di marcia) e quindi definire
una lookup table che associ ad ogni grado l'indirizzo di memoria dove \`e
custodito il relativo pacchetto. Il programma potr\`a quindi dirigersi
verso la giusta zona di memoria, leggendo la lookup table alla posizione
corrispondente al grado di zoom, e arrivare alla giusta immagine grazie
all'aggiunta di un offset (costante per tutti i gradi) che indichi quale
frame del pacchetto prendere (p. es.: l'offset per il primo frame dell'eroe
che marcia a destra \`e sempre 1873, per\`o aggiungendo un indirizzo di
base differente, custodito nella lookup table, se ne prender\`a sempre un
grado diverso).


\paragrafo{Memoria e velocit\`a}

L'ultimo tema che affrontiamo \`e decisamente il pi\`u importante, almeno
sul piano dell'implementazione tecnica.

Come abbiamo visto pi\`u volte in quest'articolo la programmazione di
un'avventura grafica (nonch\`e di quasi tutti i videogiochi) permette al
programmatore la possibilit\`a di scegliere tra pi\`u differenti soluzioni
a un singolo problema, che spesso si differenziano per la velocit\`a e la
memoria richiesta.

Sebbene raramente si trovino dei ``metodi miracolo'' che riescono ad
abbinare un breve tempo d'esecuzione ad una modesta richiesta di spazio in
memoria, nella maggior parte dei casi ogni soluzione ha dei pregi in un
campo e dei difetti nell'altro.

Questo obbliga quindi il programmatore a compiere delle scelte: tra metodi
che sfruttino in modi diversi il blitter, tra cosa tenere in memoria e cosa
calcolare, tra quando caricare nuovi dati, tra cosa eliminare dalla memoria
e quando.

Molto spesso non ci sono decisioni in assoluto migliori di altre ma solo
decisioni pi\`u adatte ad un certi obiettivi e ad una certa macchina: il
programmatore deve scegliere soprattutto in funzione di cosa vuole ottenere
dal gioco (p. es.: se vuole un gioco a 50 fps pieno di animazioni ovunque
dovr\`a spesso acconsentire a soluzioni che sacrificano memoria in favore
della velocit\`a) e dal sistema minimo sul quale esso deve girare (p. es.:
se l'obiettivo \`e che il gioco giri su un 68040/40\unit{Mhz}, AGA, con solo 1 MB
di CHIP RAM dovr\`a spesso implementare metodi che favoriscano intensi
calcoli, sfruttamento del blitter e caricamenti, piuttosto che altri
necessitanti grosse quantit\`a di memoria).

Trovare il giusto bilancio tra memoria occupata e velocit\`a d'esecuzione
\`e forse il compito pi\`u delicato, e decisivo allo tempo stesso, di un
programmatore di avventure grafiche, e pu\`o decretare inesorabilmente il
successo o il fallimento dei suoi progetti.

Dalla mia personale esperienza posso dire che nello sviluppo di avventure
grafiche i problemi di velocit\`a esistono piuttosto a livello di
sfruttamento del blitter che a quello di calcolo (che invece riguardano
maggiormente giochi d'azione tridimensionale). Se si considera un'avventura
a 50 fps con grandi personaggi e molte animazioni di sfondo (sar\`a la base
del discorso seguente) ci si rende conto che quasi tutto il ciclo
principale del programma \`e occupato a controllare il blitter nelle
molteplici operazioni di ripristino sfondi e tracciamento di frame,
lasciando cos\`{\i} comunque poco tempo per le operazioni di calcolo,
seppur poco complesse.

Se a questo si abbina la volont\`a di far girare il gioco su un A1200 di
base, che \`e una macchina dotata di molta memoria in proporzione al
processore e al blitter di cui \`e fornita, ci si accorge come spesso sia
decisamente conveniente optare per soluzioni rapide a sfavore di una
modesta richiesta di memoria. Un tale orientamento permette agevolmente
anche all'A1200 base di far girare avventure dalle caratteristiche citate
(50 fps, grandi personaggi, molte animazioni, zoom, eccetera).

Su macchine pi\`u veloci la situazione migliora sensibilmente, specie se vi
\`e anche della FAST RAM presente. Purtroppo il blitter rimane comunque
sempre lo stesso, riducendo le chance di grandi miglioramenti. Una macchina
del genere permette comunque al programmatore di modificare l'orientamento
della struttura del programma rispetto all'A1200.

Una vera e propria rivoluzione del bilancio memoria-velocit\`a sarebbe
procurato dalla presenza di un nuovo blitter pi\`u veloce, che libererebbe
gran parte del ciclo principale lasciando spazio a operazioni che
permetterebbero di risparmiare molta memoria (un esempio tipico \`e lo zoom
del personaggio, molto difficile da calcolare in tempo reale su un A1200,
visto che rimarrebbe troppo poco tempo per occuparsi degli altri elementi
dell'avventura, tra cui appunto il controllo del blitter).

Un ultima riflessione la merita il cambio di locazione: un'avventura che
struttura i dati del gioco in modo che esso possa girare tenendo in memoria
esattamente solo tutti i dati (compresa la grafica) necessari a una singola
locazione, permette di entrare nel ciclo seguente:

\begin{enumerate}
\item Caricamento di tutti i dati necessari alla locazione. In questo
      momento in memoria si trovano solo questi dati.

\item Fase di gioco. Nessun caricamento fino alla fine della locazione,
      eventuale eliminazione di dati dalla memoria (p. es. dopo una demo
      destinata a essere visualizzata soltanto una volta). Questa fase
      termina quando il personaggio esce dalla locazione.

\item Eliminazione di tutti i dati riguardanti la locazione da cui si \`e
      appena usciti. Ritorno al punto 1.
\end{enumerate}

Un gioco strutturato in questo modo, ha due vantaggi fondamentali:
un'ottimizzazione dell'occupazione della memoria e la concentrazione dei
caricamenti in una sola fase, con conseguente accelerazione e
minimizzamento dei tempi morti e quindi un aumento della giocabilit\`a.

Sebbene quest'idea appaia semplice, perfino banale, per poter essere
implementata necessit\`a di un'attenta organizzazione dei dati nei datafile
e in memoria; questo tema seppur poco appariscente, gioca un ruolo
determinante e non deve essere sottovalutato.


\paragrafo{Vega: presente e futuro}

Come accennato all'inizio, tutte le idee contenute in quest'articolo sono
basate sull'esperienza di molti mesi di programmazione di Vega: un sistema
di sviluppo e conversione di avventure grafiche.

Ispirato e basato sul mitico SCUMM, Vega rappresenta il nostro approccio al
problema e si pone l'obiettivo di portare avventure grafiche di qualit\`a
su \A ; il sistema si compone di molti editor e compilatori indipendenti
che generano vari tipi di datafile, nonch\'e di un unico player, che genera
il gioco sulla base delle informazioni contenute nei file citati.

Vega \`e un pacchetto destinato all'uso interno, per produrre nuove
avventure grafiche e conversioni da altre piattaforme sia a livello
shareware che commerciale (come \`e il caso di Sixth Sense Investigations,
di prossima publicazione da Epic).

Come esempio dei risultati raggiunti posso dire che le caratteristiche
principali di Vega, al momento della scrittura di questo articolo, sono:

\begin{itemize}
\item{scrolling, personaggio principale e puntatore del mouse a 50 frames/secondo;}
\item{animazioni di sfondo a frequenza variabile da 1 a 50 fps, con tutte le opzioni
   descritte nel paragrafo {\em Animazioni};}
\item{area di gioco a 256 colori e dimensione variabile;}
\item{zoom del personaggio principale e di un personaggio secondario;}
\item{ricerca automatica e precisa del percorso tra due punti; questa impiega
   mediamente 1/60 di secondo;}
\item{risposta praticamente immediata del mouse;}
\item{backobject a livelli di profondit\`a virtualmente illimitati (dipendenti dalla memoria del computer);}
\item{gestione di oggetti e dialoghi complessi, con interazioni reciproche.
   Entrambi possono avere influenze sull'inventario, le animazioni e gli altri personaggi, anche a distanza
   di tempo e/o di locazioni;}
\item{interfaccia utente in stile LucasArts; essa \`e completamente configurabile.}
\end{itemize}

Sebbene sia gi\`a in grado di poter generare un'intera avventura, il
sistema \`e ben lontano dall'essere perfetto, dallo sfruttare completamente
\A e ancor di pi\`u dal potersi considerare potente al punto da inglobare
tutti gli aspetti delle avventure grafiche moderne, specialmente di quelle
per PC. Siamo quindi molto aperti alle migliorie; tra le pi\`u
interessanti, che sarebbe bello implementare, vi sono:

\begin{itemize}
\item un sistema sonoro che si adatti alla situazione attuale di gioco (simile
      quindi all'iMUSE della LucasArts);
\item un sistema di demo d'intermezzo che permetta di sfruttare tutte le caratteristiche
      degli oggetti, delle animazioni e dei dialoghi;
\item Personaggi intelligenti: per esempio personaggi che seguono il giocatore, o altri che cambiano
      comportamento in funzione delle sue azioni;
\item Animazioni e personaggi tridimensionali, sul modello ``Alone in the Dark''.
\end{itemize}

Una demo di un gioco fatto con Vega (``Sixth Sense Investigation'') si trova
sul CD di IPISA.

\paragrafo{Conclusioni}

Le avventure grafiche sono al momento tra i giochi pi\`u ricercati, eppure
su \A ne escono veramente poche, senza contare che spesso la loro qualit\`a
non rende giustizia alla nostra amata macchina, in particolare dal punto di
vista tecnico: basti pensare alle conversioni di molti famosi titoli per
PC.

Eppure titoli come ``Hook'' (e spero anche Vega) hanno dimostrato che \`e
pi\`u che possibile realizzare questo tipo di giochi con successo, anche su
macchine relativamente poco potenti (A1200/A500).

In effetti il problema non risiede tanto nell'implementazione tecnica,
relativamente semplice, specie se comparata allo sviluppo di altri giochi
moderni come gli spara e fuggi tridimensionali, quanto nella quantit\`a di
persone diverse necessarie per lo sviluppo di un'avventura, che, come
sappiamo, d\`a una grande importanza alla storia e necessit\`a di grosse
quantit\`a di grafica (questo genera anche problemi di coordinamento).

Una facile soluzione al problema appare quella di limitarsi a convertire i
giochi da altre piattaforme, ma purtroppo sembra che la grande maggioranza
delle software house operanti nel settore siano poco fiduciose nella
validit\`a del mercato \A e rinuncino quindi all'operazione.

Sulla base della certezza che un'avventura grafica su \A oggi ha grandi
possibilit\`a di successo, occorre quindi armarsi della dote fondamentale
di uno sviluppatore \A (la volont\`a), appoggiarsi alla certezza della
possibilit\`a tecnica di realizzare avventure di qualit\`a, e cominciare a
produrne di nuove, con la convinzione che questo possa dare un nuovo
impulso al settore.

Spero che il mio modesto contributo personale alle avventure grafiche su \A,
rappresentato principalmente dal presente articolo e dal videogioco {\em
Sixth Sense Investigations}, possa aiutare a stimolare quanti pi\`u
sviluppatori possibile a dedicare la loro attenzione a questo tema, a noi
molto caro.

Tengo particolarmente a ringraziare Claudio Preian\`o, fondatore del gruppo
CineTech, per il suo enorme contributo allo sviluppo di Vega, costituito da
moltissime idee (alcune delle quali illustrate in questo articolo), buona
pianificazione e soprattutto molta motivazione.


\autore{Vittorio Ferrari\\
Chemin de Montrevers 9\\
1700 Friborgo\\
Svizzera\\[1ex]
Tel. 0041/26/322.26.50\\[1ex]
e-mail: \c{vega@tilink.ch}\\
\phantom{e-mail:} \c{Vittorio.Ferrari@tilink.ch}}{}

\citazione{\selectlanguage{english}At times one remains faithful to a cause only\\
because its opponents do not cease to be insipid.}{Friedrich Nietzsche}
