lunedì 10 dicembre 2012

Programmare con Clutter (versione 1.12.2): Timeline e animazioni. Tutorial parte 2


Proseguiamo con la seconda parte del tutorial sulla programmazione con Clutter. Nella prima parte avevamo creato un programma che si limitava a disegnare un quadrato verde su sfondo nero, questa volta aggiungeremo un po di movimento!


Creiamo un'istanze della classe ClutterTimeline

Faremo in modo che il nostro “attore” ruoti sull'asse z (l'asse della profondità, quella retta passante per il vostro occhio che va dritta dentro lo schermo).

Per ottenere questa animazione utilizzeremo una nuova classe: ClutterTimeline.
Un timeline non è altro che un cronometro che chiama una funzione (generalmente chiamata call-back function) ogni n millisecondi.

Per prima cosa creiamo un timeline ed avviamo il cronometro, aggiungiamo queste righe prima di clutter_actor_show(stage);


ClutterTimeline *timeline_rotation = clutter_timeline_new(500);
g_signal_connect(timeline_rotation, "new-frame", G_CALLBACK(on_timeline_rotation_new_frame), rect);
clutter_timeline_set_repeat_count (timeline_rotation, -1);
clutter_timeline_start(timeline_rotation);

Prima di proseguire guardiamo insieme il codice che abbiamo aggiunto:


  • clutter_timeline_new() crea l'istanza della classe  ClutterTimeline, timeline_rotation è il suo puntatore. 500 è un valore in millisecondi, il nostro cronometro chiamerà la funzione di ritorno (call-back function) dopo mezzo secondo.
  • g_signal_connect() collega la funzione di ritorno al nostro cronometro, vediamo i parametri passati: timeline_rotation è il puntatore al nostro timeline, “new-frame” è una stringa che definisce il tipo di funzione di ritorno, in questo caso è una funzione che disegna il nuovo frame dell'animazione. G_CALLBACK(on_timeline_rotation_new_frame) è la nostra funzione di ritorno, in realtà non l'abbiamo ancora definita, ma lo faremo tra poco. L'ultimo parametro è un puntatore a qualsiasi tipo di dato che vorremmo passare alla nostra funzione di ritorno, in questo caso passiamo il puntatore al nostro attore, rect.
  • clutter_timeline_set_repeat_count () imposta le volte che il cronometro deve ripetersi, passando -1 si ripeterà all'infinito;
  • clutter_timeline_start() avvia il cronometro e quindi la nostra animazione.

All'uscita del programma ci dobbiamo ricordare di liberare le risorse allocate dalla nostra timeline, aggiungiamo questa riga prima di return error;


g_object_unref (timeline_rotation);


Definiamo la funzione di ritorno del nostro timeline

Se provassimo a compilare adesso riceveremmo un errore che ci segnala che la nostra funzione di ritorno  on_timeline_rotation_new_frame non è stata definita, quindi mettiamoci al lavoro.


L'idea è quella di incrementare di n gradi la rotazione del nostro attore ad ogni chiamata di ritorno del cronometro, per farlo avremo bisogno di una variabile globale che contenga il valore di rotazione applicata al nostro attore.

Aggiungiamo quindi subito dopo #include <clutter/clutter.h>


gdouble rotation = 0;

Partiamo quindi da una rotazione di 0 gradi. Aggiungiamo di seguito la definizione della funzione di ritorno:


void on_timeline_rotation_new_frame(ClutterTimeline *timeline, gint frame_num, ClutterActor* actor)
{
    rotation += 0.5;
    clutter_actor_set_rotation_angle(actor, CLUTTER_Z_AXIS, rotation);
}

Vediamo subito i parametri ricevuti dalla funzione:


  • timeline è il puntatore al cronometro che ha chiamato la funzione;
  • frame_num è il numero del frame. Noi non utilizzeremo questo valore, ma c'è perché è previsto nelle funzioni di ritorno di tipo “new-frame”;
  • actor è il nostro attore, questo parametro lo abbiamo aggiunto noi per ottenere il nostro scopo, cioè quello di applicare la rotazione.

Al suo interno la nostra funzione è piuttosto semplice, dapprima incrementa di 0.5 il valore di rotazione, quindi applica la rotazione con la funzione clutter_actor_set_rotation_angle(), alla quale passiamo il puntatore all'attore, l'asse sul quale viene applicata la rotazione e la variabile con il valore della rotazione.

