Creare un gestore di tween

Lascia un commento

04/05/2012 di baudaffi

In Flash i tween servono a modificare delle proprietà di un oggetto nel tempo.
Purtroppo però un singolo tween gestisce una sola proprietà, quindi dopo aver visto cosa ci dona il web (svariate librerie che gestiscono anche le cose più assurde) mi sono deciso di crearmene una mia versione semplificata che faccia giusto quello che serve a me senza troppi fronzoli (non è detto che sia la soluzione giusta per voi).

Un pò di matematica per capire meglio come gestire l’avanzamento dell’animazione:

FR : frame rate del filmato
T : tempo dell'animazione
dT = T / FR  #delta temporale per frame
pI : valore iniziale della proprietà
pF : valore finale della proprietà
dP : (pF - pI) / dT #incremento frame nel nostro caso

Capiti questi semplicissimi conti il resto è solo implementazione, che a partire dalla versione basilare che allegherò in basso, potete aggiungere funzionalità e/o ottimizzare alcuni passaggi.

Per prima cosa ho creato una classe che mi gestisse la lista di animazioni, quindi mi servirà un array per maneggiarle, un booleano per gestire l’interruzzione brusca di tutte le animazioni in corso e un intero per mantermi il frameRate del mio filmato.

private var frameRate:int = 0;
private var lista:Array = [];
private var killed:Boolean = false;

Quindi il costruttore necessita il parametro frameRate per poter inizializzare la variabile:

public function TweenManager(fr:int) 
{
      frameRate = fr;
}

Per la firma del metodo per l’inserzione di una nuova animazione mi sono adattato a quello che utilizzano le altre librerie, semplificando dove si può:

public function to(target:MovieClip, tempo:Number, param:Object):void

target è l’oggetto che subisce la modifica
tempo indica il tempo complessivo di esecuzione della modifica
param contiene le proprietà con relativo valore finale (es.: x:100, y:50, alpha:0.2 ecc..) e 5 proprietà aggiuntive che mi tornano utili per il mio lavoro e sono:

  • delay  : ritardo prima di far partire l’animazione
  • onStart :  funzione da invocare prima dell’animazione
  • onStartParams : parametri da passare alla funzione onStart
  • onComplete : funzione da lanciare alla fine dell’animazione
  • onCompleteParams :parametri da passare alla funzione onComplete

Per recuperare queste informazioni:

//prendo il delay
if(param.delay != undefined)
{
    delay = Number(param.delay) *800;
    delete param.delay;
}

//onStart
var sFunction:Function = null;
if(param.onStart!= undefined)
{
    sFunction = param.onStart;
    delete param.onStart;
}
//onStartParams
var onStartParams:Array = [];
if(param.onStartParams != undefined)
{
    onStartParams = param.onStartParams as Array;
    delete param.onStartParams;
}

//onComplete 
var cFunction:Function = null;
if(param.onComplete != undefined)
{
    cFunction = param.onComplete;
    delete param.onComplete;
}
//onCompleteParams
var onCompleteParams:Array = [];
if(param.onCompleteParams != undefined)
{
    onCompleteParams = param.onCompleteParams as Array;
    delete param.onCompleteParams;
}

Se vi state chiedendo il motivo per cui cancello le proprietà “particolari” dai params, la risposta e’ semplice, per semplificarmi la gestione dei prorpietà base, se queste sono esplicitate:

var dT:Number = (tempo) * frameRate;
var delay:Number = 0.0;
var prop:Array = [];
var step:Object = { };
for (var prp in param)
{
    prop.push(prp);
    step[prp] = (param[prp] - target[prp]) / dT;
}

ciclo su quelle rimanenti e ne salvo il nome e il valore da raggiungere, con il quale vado a calcolarmi il valore dello step incrementale, da poter così passare alla classe che lo gestirà.
Questa simpatica funzione si conclude con il consimo del primo parametro il delay, quindi non vado a far altro che settarmi un time-out per poi far partire tutto il meccanismo:

setTimeout(startAnimation,delay,target, prop, step,dT,cFunction,onCompleteParams,sFunction,onStartParams);

Ora andiamo ad analizzare la funzione start animation, che dovrà chiamare la funzione onStart se specificata e lanciare l’animazione:

public function startAnimation():void 
        {
            if (arguments[6] != null)
            {
                arguments[6].apply(null, arguments[7]);
            }

            var tt:GOLTween = new GOLTween(lista.length);
            tt.Tweener(arguments[0], arguments[1], arguments[2],arguments[3],arguments[4],arguments[5]);
            lista.push(tt); 
            if (!hasEventListener(Event.ENTER_FRAME) && !killed )
            {
                addEventListener(Event.ENTER_FRAME, onEnterFrame);
            }
        }

Purtroppo non potendo specificare gli argomenti nella firma della funzione si devono richiamare gli argomenti in maniera poco classica, andando a reperirli nell’array degli argomenti arguments, quindi tenete sempre sott’occhio la chiamata per capire quale argomento si sta utilizzando.
Ora la funzione che ogni frame verra’ richiamata, che non fara’ altro che aggiornare le animazioni nell’array o le eliminera’ al loro completamento:

private function onEnterFrame(e:Event):void 
        {
    for (var it:int; it < lista.length ;++it )
    {
        if (!lista[it].ucciso)
        {
            lista[it].onIncrement();
        }
        else
        {
            lista[it].onComplete();
            lista.splice(it, 1);
        }
    }

    if (lista.length == 0 )
    {
        removeEventListener(Event.ENTER_FRAME, onEnterFrame);
    }
}

infine la funzione che arresta tutto:

public function kill():void
{
    killed = true;
    removeEventListener(Event.ENTER_FRAME, onEnterFrame);
    for (var t:int = 0; t < lista.length;++t )
    {
        lista[t].ucciso= true;
        lista[t] = null;
    }
}

E chi si conclude la clase che gestisce le animazioni.
Ora passiamo alla classe che esegue la singola animazione.

public function Tweener(target:Object, prop:Array, step:Object,nStep:int,onComplete:Function, completeParam:Array):void 
{
    _target = target;
    _prop = prop;
    _step = step;
    _nStep = nStep;
    _onComplete = onComplete;
    _completeParam = completeParam;
}

Mi prendo i parametri che mi servono: il target dell’animazione, la lista delle proprietà da aggiornare, il valore di ogni  incremento, gli step da fare e la funzione da chiamare a fine animazione con i suoi parametri.
Ora la funzione che esegue l’aggiornamento:

public function onIncrement():void 
{
    if (ucciso){ return; }
    _nStep--;
    ucciso = _nStep == 0;

    for(var prp in _prop)
    {
        _target[_prop[prp]] += _step[_prop[prp]];
    }
}

e la chiamata alla onComplete, che abbiamo visto prima:

public function onComplete():void 
{
    if (_onComplete != null)
        {
            _onComplete.apply(null,_completeParam);
        }
}

per concludere la funzione che banalmente uccide l’animazione:

public function kill():void 
{
    ucciso = true;
}

Fine.

Per chi ha bisogno di spingere al massimo le potenzialità di queste funzioni, può migliorarlo andando a cambiare prima di tutto l’arrai nel gestore e sostituirlo con una struttura dati tipo Linked-List che è più performante in inserimento ed eliminazione di oggetti; cche usare tecniche di loop-unrooling in caso di uso massiccio.

Annunci

Rispondi

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione /  Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione /  Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione /  Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione /  Modifica )

Connessione a %s...

%d blogger hanno fatto clic su Mi Piace per questo: