Controlar una acción: play, stop, pausa y velocidad
La diferencia real entre stop, paused y enabled, cómo funcionan timeScale y setEffectiveTimeScale, y para qué sirven startAt, halt, warp y syncWith.
Arrancar y parar
const accion = mixer.clipAction( clip );accion.play(); // registra la accion en el mixer, empieza a evaluarseaccion.stop(); // la des-registra y la reseteaaccion.reset(); // resetea sin des-registrarplay() no hace nada más que registrar la acción como activa: this._mixer._activateAction( this ). No resetea el tiempo. Una acción que se paró con paused = true y a la que llamas play() continúa donde estaba.
stop() hace dos cosas: des-registra y llama a reset(). Y reset() pone paused = false, enabled = true, time = 0, olvida el conteo de bucles y cancela cualquier desvanecimiento o warp pendiente.
Las tres formas de detener, con lo que quedan:
| ¿Sigue registrada? | ¿Conserva time? |
¿Afecta a la propiedad? | |
|---|---|---|---|
stop() |
no | no, vuelve a 0 | no |
paused = true |
sí | sí | sí, congela la pose actual |
enabled = false |
sí | sí | no, peso efectivo 0 |
La fila del medio es la que sorprende y la que resuelve un caso muy concreto. Una acción pausada sigue escribiendo su valor en la propiedad cada frame, porque su peso sigue siendo el que era: el personaje se queda congelado en la pose exacta en que estaba. Una acción con enabled = false deja de contribuir del todo, y si no hay ninguna otra acción escribiendo esa propiedad, el objeto vuelve a su transformación original de golpe.
Y hay un detalle documentado en el código de reset() que conviene retener: desactivar enabled no resetea la acción. Al volver a activarla, continúa desde donde estaba.
Velocidad y duración
timeScale y su setter
accion.timeScale = 2; // el doble de rapidoaccion.setEffectiveTimeScale( 2 ); // lo mismo, y ademas cancela el warpLa propiedad se puede asignar directamente y funciona. El método hace dos cosas más:
// Implementacion real de setEffectiveTimeScale:setEffectiveTimeScale( timeScale ) { this.timeScale = timeScale; this._effectiveTimeScale = this.paused ? 0 : timeScale; return this.stopWarping();}Actualiza el valor efectivo inmediatamente en lugar de esperar al siguiente update, y cancela cualquier warp en curso. Esa segunda parte es la razón de existir del método: si hay un cambio gradual de velocidad programado, asignar timeScale a pelo no lo cancela y el warp seguirá sobrescribiendo tu valor. Usa el método siempre que puedas haber programado un warp.
Los valores tienen semántica completa:
accion.timeScale = 1; // normalaccion.timeScale = 0.5; // mitad de velocidadaccion.timeScale = 0; // congelada, equivalente a paused para el avanceaccion.timeScale = -1; // hacia atrasReproducir hacia atrás funciona de verdad, incluidos los bucles y el evento finished, que llega con direction: -1.
Y existe la escala global del mixer, que multiplica a la de cada acción:
mixer.timeScale = 0.25; // toda la escena a cuarto de velocidadAjustar la duración sin tocar el clip
accion.setDuration( 3 );No modifica el clip: calcula el timeScale necesario para que el clip completo dure ese tiempo. La implementación es literal:
setDuration( duration ) { this.timeScale = this._clip.duration / duration; return this.stopWarping();}Es el método que quieres cuando una animación tiene que encajar en una ventana temporal fija —sincronizar con audio, con una transición de página, con otra animación— sin importar cuál era su duración original.
Programar el arranque y frenar con suavidad
Programar el arranque
// Empieza cuando el reloj del mixer llegue a ese instante.accion.startAt( mixer.time + 1.5 ).play();startAt guarda un instante en el reloj del mixer, no en el reloj del navegador. Hasta llegar a él, la acción está registrada pero su delta es cero, y isRunning() devuelve false mientras isScheduled() devuelve true. Esa pareja de métodos es exactamente para distinguir esos dos estados.
Es la forma limpia de encadenar sin setTimeout, y respeta mixer.timeScale: si pones la escena a cámara lenta, el arranque programado también se retrasa proporcionalmente, cosa que un temporizador de JavaScript no haría.
Frenar y acelerar suavemente
accion.halt( 0.8 ); // frena hasta parar en 0.8 saccion.warp( 1, 2.5, 1.2 ); // acelera de 1x a 2.5x en 1.2 saccion.stopWarping(); // cancela el warp en cursowarp programa un interpolante sobre timeScale entre dos valores. Y halt es literalmente warp( timeScaleActual, 0, duracion ): cuando la escala llega a cero, el código pone paused = true.
La diferencia con parar de golpe se nota mucho en animaciones cíclicas. Un personaje corriendo que se detiene con stop() congela un frame arbitrario del ciclo; el mismo personaje con halt( 0.5 ) desacelera de forma que los pasos se acortan hasta pararse, que es lo que hace un cuerpo real.
Sincronizar dos acciones
correr.syncWith( andar );Copia time y timeScale de la otra acción. Es el paso previo obligatorio a un crossfade entre dos ciclos de locomoción: si andar va por el 40 % de su ciclo y correr empieza en cero, el crossfade mezcla el pie izquierdo adelantado de una con el derecho adelantado de la otra, y el resultado es un tropiezo. Sincronizando primero, los dos ciclos van en fase y la mezcla es limpia.
El requisito es que los dos clips tengan la misma estructura de fase: que el punto de contacto del pie esté en la misma fracción de la duración. Eso hay que pedírselo al animador, o conseguirlo con setDuration sobre uno de los dos.