Adesso possiamo compilare ed avviare il nostro programma per vedere quel bel quadrato verde ruotare sull'asse Z.


Avrete sicuramente notato che la rotazione avviene sul vertice in alto a sinistra del quadrato e non al centro. Questo avviene perché il vertice in alto a sinistra è il punto di riferimento di default per tutte le trasformazioni applicate all'attore.

Aggiungiamo un'istruzione che modifica il punto di riferimento del nostro attore subito prima di aggiungerlo allo stage:


clutter_actor_set_pivot_point (rect, 0.5, 0.5);

I due valori 0.5 sono riferiti rispettivamente all'asse X e a quello Y. Per quanto riguarda l'asse X, 0 corrisponde al vertice sinistro (default) e 1 è quello destro, 0.5 indica quindi al centro. Sull'asse Y, 0 è il vertice in alto (default), 1 è quello in basso, 0.5 è il centro.


Compiliamo ed avviamo il programma, il nostro quadrato verde adesso ruota correttamente.


Il programma completo fin qui dovrebbe avere questo aspetto:


#include <stdio.h>
#include <stdlib.h>
#include <clutter/clutter.h>
gdouble rotation = 0;
void on_timeline_rotation_new_frame(ClutterTimeline *timeline, gint frame_num, ClutterActor* actor)
{
    rotation += 0.5;
    clutter_actor_set_rotation_angle(actor, CLUTTER_Z_AXIS, rotation);
}
int main(int argc, char *argv[])
{
    ClutterInitError error = clutter_init(&argc, &argv);
    ClutterColor stage_color = { 0, 0, 0, 255 };
    ClutterActor *stage = clutter_stage_new(); clutter_actor_set_size(stage, 512, 512);
    clutter_actor_set_background_color(stage, &stage_color);
    ClutterColor actor_color = { 0, 255, 0, 128 };
    ClutterActor *rect = clutter_actor_new();
    clutter_actor_set_background_color(rect, &actor_color);
    clutter_actor_set_size(rect, 100, 100);
    clutter_actor_set_position(rect, 100, 100);
    clutter_actor_set_pivot_point (rect, 0.5, 0.5);
    clutter_actor_add_child(stage, rect);
    ClutterTimeline *timeline_rotation = clutter_timeline_new(500);
    g_signal_connect(timeline_rotation, "new-frame", G_CALLBACK(on_timeline_rotation_new_frame), rect);
    clutter_timeline_set_repeat_count (timeline_rotation, -1);
    clutter_timeline_start(timeline_rotation);
    clutter_actor_show(stage);
    g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
    clutter_main();
    g_object_unref(timeline_rotation);
    return error;
}

Per poter introdurre l'utilizzo della classe ClutterTimeline abbiamo utilizzato una funzione di ritorno per creare l'animazione del nostro attore, ma in realtà, per ottenere questo tipo di animazioni, possiamo utilizzare le proprietà animabili della classe ClutterActor o la classe ClutterTransition. Non allarmatevi, non stiamo complicando le cose ma vedremo come può essere facile creare delle animazioni anche complesse con poche righe di codice.

Ci sono sostanzialmente due modi per definire l'animazione di un attore: utilizzare l'animazione implicita della classe ClutterActor o definire un'animazione in modo esplicito tramite la classe ClutterTransition. Iniziamo con il primo modo.



Animazione implicita della classe ClutterActor

Per sperimentare le funzioni di animazione integrate nella classe ClutterActor dobbiamo smantellare tutto quello che abbiamo fatto finora, cioè possiamo eliminare il timeline e la funzione di ritorno. Eliminiamo anche la variabile globale rotation e aggiungiamo queste righe di codice subito dopo clutter_actor_add_child(stage, rect);


clutter_actor_save_easing_state (rect);
clutter_actor_set_easing_delay (rect, 500);
clutter_actor_set_easing_duration (rect, 500);
clutter_actor_set_rotation_angle (rect, CLUTTER_Z_AXIS, 360.0);
clutter_actor_restore_easing_state (rect);

