di  -  martedì 5 ottobre 2010

Introduzione
Per cominciare analizziamo le tre funzioni più semplici che compongono il programma. Vi ricordo solo che agg_rect() è stata creata per sopperire al mancato funzionamento di Rect.move_ip() con i numeri float.

Quando si crea un videogioco, ma non solo, dovete scegliere bene quali sono le routine che possono essere automatizzate e come, creando quindi delle funzioni ad hoc. Molto spesso le soluzioni che si trovano possono essere scritte in diverso modo e anche se il risultato è lo stesso, ne potete guadagnare sotto vari aspetti: come rapidità, facilità di lettura o adattamento a situazioni diverse ecc… Detto questo, passiamo al codice.

Codice

def carica_imm_sprite(nome,h,w,num):
    immagini = []
    if num is None or num == 1:
        imm1 =  pygame.image.load(nome+".png").convert_alpha()
        imm1_w, imm1_h = imm1.get_size()

        for y in range(int(imm1_h/h)):
            for x in range(int(imm1_w/w)):
                immagini.append(imm1.subsurface((x*w,y*h,w,h)))

         return immagini
     else:
         for x in range(1,num):
             imm1 = pygame.image.load(nome+str(x)+".png").convert_alpha()
             immagini.append(imm1)
         return immagini

def agg_rect(sprite):
    x,y = sprite.coordinate
    s,t,o,p = sprite.rect
    sprite.rect.move_ip(int(x)-s,int(y)-t)

def tps(orologio, fps):
    temp = orologio.tick(fps)
    tps = temp /1000.
    return tps

Per utilizzare la vostra sprite animata dovete caricare prima di tutto i frame dell’animazione.  Come potete notare la funzione carica_imm_sprite() necessita di quattro parametri:

  1. Il nome del file, senza estensione ‘.png’.
  2. L’altezza della nostra sprite, non quella del file immagine.
  3. La larghezza della sprite.
  4. Numero di immagini che formano la sprite (per numero di immagini si intende quanti file immagini ci sono per questa sprite e naturalmente ogni file immagine si presume sia un frame).

L’obbiettivo è quello di passare una lista di immagini che rappresenti i frame di animazione della nostra sprite, quindi per prima cosa creiamo l’oggetto che ritorneremo al chiamante (piccola nota: in questo caso il passaggio avviene per valore, visto che non si può passare per riferimento in python), come possiamo vedere dalla riga 2.

La condizione successiva serve per individuare un caricamento da più file o da uno soltanto. Se il file è singolo, allora carichiamo l’immagine e la convertiamo senza perdere il canale alpha, la memorizziamo in imm1 e poi salviamo anche le sue grandezze (imm1_w, imm1_h). Con i due for alle righe 7 e 8, dividiamo l’immagine in frame, come se fosse una matrice: quindi il primo frame sarà quello ritagliato dall’angolo in alto a sinistra dell’immagine, mentre l’ultimo quello ritagliato dall’angolo in basso a destra.

Come potete vedere, divindendo la lunghezza dell’immagine per la larghezza del frame, abbiamo il numero di frame su una linea. Quindi per avere le coordinate dell’inizio del frame basta moltiplicare le x (cioè il frame che stiamo memorizzando) per la lunghezza ‘w’. Facciamo la stessa cosa per il numero delle colonne. Con questi dati, ritagliamo dall’immagine originale una sottosuperfice, che creerà l’immagine vera e proria del frame. Memorizziamo il tutto nella lista di immagini creata all’inizio con il seguente comando:

immagini.append(imm1.subsurface((x*w,y*h,w,h)))

Da notare che subsurface ha bisogno di quattro elementi (perché ha bisogno di un oggetto Rect e si può creare anche in questo modo): i primi due indicano le coordinate iniziali dell’immagine da ritagliare (ricordate che si parte dall’angolo in alto a sinistra), mentre le ultime due sono la larghezza e l’altezza del frame da “tagliare”.

Naturalmente se abbiamo dei file separati la cosa è molto più semplice e di questo se ne occupano le righe che vanno dalla 12 alla 16. Si presume che i file abbiamo lo stesso nome e differiscano per un numero alla fine.

La funzione termia ritornando la lista di immagini, che possiamo prontamente riutilizzare nelle nostre sprites e vedremo più avanti come. Vi premetto che più frame caricate, più l’animazione sarà fluida e dettagliata.

Passiamo ora alla funzione agg_rect(), che prende come argomento una sprite. Il compito principale di questa parte di codice è quello di aggiornare le coordinate rect (coordinate necessarie per le collisioni) di una qualsiasi sprite. L’aggiornamento viene effettuato tramite la funzione move_ip(x,y), che fa parte di uno dei metodi di rect.

Quest’ultima funzione prende come argomenti gli spostamenti x e y da effettuare dalla posizione originale, non le nuove coordinate dove ridisegnare rect, il nostro rettangolo delle collisioni. Per questo memorizzo le coordinate attuali della sprite (che sono aggiornate in base alla posizione sullo schermo) e quelle di rect, converto poi i valori in interi (perché non posso lavorare con i float, per il bug citato nell’articolo precendente) e sottraggo le coordinate di rect (che sono temporalmente più vecchie). Così rect riceverà il giusto spostamento per restare incollato alla nostra sprite, in modo abbastanza realistico e non necessitiamo di ridisegnare un nuovo rect ogni volta che spostiamo il nostro personaggio.

