Ampliación de la Timeline: Una guía práctica

CIRO CONTINISIO / UNITY TECHNOLOGIESContributor
Sep 5, 2018|13 minutos
Ampliación de la Timeline: Una guía práctica
Para tu comodidad, tradujimos esta página mediante traducción automática. No podemos garantizar la precisión ni la confiabilidad del contenido traducido. Si tienes alguna duda sobre la precisión del contenido traducido, consulta la versión oficial en inglés de la página web.

Unity lanzó Timeline junto con Unity 2017.1 y desde entonces, hemos recibido muchos comentarios al respecto. Después de hablar con muchos desarrolladores y responder a los usuarios en los foros, nos dimos cuenta de cuántos de ustedes quieren usar Timeline para algo más que como una simple herramienta de secuenciación. Ya he dado un par de charlas sobre esto (por ejemplo, en Unite Austin 2017) sobre cómo utilizar Timeline para usos no convencionales.

Timeline fue diseñado con la extensibilidad como objetivo principal desde el principio; el equipo que diseñó la función siempre tuvo en mente que los usuarios querrían crear sus propios clips y pistas además de los incorporados. Como tal, hay muchas preguntas sobre la creación de scripts con Timeline. El sistema en el que se basa Timeline es poderoso, pero puede resultar difícil trabajar con él para aquellos no iniciados.

Pero primero, ¿qué es Timeline? Es una herramienta de edición lineal para secuenciar diferentes elementos: clips de animación, música, efectos de sonido, tomas de cámara, efectos de partículas e incluso otras líneas de tiempo. En esencia, es muy similar a herramientas como Premiere®, After Effects® o Final Cut®, con la diferencia de que está diseñado para la reproducción en tiempo real.

Para una visión más profunda de los conceptos básicos de Timeline, le recomiendo visitar la sección de documentación de Timeline del Manual de Unity , ya que haré un uso extensivo de esos conceptos.

La API jugable

La Timeline se implementa sobre la API de Playables.

Es un conjunto de API potentes que le permite leer y mezclar múltiples fuentes de datos (animación, audio y más) y reproducirlas a través de una salida. Este sistema ofrece un control programático preciso, tiene una sobrecarga baja y está optimizado para el rendimiento. Por cierto, es el mismo marco detrás de la máquina de estados que impulsa el componente Animator, y si has programado para Animator probablemente verás algunos conceptos familiares.

Básicamente, cuando una Timeline comienza a reproducirse, se construye un gráfico compuesto de nodos llamados Jugables. Están organizados en una estructura similar a un árbol llamada PlayableGraph.

Nota: Si quieres visualizar el árbol de cualquier PlayableGraph en la escena (Animadores, Líneas de Tiempo, etc.) puedes descargar una herramienta llamada PlayableGraph Visualizer. Esta publicación lo utiliza para visualizar los gráficos de los diferentes clips personalizados.

Ahora repasaremos tres ejemplos sencillos que le mostrarán cómo ampliar Timeline. Para sentar las bases, comenzaré con la forma más sencilla de agregar un script en Timeline. Luego se irán añadiendo más conceptos paulatinamente para aprovechar la mayoría de las funcionalidades.

Activos

He empaquetado un pequeño proyecto de demostración con todos los ejemplos utilizados en esta publicación. Siéntete libre de descargarlo para seguirlo. De lo contrario, puedes disfrutar la publicación por sí sola.

Nota: Para los activos, he utilizado prefijos para diferenciar las clases en cada ejemplo (“Simple_”, “Track_”, “Mixer_”, etc.). En el código siguiente, estos prefijos se omiten para facilitar la legibilidad.

Ejemplo 1: Clips personalizados

Este primer ejemplo es muy simple: el objetivo es cambiar el color y la intensidad de un componente de Luz con un clip personalizado. Para crear un clip personalizado, necesitas dos scripts:

  • Uno para los datos: heredando de PlayableAsset
  • Uno por la lógica: heredando de PlayableBehaviour

Un principio fundamental de la API reproducible es la separación de la lógica y los datos. Es por esto que primero necesitarás crear un PlayableBehaviour, en el que escribirás lo que quieres hacer, de la siguiente manera:

public class LightControlBehaviour : PlayableBehaviour
{
public Light light = null;
public Color color = Color.white;
public float intensity = 1f;

public override void ProcessFrame(Playable playable, FrameData info, object playerData)
{
if (light != null)
{
light.color = color;
light.intensity = intensity;
}
}
}