Provate a compilare ed eseguire il programma, vedrete il quadrato ruotare e quindi fermarsi definitivamente. Adesso guardiamo il codice che abbiamo aggiunto:


  • clutter_actor_save_easing_state() è la prima funzione che va chiamata per definire l'animazione implicita dell'attore, salva il così detto easing state;
  • clutter_actor_set_easing_delay() imposta un ritardo espresso in millisecondi prima di iniziare l'animazione, nel nostro caso è di mezzo secondo;
  • clutter_actor_set_easing_duration() imposta la durata dell'animazione, cioè quanti millisecondi impiegherà il nostro attore per portare a termine l'animazione che gli abbiamo assegnato. Ancora una volta abbiamo immesso un valore corrispondente a mezzo secondo;
  • clutter_actor_set_rotation_angle(), questa funzione ruota l'attore rect sull'asse Z di 360 gradi, cioè un giro completo;
  • clutter_actor_restore_easing_state() è la funzione che chiude la definizione dell'animazione implicita e l'avvia.

Ricapitolando abbiamo detto al nostro attore di aspettare mezzo secondo prima di iniziare a muoversi, quindi di ruotare di 360 gradi sull'asse Z e di farlo con la velocità necessaria per impiegare mezzo secondo per concludere la rotazione.

I parametri animabili di un attore sono molteplici, ad esempio le sue coordinate, la rotazione sugli assi, la trasparenza, ecc... L'animazione implicita è molto semplice da utilizzare e conveniente per animazioni semplici, ma non ci permette di definire animazioni che si ripetono all'infinito.

Per ottenere animazioni più complesse o infinite come quella che avevamo ottenuto inizialmente con l'utilizzo di ClutterTimeline è necessario utilizzare l'animazione esplicita.


Animazione esplicita con la classe ClutterTransition

Ancora una volta dobbiamo fare un passo indietro, rimuovete il codice che definiva l'animazione implicita che avevamo aggiunto precedentemente e sostituitelo con questo:



ClutterTransition *transition = clutter_property_transition_new ("rotation-angle-z");
clutter_transition_set_from (transition, G_TYPE_DOUBLE, 0.0);
clutter_transition_set_to (transition, G_TYPE_DOUBLE, 360.0);
clutter_timeline_set_duration (CLUTTER_TIMELINE (transition), 500);
clutter_timeline_set_repeat_count (CLUTTER_TIMELINE (transition), -1);
clutter_actor_add_transition (rect, "centrifuga", transition);

Compilate ed eseguite il programma, il nostro quadrato ruota velocemente senza fermarsi! Vediamo che cosa abbiamo fatto nel dettaglio:

  • clutter_property_transition_new() crea un'istanza della classe ClutterTransition, il parametro “rotation_angle_z” definisce il tipo di animazione come rotazione sull'asse Z;
  • clutter_transition_set_from() imposta il valore di partenza dell'animazione, nel nostro caso la rotazione usa valori di tipo double ed il valore di partenza è 0;
  • clutter_transition_set_to() imposta invece il valore finale, 360 gradi è un giro completo;
  • clutter_timeline_set_duration() imposta la durata dell'animazione, intesa come velocità d'esecuzione. Notare che questa funzione fa parte della classe ClutterTimeline, ma noi gli passiamo il puntatore della nostra istanza di ClutterTransition utilizzando la macro per il cast CLUTTER_TIMELINE(). In poche parole gli diciamo di prendere il puntatore come fosse di tipo ClutterTimeline;
  • clutter_timeline_set_repeat_count() è la stessa identica funzione che abbiamo utilizzato con il nostro timeline all'inizio del tutorial, imposta ad infinito il numero di ripetizioni dell'animazione;
  • clutter_actor_add_transition() aggiunge l'animazione all'attore, da questo momento verrà avviata. “centrifuga” è il nome che abbiamo dato a questa animazione per identificarla, un attore infatti può contenere più animazioni definite con la classe ClutterTransition, aggiungendo più animazioni semplici otterremo animazioni complesse.

Con questo concludo la seconda parte del tutorial e mi impegno ad approfondire ulteriormente gli altri aspetti di Clutter nel prossimo tutorial.

Vi consiglio sempre di andare a guardare sulla guida di riferimento la sintassi completa delle istruzioni che abbiamo fin qui utilizzato. Utilizzate DevHelp o andate sul sito per farlo.


Per domande, suggerimenti, o segnalazioni di errori vi invito a lasciare dei commenti.


Nessun commento:

Posta un commento