BeeTween — Documentazione Tecnica
Interpola numeri sul target ogni frame:
to / from /
fromTo, capture una tantum, clock
gioco.tweens
📁 Panoramica
BeeTween scrive proprietà numeriche. Vive in src/core/BeeTween.js insieme a
BeeEase, resolveEase, BEE_TWEEN_DEFAULTS e
BeeTweenClock. Il barrel le re-esporta. BeeTimer conta i secondi.
Questa classe assegna x, alpha, scaleX,
volume ogni frame, con easing. Non è un cooldown, non è GSAP, non è
BeeAnimator (grafo di clip). Per una fila di tween + wait + call usa
BeeTimeline.
BeeEngine crea un clock: this.tweens = new
BeeTweenClock(). Ogni frame, anche in pausa, il loop fa
time.tick(timestamp), this.timers.tick(this.time) e subito
this.tweens.tick(this.time). gioco.to /
from / fromTo iniettano quel clock e partono da soli
(autoStart: true). I tween figli di una timeline
non stanno sulla lista live: nascono con autoStart: false,
overwrite: false e senza clock. Li guida la timeline con
seek.
📁 Due vie: clock del motore vs update manuale
gioco.to(target, props, durationOrOptions) è zucchero:
BeeTween.to(..., { ...opts, clock: this.tweens }). Stesso schema per
from / fromTo. Il terzo argomento è un numero (solo
duration) oppure un oggetto. Le factory statiche forzano
autoStart: true: start() gira nel costruttore.
Un new BeeTween({...}) senza clock non entra in
gioco.tweens. Devi chiamare tween.update(gioco.time) (o un
numero). Se lo metti in scene.update / entity.update, in pausa
del motore quell’hook non parte: l’opzione unscaled da sola non basta.
Il callback engine.update gira anche in pausa; oppure registralo sul clock.
Non fare entrambe. Un tween già sul clock che riceve anche
update a mano avanza due volte nello stesso frame.
Se passi clock nel costruttore senza autoStart,
clock.add(this) parte comunque: l’istanza resta in lista con
running === false (zombie) finché non fai start() o
cancel(). gioco.to non cade qui: autoStart è già true. I figli
della timeline non ricevono clock.
📁 to / from / fromTo
Tre mode, stessi numeri: path stringa (x, transform.x), valore
finito. readProp / writeProp camminano il path; un intermedio
null non crea oggetti, la write è no-op. Non-numero in lettura →
0.
| Mode | From (catturato a #begin) |
To |
|---|---|---|
to |
valore corrente sul target | spec to; se l’end non è finito resta il from (no-op) |
from |
spec from (se non finito: corrente) |
valore corrente al capture |
fromTo |
spec from se la chiave c’è, altrimenti corrente |
spec to se la chiave c’è, altrimenti il from (hold) |
Il mode lo imposta la factory. Nel costruttore nudo: se c’è from e anche
chiavi in to → fromTo; solo from →
from; altrimenti to. Le chiavi sono uno snapshot dello spec:
mutare l’oggetto passato dopo new non cambia il tween.
Ease: nome in BeeEase (quadOut default,
backOut, bounceOut, elasticOut…) o funzione
t => t. Stringa sconosciuta → linear. L’input della curva
è clampato in [0, 1]; l’uscita no: back/elastic/bounce possono
overshootare. Non ci sono bounceIn, elasticIn,
circ*: passa una funzione.
fromTo non filtra i non-finiti (a differenza di to /
from): Number('foo') diventa NaN e finisce sul
target. Tieni numeri veri nello spec. Vedi
Known limitations.
📁 Capture: una tantum, dopo il delay
#capture gira una volta, in #begin, quando
#captured è ancora false. Poi il flag resta true. start()
azzera elapsed / cycles / backward / delay, non ricattura. Un restart
(o un repeat della timeline sul figlio) riparte dallo snapshot originale, non dai
valori correnti. È il motivo per cui un .to(box, { x: 200 }) in loop
non resta inchiodato sul to.
Il capture sta dopo il delay: un to con
delay: 0.4 legge il from quando la coda scade, non alla
new. seek chiama #begin subito: salta il delay
e cattura in quel momento.
Cambiare target o lo spec e rifare start() sulla stessa
istanza è sbagliato: scrive i vecchi from/delta sul (nuovo) target. Serve un tween
nuovo. Non esiste reset / recapture pubblico.
📁 Delay e duration 0
delay è Math.max(0, Number(spec.delay) || 0), copiato in
#wait a ogni start. update scala
#wait con lo step; l’eventuale overflow del delay diventa step del
tween nello stesso frame. Durante l’attesa elapsed resta 0,
progress è 0, onStart non è ancora partito.
duration non finita o negativa → default
BEE_TWEEN_DEFAULTS.duration (0.4). 0 è legale: al primo
update dopo #begin applica u = 1 e entra in
#complete. progress con duration 0 vale sempre 1.
repeat: Infinity a duration 0 completa un ciclo ogni update
(snap). Un numero negativo passato a update sottrae da
elapsed: non è un rewind ufficiale.
📁 play → delay → capture → complete
start() è un restart: azzera elapsed / finished / cancelled, rimette
#wait = delay, re-add sul clock. Se il delay è 0 chiama
#begin nello stesso call (capture, overwrite, onStart). Il
complete non parte da start: parte da
update quando elapsed raggiunge duration (o
duration è 0).
Un ciclo standalone: armi, aspetti il delay, catturi una
volta, il clock somma dt, applichi, chiudi o ripeti. seek non è
questo percorso.
-
1. startAzzera,
clock.add,#beginse delay 0. -
2. delay
#wait -= dt. Overflow nello stesso frame. -
3. updateCapture una tantum,
elapsed += dt, ease, write. -
4. #completeYoyo/repeat oppure
onComplete. Il clock toglie i one-shot.
📁 Due assi: scaled e unscaled
Il tween non sceglie il delta da solo se gli passi un numero. Sceglie solo se gli
passi BeeTime (ha delta) o un oggetto
{ dt, unscaledDt }. Il clock del motore passa sempre
this.time.
Argomento di update |
Tween scalato | Tween unscaled |
|---|---|---|
BeeTime (ha delta) |
time.delta(false) → dt (0 in pausa, segue
timeScale) |
time.delta(true) → unscaledDt |
oggetto { dt, unscaledDt } |
dt se è un number, altrimenti 0 |
unscaledDt se è un number, altrimenti 0 |
| numero | quello step, punto. Il flag unscaled viene ignorato |
|
unscaled e l’alias useUnscaledTime si allineano nel
costruttore. resolveStep legge tween.unscaled. Se muti solo
useUnscaledTime a caldo, lo step non cambia.
pause() sull’istanza è un asse diverso dalla pausa di
BeeTime: spegne running, tiene elapsed, resta in lista, il
prossimo update esce subito. In freeze del motore (F4)
tweens.tick parte lo stesso: i tween scalati sommano dt = 0,
gli unscaled sommano unscaledDt.
📁 start, pause, resume, cancel, seek
Non sono sinonimi. Fluent: tutti ritornano this. Non esiste
stop (alias di BeeTimer), non esiste reset, non esiste
destroy sull’istanza.
| Metodo | elapsed | running / paused | finished / cancelled | Clock / capture |
|---|---|---|---|---|
start() |
azzera; #wait = delay |
true / false |
entrambi false |
re-add; #begin se delay 0; non ricattura |
pause() |
invariato | false / true (no-op se non era running) |
invariati | resta in lista, update esce subito |
resume() |
invariato | true / false |
no-op se finished o cancelled |
re-add |
cancel() |
invariato | false / false |
finished false, cancelled true |
clock.remove(this); niente onComplete |
seek(seconds) |
clamp [0, duration] |
invariati, salvo il ramo “fine one-shot” | può marcare finished senza #complete |
se non era started: #begin (salta il delay) |
start due volte è un restart, non un no-op: elapsed torna a 0, il pose
riparte dallo snapshot. Dopo un one-shot finito, resume non riparte:
serve start(). Dopo cancel, stesso discorso: solo
start (riazzera cancelled e re-add).
Teardown = cancel() o gioco.tweens.kill(target).
engine.destroy() chiama tweens.clear(): stacca
clock e fa cancel su ciascun live.
📁 seek vs update
Due API, due contratti. update è il tempo che scorre:
delay → begin → elapsed += dt → #apply → a
u >= 1 entra in #complete (yoyo, repeat,
onComplete). seek(seconds) è un pose setter: forza
#begin se manca, scrive elapsed, applica l’ease,
non chiama #complete.
Se t >= duration e repeat === 0 e non c’è
yoyo, seek marca finished = true e
running = false. Stop. Niente onComplete, niente ciclo
yoyo/repeat, niente overflow. Con yoyo o repeat resta dove l’hai messo (clamp alla
duration) e non gira i cicli.
BeeTimeline.#applyTime fa esattamente questo: tween.start()
poi tween.seek(local). Perciò tween.onComplete di un figlio
non parte mai dalla timeline, né da timeline.seek. I
marker di clip stanno su timeline.call o su
timeline.onComplete.
seek su un tween già finished continua a scrivere il pose
(utile per lo scrub) ma non rimette running. Il prossimo
update esce subito. seek prima di start spara
onStart e salta il delay; il start successivo può
risparare onStart.
📁 repeat, yoyo, loop
Default repeat: 0 → un ciclo. repeat: 2 → tre giri (come
GSAP: repeat + 1). repeat: Infinity imposta anche
loop = true: il compact del clock lo tiene in lista.
A fine ciclo #complete incrementa #cycles. Se ne restano:
elapsed = 0; se yoyo inverte #backward,
altrimenti #apply(0) (snap al from). L’overflow
elapsed - duration si scarta: niente catch-up come in
BeeTimer.
yoyo: true con repeat: 0 non torna
indietro: un ciclo, fine, onComplete. Serve
repeat >= 1 (o Infinity) per l’andata/ritorno. In
reverse il pose è ease(1 - u). progress resta 0→1 sul
ciclo locale, non è l’asse visivo.
Sulla timeline, yoyo/repeat dell’item allungano il ciclo del
BeeTween ma lo slot resta tween.duration (un ciclo) e il
figlio è guidato con seek, che non cicla. Yoyo/repeat della
sequenza stanno sul costruttore della timeline.
Se imposti tween.loop = true a mano ma repeat è finito, il
complete marca finished e update esce: resta uno zombie sul
clock. Lascia che loop lo derivi da Infinity.
📁 overwrite, steal, kill
Default overwrite: true (spec.overwrite !== false). In
#begin, dopo il capture, se c’è un clock con
overwrite parte clock.overwrite(this): sugli altri live
con lo stesso target chiama steal(target, keys).
steal toglie le path uguali (confronto stringa:
x ≠ transform.x). A chiavi finite fa
cancel().
Contratto “ultimo tween che fa #begin vince”. Un
gioco.to(stessoOggetto, stesseChiavi) ruba le chiavi a un altro
standalone e, via timeline.steal, anche ai figli di una timeline già
armati. I figli non sono sul clock: la timeline fa prima
#stealSiblings tra item armati, poi clock.overwrite.
steal su un tween non ancora catturato è no-op (#keys è
[]). Un tween ancora in delay non ha chiavi: l’overwrite di un altro
non lo svuota; quando scade il delay ricattura e può riprendersi le proprietà. Vedi
Known limitations.
gioco.tweens.kill(target) scorre i live: se
item.target === target (riferimento) chiama cancel;
altrimenti, se esiste killTarget (timeline), inoltra. La timeline
non si spegne: cancella solo i figli su quel target. Il rewind di
un repeat timeline rimette cancelled = false sul figlio; al prossimo
arm start() non ricattura.
📁 Target morto
In testa a ogni update: se target && target.destroyed ===
true → cancel(), niente onComplete. Il pose resta
all’ultimo write. BeeEntity.destroy mette proprio
destroyed = true. Un plain object senza quel flag continua.
target === null (o assente): il check non scatta,
writeProp è no-op, il tween arriva a #complete e spara
onComplete (fantasma). destroyed truthy ma non
=== true non conta. Durante il delay il check è già attivo: un’entity
distrutta in coda non fa onStart.
📁 BeeTweenClock
add è idempotente e assegna item.clock = this.
remove stacca il puntatore se era questo clock, non chiama
cancel. tick(time) chiama update sui non
cancelled, poi compatta: via i cancelled e i finished && !loop.
Un paused (running false, non finished) resta. clear azzera la lista,
mette clock = null, poi cancel su ciascuno.
Nessun try/catch. Un throw in onStart / onUpdate /
onComplete ferma il resto del tick di quel frame.
📁 Integrazione: loop, scene, entity
Ordine reale nel loop V2.4:
I tween sul clock si muovono prima di scene e entity. Un
onComplete che fa scenes.change vede ancora le entity
vecchie in quello stesso frame.
📁 Esempio: slide, pulse HUD, kill
📁 Note per chi integra
Clock per HUD e freeze. gioco.to se deve vivere la
pausa (con unscaled: true) o non hai un hook ogni frame. Update
manuale se il tween muore con l’entity: cancel() in
destroy — oppure lascia che destroyed === true lo
cancelli al prossimo tick.
Un numero è uno step. Per rispettare pausa e slow-mo passa
gioco.time, non gioco.time.dt.
start = restart. Utile per un intro che si rinfresca. Non è “parte
solo se era ferma”. Lo snapshot resta quello del primo #begin.
Callback di fine. Su un tween standalone usa
onComplete (solo da update → #complete). Su
una timeline usa call / timeline.onComplete, non
tween.onComplete.
Callback che lancia. Non è wrappata. Gli altri live dello stesso
tick possono non girare. Tieni onStart /
onUpdate / onComplete corte e senza throw.
Niente destroy sull’istanza. cancel in
entity.destroy se l’hai creata a mano. Il motore pulisce solo
gioco.tweens.
📁 Known limitations
Limiti del contratto attuale — non workaround da “fixare in userland” se il JS non
cambia. Il path felice (gioco.to + clock + update) è
solido; BeeTimeline dipende da capture-una-tantum e da
seek che non completa.
seek non è update. Niente
#complete: onComplete non parte, yoyo/repeat non ciclano.
A fine one-shot marca solo finished / spegne running.
Salta il delay e può sparare onStart. Non “fixare” facendo entrare
seek in #complete: spezzeresti le timeline. Se serve un
complete esplicito, è un’altra API.
Capture una tantum. start() non rilegge il target.
Cambiare spec/target e riavviare la stessa istanza è fuori contratto. Per un from
nuovo, new di un altro tween.
fromTo e valori non finiti. to /
from cadono sul corrente se l’end/start non è finito.
fromTo spinge Number(...) crudo: non-numero →
NaN sul target.
Clock add nel costruttore. new BeeTween({ clock })
senza autoStart entra in lista da fermo. gioco.to e i figli timeline
non passano di lì.
Overwrite vs delay. steal prima del capture non toglie
chiavi. Il tween in coda, quando inizia, può riprendersi le proprietà.
Yoyo senza repeat. yoyo: true e
repeat: 0 fanno un’andata sola. L’andata/ritorno vuole
repeat >= 1.
Yoyo/repeat del figlio in timeline. Lo slot dura un ciclo; il
guida è seek. Non aspettarti il loop dell’item.
Target assente. Solo destroyed === true cancella.
null completa da fantasma. La write su path rotto è silenziosa.
Residuo di dt / niente catch-up. Superata la duration, si applica
l’estremo e si chiama #complete. L’overflow si scarta. Un dt più grosso
di un ciclo non entra nel giro nuovo (il delay invece sì versa l’avanzo
nello stesso frame).
Nessun try/catch. Throw in callback ferma il
clock.tick di quel frame.
📁 Riepilogo API
Fluent: start, pause, resume,
cancel, steal, seek, update
ritornano this. Default frozen: BEE_TWEEN_DEFAULTS
(duration 0.4, ease quadOut, delay 0, overwrite true, autoStart
false sulle opzioni nudo — le factory statiche forzano true).
| Membro | Ritorna | Scopo |
|---|---|---|
new BeeTween(options?) |
BeeTween |
target, to / from,
mode, duration, delay,
ease, unscaled /
useUnscaledTime, yoyo, repeat,
overwrite, callback, clock,
autoStart. |
BeeTween.to(target, props, durationOrOptions?) |
BeeTween |
Mode to, autoStart: true. Terzo arg: numero o
opzioni (possono sovrascrivere anche autoStart /
clock). |
BeeTween.from(target, props, durationOrOptions?) |
BeeTween |
Mode from. Stesso zucchero. |
BeeTween.fromTo(target, from, to, durationOrOptions?) |
BeeTween |
From e to nello spec. Stesso zucchero. |
gioco.to / from / fromTo |
BeeTween |
Come le statiche, con clock: gioco.tweens. |
duration / elapsed / delay |
number |
Secondi. delay è lo spec; il countdown vivo è privato
(#wait). |
progress |
number |
In [0, 1]; se duration <= 0 vale 1. In yoyo
resta 0→1 sul ciclo locale. In delay vale 0. |
keys |
string[] |
Path catturate. Vuoto prima di #begin. Usato da
clock.overwrite. |
repeat / yoyo / loop |
number / boolean |
Giri extra; invertito a fine ciclo. loop è
repeat === Infinity (ristabilito in start). |
unscaled |
boolean |
Asse reale. Alias costruttore: useUnscaledTime. |
overwrite |
boolean |
Default true. In #begin chiama
clock.overwrite(this) se il clock ce l’ha. |
running / paused / finished /
cancelled |
boolean |
paused è getter su campo privato: true solo dopo
pause su un tween che stava running. |
onStart / onUpdate / onComplete |
function | null |
Ricevono (tween). onStart in
#begin; onUpdate in ogni
#apply (anche seek); onComplete solo da
#complete via update. |
clock |
BeeTweenClock | null |
Clock owner, o null se è un tween manuale / dopo
remove. |
ease |
function |
Risolta da resolveEase nel costruttore. Mutarla a caldo
con una stringa rompe #apply. |
start() |
this |
Azzera e parte. Restart se già running. Non ricattura. |
pause() |
this |
Ferma, tiene elapsed. Nessun alias stop. |
resume() |
this |
Riparte senza azzerare. No-op se finished o cancelled. |
cancel() |
this |
Spegne, marca cancelled, toglie dal clock. Niente
onComplete. |
steal(target, keys) |
this |
Toglie le path. A chiavi finite: cancel. Usato da
clock.overwrite. |
seek(seconds) |
this |
Pose setter. Non completa. Può marcare finished su un one-shot. |
update(dtOrTime) |
this |
Avanza. Accetta BeeTime,
{ dt, unscaledDt }, o un number (step esplicito). |
BeeEase / resolveEase(nameOrFn) |
oggetto frozen / function |
Curve t in [0, 1]. Nome sconosciuto o non
function → linear. |
BEE_TWEEN_DEFAULTS |
oggetto frozen | duration 0.4, delay 0, ease quadOut, unscaled false,
yoyo false, repeat 0, autoStart false, overwrite true. |
new BeeTweenClock() |
BeeTweenClock |
Lista live. gioco.tweens è l’istanza del motore. |
clock.add / remove / tick /
clear |
item o this |
add idempotente. tick compatta cancelled e
one-shot finished. clear cancel di tutti. |
clock.overwrite(source) |
this (clock) |
steal sugli altri live con lo stesso target e le stesse
keys. |
clock.kill(target) |
this (clock) |
Cancel i tween con quel target; sulle timeline chiama
killTarget. |
clock.size |
number |
Lunghezza della lista live (paused compresi, finished one-shot già compattati no). |
gioco.timeline(options?) |
BeeTimeline |
Sequencer sullo stesso clock. I figli non sono live: vedi BeeTimeline. |