¿Que está pasando aquí? Primero, hay información sobre qué propiedades de la Luz quieres cambiar. Además, PlayableBehaviour tiene un método llamado ProcessFrame que puedes anular.

ProcessFrame se llama en cada actualización. En ese método, puedes configurar las propiedades de la Luz. Aquí está la lista de métodos que puedes anular en PlayableBehaviour. Luego, crea un PlayableAsset para el clip personalizado:

public class LightControlAsset : PlayableAsset
{
   public ExposedReference<Light> light;
   public Color color = Color.white;
   public float intensity = 1.0f;

   public override Playable CreatePlayable (PlayableGraph graph, GameObject owner)
   {
       var playable = ScriptPlayable<LightControlBehaviour>.Create(graph);

       var lightControlBehaviour = playable.GetBehaviour();
       lightControlBehaviour.light = light.Resolve(graph.GetResolver());
       lightControlBehaviour.color = color;
       lightControlBehaviour.intensity = intensity;

       return playable;
   }
}

Un activo jugable tiene dos propósitos. En primer lugar, contiene datos del clip, ya que están serializados dentro del propio activo de la Timeline . En segundo lugar, construye el PlayableBehaviour que terminará en el gráfico Playable.

Mira la primera línea:

var playable = ScriptPlayable<LightControlBehaviour>.Create(graph);

Esto crea un nuevo Playable y le adjunta un LightControlBehaviour, nuestro comportamiento personalizado. Luego puedes configurar las propiedades de luz en PlayableBehaviour.

¿Qué pasa con ExposedReference? Dado que un PlayableAsset es un activo, no es posible hacer referencia directa a un objeto en una escena. Una ExposedReference actúa entonces como una promesa de que, cuando se llama a CreatePlayable , se resolverá un objeto.

Ahora puedes agregar una pista reproducible en la línea de tiempo y agregar el clip personalizado haciendo clic derecho en esa nueva pista. Asigna un componente Luz al clip para ver el resultado.

En este escenario, la pista reproducible incorporada es una pista genérica que puede aceptar estos clips reproducibles simples como el que acaba de crear. Para situaciones más complejas, necesitarás alojar los clips en una pista dedicada.

Ejemplo 2: Pistas personalizadas

Una advertencia del primer ejemplo es que cada vez que agregas tu clip personalizado, necesitas asignar un componente Luz a cada uno de tus clips, lo que puede ser tedioso si tienes muchos. Puedes resolver esto utilizando un objeto enlazadoa una pista.

Imagen
Imagen

Una pista puede tener un objeto o un componente asociado a ella, lo que significa que cada clip de la pista puede operar directamente sobre el objeto asociado. Este es un comportamiento muy común y, de hecho, es cómo funcionan las pistas de Animación, Activación y Cinemachine .

Si desea modificar las propiedades de una luz con varios clips, puede crear una pista personalizada que solicite un componente de luz como objeto vinculado. Para crear una pista personalizada, necesitas otro script que extienda TrackAsset:

[TrackClipType(typeof(LightControlAsset))]
[TrackBindingType(typeof(Light))]
public class LightControlTrack : TrackAsset {}

Aquí hay dos atributos:

  • TrackClipType especifica qué tipo de PlayableAsset aceptará la pista. En este caso, especificará el LightControlAssetpersonalizado.
  • TrackBindingType especifica qué tipo de enlace solicitará la pista (puede ser un GameObject, un Componente o un Activo). En este caso, desea un componente ligero.

También es necesario modificar ligeramente PlayableAsset y PlayableBehaviour para que funcionen con una pista. Como referencia, he comentado las líneas que ya no necesitas.

public class LightControlAsset : PlayableAsset
{
    public LightControlBehaviour template;

    public override Playable CreatePlayable (PlayableGraph graph, GameObject owner) {
        var playable = ScriptPlayable<LightControlBehaviour>.Create(graph, template);
        return playable;
    }
}

El PlayableBehaviour ahora no necesita una variable Light. En este caso, el método ProcessFrame proporciona directamente el objeto enlazado de la pista. Todo lo que necesitas es convertir el objeto al tipo apropiado. ¡Eso es genial!

public class LightControlAsset : PlayableAsset
{
   //public ExposedReference<Light> light;
   public Color color = Color.white;
   public float intensity = 1f;

   public override Playable CreatePlayable (PlayableGraph graph, GameObject owner)
   {
       var playable = ScriptPlayable<LightControlBehaviour>.Create(graph);

       var lightControlBehaviour = playable.GetBehaviour();
       //lightControlBehaviour.light = light.Resolve(graph.GetResolver());
       lightControlBehaviour.color = color;
       lightControlBehaviour.intensity = intensity;

       return playable;
   }
}