Non era strettamente necessario utilizzare una funzione esterna, ma non potendo utilizzare direttamente le coordinate dell’immagine ho preferito crearmene una che può essere utilizzata anche in altre occasioni, visto che necessita solo il passaggio di una sprite che ha memorizzate le sue coordinate e rect.

Per concludere l’ultima funzione gestisce il tempo di gioco, nel senso che restituisce il tempo trascorso in secondi, in base all’orogolio che abbiamo creato per il programma e agli fps da raggiungere (tramite la funzione tick di un oggetto pygame.time.Clock). Come vedete, niente di complicato, ma di fondamentale importanza per sincronizzare spostamenti e animazioni.

Conclusioni

Spero di non aver creato dubbi, ma di aver contribuito, anche se in misera parte, ad uno dei vostri progetti. Infatti l’utilizzo di queste funzioni è di fondamentale importanza e sono le basi per l’utilizzo delle sprite (o almeno, per il caricamento dei loro frame). Successivamente analizzeremo le due classi, per poi passare al corpo del gioco. Alla fine di tutto questo avremo qualcosa di veramente consistente, che ci sonsentirà di sperimentare e affinare le nostre idee e il nostro videogioco.

10 Commenti »

I commenti inseriti dai lettori di AppuntiDigitali non sono oggetto di moderazione preventiva, ma solo di eventuale filtro antispam. Qualora si ravvisi un contenuto non consono (offensivo o diffamatorio) si prega di contattare l'amministrazione di Appunti Digitali all'indirizzo info@appuntidigitali.it, specificando quale sia il commento in oggetto.

  • # 1
    Cesare Di Mauro
     scrive: 

    Qualche suggerimento per rendere il codice più “pythonico” e/o efficiente.

    Usare is None anziché == None.
    Al posto di range è meglio la funzione xrange.
    Sfruttare le list comprehension per costruire al volo le liste.
    L’interpolazione (%) quando devi concatenare in maniera “variegata” più di 2 stringhe.

    In questo modo si cominciano a vedere anche alcune interessanti (e utili) caratteristiche di questo splendido linguaggio. ;)

  • # 2
    Mirco Tracolli
     scrive: 

    Cercherò di mettere in pratica subito i tuoi suggerimenti, grazie mille :D Ti volevo solo chiedere una cosa: ho notato nella versione 3.1 di python che xrange non esiste più e sono costretto ad usare range (più scomodo infatti perché utilizza più memoria). Vorrei una tua conferma, per il resto cercherò di apporta le modifiche ai prossimi articoli.

  • # 3
    Cesare Di Mauro
     scrive: 

    Figurati, per così poco. ;)

    Dalla 3.0 in poi la vecchia “xrange” ha cambiato nome in “range”, e la vecchia “range” non esiste più (per ottenerla basta: “list(range(10))”, ad esempio).

  • # 4
    Emanuele Rampichini
     scrive: 

    @Cesare

    Usare is None anziché == None.

    Grazie della dritta… non avevo mai fatto caso al fatto che None fosse un oggetto singleton.
    Piccola domanda… non riesco a capire in quale caso la differenza semantica tra == e is in questo particolare caso possa portare problemi. Oppure si tratta solo una questione di migliore leggibilità? (le keyword ci piacciono più dei simboli :D ).
    Oppure nessuna delle due precedenti ed è una questione “prestazionale” visto che == va a guardare il contenuto degli oggetti mentre is fa solo il confronto tra l’id degli oggetti?

  • # 5
    Cesare Di Mauro
     scrive: 

    In questo caso il problema è soltanto di leggibilità, perché l’uguaglianza comporta la valutazione del primo argomento e il confronto con None, e l’operazione torna True se e solo se il primo è anch’esso None.

    C’è anche un discorso prestazionale, perché is confronta soltanto gli id (i puntatori, in sostanza; e l’operazione è immediata), mentre l’uguaglianza ne controlla il contenuto, appunto come dicevi tu.

    La differenza può sembrare di poco conto, ma in realtà è abbastanza marcata perché dipende tutto da come vengono istanziati gli oggetti.

    Esempio:

    >>> x = 1000
    >>> x is 1000
    False
    >>> x == 1000
    True

  • # 6
    Emanuele Rampichini
     scrive: 

    Perfetto. Come sospettavo.

  • # 7
    TheKaneB
     scrive: 

    … un elegante metodo per gestire istanze diverse dello stesso oggetto senza scomodare la semantica del puntatore. Ingegnoso, mi piace :-)

  • # 8
    Alessandro
     scrive: 

    scusate la domanda poco inerente… dato che non ho delle solide basi informatiche, non avendola mai studiata ne a scuola ne altrove, mi chiedevo dove posso trovare materiale valido per comprendere le basi della programmazione, dato che ogni tanto mi accorgo di avere gravi lacune soprattutto per quanto riguarda alcune istruzioni.

  • # 9
    Cesare Di Mauro
     scrive: 

    Siamo OT, comunque: http://www.python.it/doc/Howtothink/HowToThink_ITA.pdf

  • # 10
    Alessandro
     scrive: 

    grazie ;)

Scrivi un commento!

Aggiungi il commento, oppure trackback dal tuo sito.

I commenti inseriti dai lettori di AppuntiDigitali non sono oggetto di moderazione preventiva, ma solo di eventuale filtro antispam. Qualora si ravvisi un contenuto non consono (offensivo o diffamatorio) si prega di contattare l'amministrazione di Appunti Digitali all'indirizzo info@appuntidigitali.it, specificando quale sia il commento in oggetto.