El PlayableAsset ya no necesita contener una ExposedReference para un componente Light. La referencia será administrada por la pista y entregada directamente a PlayableBehaviour.

En nuestra línea de tiempo, podemos agregar una pista LightControl y asociarle una luz. Ahora, cada clip que agreguemos a esa pista operará en el componente Luz que está vinculado a la pista.

Si utiliza el Visualizador de gráficos para mostrar este gráfico, se verá así:

Imagen
Imagen

Como era de esperar, verás los clips en el lado derecho como 5 bloques que se alimentan en uno. Puedes pensar en la caja como si fuera la pista. Luego todo va a la Timeline: el cuadro morado.

Nota: El cuadro rosa llamado “Playable” es en realidad un mezclador de cortesía Playable que Unity crea para ti. Por eso es del mismo color que los clips. ¿Qué es un mezclador? Hablaremos de mezcladores en el siguiente ejemplo.

Ejemplo 3: Mezcla de clips con un mezclador

La Timeline admite la superposición de clips para crear fusiones o fundidos cruzados entre ellos. Los clips personalizados también admiten la fusión. Sin embargo, para habilitarlo, debes crear un mezclador que acceda a los datos de todos los clips y los combine.

Un mezclador deriva de PlayableBehaviour, al igual que el LightControlBehaviour que usaste anteriormente. De hecho, todavía utilizas la función ProcessFrame . La diferencia clave es que este Playable se declara explícitamente como un mezclador mediante el script de pista, al anular la función CreateTrackMixer. El script LightControlTrack ahora se ve así:

[TrackClipType(typeof(LightControlAsset))]
[TrackBindingType(typeof(Light))]
public class LightControlTrack : TrackAsset
{
    public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount) {
        return ScriptPlayable<LightControlMixerBehaviour>.Create(graph, inputCount);
    }
}

Cuando se crea el gráfico reproducible para esta pista, también se creará un nuevo comportamiento (el mezclador) y lo conectará a todos los clips de la pista.

También desea trasladar la lógica de PlayableBehaviour al mezclador. De esta forma, el PlayableBehaviour ahora se verá bastante vacío:

public class LightControlBehaviour : PlayableBehaviour
{
    public Color color = Color.white;
    public float intensity = 1f;
}

Básicamente solo contiene los datos que provendrán del PlayableAsset en tiempo de ejecución. El mezclador, por otro lado, tendrá toda la lógica en su función ProcessFrame :

public class LightControlMixerBehaviour : PlayableBehaviour
{
    // NOTE: This function is called at runtime and edit time.  Keep that in mind when setting the values of properties.
    public override void ProcessFrame(Playable playable, FrameData info, object playerData)
    {
        Light trackBinding = playerData as Light;
        float finalIntensity = 0f;
        Color finalColor = Color.black;

        if (!trackBinding)
            return;

        int inputCount = playable.GetInputCount (); //get the number of all clips on this track

        for (int i = 0; i < inputCount; i++)
        {
            float inputWeight = playable.GetInputWeight(i);
            ScriptPlayable<LightControlBehaviour> inputPlayable = (ScriptPlayable<LightControlBehaviour>)playable.GetInput(i);
            LightControlBehaviour input = inputPlayable.GetBehaviour();

            // Use the above variables to process each frame of this playable.
            finalIntensity += input.intensity * inputWeight;
            finalColor += input.color * inputWeight;
        }

        //assign the result to the bound object
        trackBinding.intensity = finalIntensity;
        trackBinding.color = finalColor;
    }
}

Los mezcladores tienen acceso a todos los clips presentes en una pista. En este caso, necesitas leer los valores de intensidad y color de todos los clips que participan actualmente en la mezcla, por lo que debes iterarlos con un bucle for. En cada ciclo, accede a las entradas (GetInput(i)) y construye los valores finales usando el peso de cada clip (GetInputWeight(i)) para obtener cuánto contribuye ese clip a la mezcla.

Entonces, imagina que tienes dos clips fusionándose: uno contribuye con rojo y el otro con blanco. Cuando la mezcla está en un cuarto de su recorrido, el color es 0,25 * Color.red + 0,75 * Color.white, lo que da como resultado un rojo ligeramente descolorido.

Una vez finalizado el bucle, se aplican los totales al componente Light enlazado. Esto te permite crear algo como esto:

Imagen

Ahora puedes ver que el cuadro rojo es exactamente el mezclador Playable que programaste y sobre el que ahora tienes control total. Esto contrasta con el Ejemplo 2 anterior, donde el mezclador era el predeterminado proporcionado por Unity.

Observe también que debido a que el gráfico está en el medio de una mezcla, los cuadros verdes 2 y 3 tienen una línea brillante que los conecta al mezclador, lo que indica que su peso es algo así como 0,5 cada uno.

Tenga en cuenta que siempre que implemente mezclas en un mezclador, usted deberá decidir cuál es la lógica. Mezclar dos colores es fácil, pero ¿qué sucede cuando mezclas (un ejemplo curioso) dos clips que representan diferentes estados de IA en tu sistema de IA? ¿Dos líneas de diálogo en tu interfaz de usuario? ¿Cómo combinar dos poses estáticas en una animación stop-motion? Quizás tu combinación no sea continua, sino "escalonada" (de modo que las poses se transforman entre sí, pero en incrementos discretos: 0, 0.25, 0.5, 0.75, 1).

¡Con este poderoso sistema a tu disposición, los escenarios son emocionantes e infinitos!

Ejemplo 4: Animación de clips personalizados

Como paso final de esta guía, volvamos al ejemplo anterior e implementemos una forma diferente de mover datos utilizando algo a lo que llamamos "plantillas". Una gran ventaja de este patrón es que permite crear fotogramas clave de las propiedades de la plantilla, lo que hace posible crear animaciones para clips personalizados directamente en la Timeline.

En el ejemplo anterior, tenías una referencia al componente Luz, el color y la intensidad tanto en PlayableAsset como en PlayableBehaviour. Los datos se configuraron en PlayableAsset en el Inspector y luego, en tiempo de ejecución, se copiaron en PlayableBehaviour al crear el gráfico.

Esta es una forma válida de hacer las cosas, pero duplica los datos que luego deben mantenerse sincronizados en todo momento. Esto puede conducir fácilmente a errores. En su lugar, puedes utilizar el concepto de una “plantilla” PlayableBehaviour , creando una referencia a ella en PlayableAsset. Por lo tanto, primero, reescribe tu LightControlAsset de esta manera:

public class LightControlAsset : PlayableAsset
{
    public LightControlBehaviour template;

    public override Playable CreatePlayable (PlayableGraph graph, GameObject owner) {
        var playable = ScriptPlayable<LightControlBehaviour>.Create(graph, template);
        return playable;
    }
}

El LightControlAsset ahora solo tiene una referencia al LightControlBehaviour en lugar de a los valores en sí. ¡Es incluso menos código que antes!

Deje LightControlBehaviour sin cambios:

[System.Serializable]
public class LightControlBehaviour : PlayableBehaviour
{
    public Color color = Color.white;
    public float intensity = 1f;
}

La referencia a la plantilla ahora produce automáticamente este Inspector cuando selecciona el clip en la Timeline:

Imagen

Una vez que tengas este script en su lugar, estarás listo para animar. Tenga en cuenta que si crea un nuevo clip, verá un botón rojo circular en el encabezado de la pista. Esto significa que ahora es posible crear fotogramas clave en el clip sin necesidad de agregarle un animador. Simplemente haga clic en el botón rojo, seleccione el clip, coloque el cabezal de reproducción donde desea crear una clave y cambie el valor de esa propiedad.

También puede ampliar la vista Curvas haciendo clic en el botón del cuadro blanco para ver las curvas creadas por los fotogramas clave:

Imagen
Imagen

Hay una ventaja adicional: puedes hacer doble clic en el clip de la Timeline y Unity abrirá el panel Animación y lo vinculará a la Timeline. Notarás que están vinculados cuando aparece este botón:

Imagen

Cuando esto sucede, puedes desplazarte tanto por la Timeline como por la ventana de animación y los cabezales de reproducción se mantendrán sincronizados, por lo que tendrás control total sobre tus fotogramas clave. Ahora puedes modificar tu animación en la ventana Animación para trabajar en los fotogramas clave en un entorno más cómodo:

Imagen

En esta vista, puedes utilizar todo el poder de las curvas de animación y la hoja de trabajo para refinar realmente las animaciones de tus clips personalizados.

Nota: Cuando animas cosas de esta manera, estás creando clips de animación. Puedes encontrarlos en el recurso Timeline :

Imagen
En conclusión

Espero que esta publicación haya sido una valiosa introducción a las infinitas posibilidades que Timeline puede ofrecer cuando lo llevas al siguiente nivel con scripts.

¡Envíame un mensaje en Twitter con tus preguntas, comentarios y creaciones de tu Timeline !