Loops
Primera parte · After Effects · 3D · Expresiones

Loops de
motion design

Cinco piezas en bucle animadas paso a paso en After Effects.
Loops
Recorrido

Del empalme al acabado, en cinco piezas completas

Antes de animar

Cinco prácticas

Cierre

Loops
Antes de animar

El empalme

Un loop pasa del último frame al primero sin pausa. Ese paso es el empalme, y en un buen loop no se nota: en inglés se le llama seamless loop.

Lo que gira, oscila o se desplaza solo vuelve a su posición inicial si completa un número entero de ciclos en la duración del loop.

◆ Pon 1.5 ciclos y repasa el empalme

Tres movimientos, un loop de 2 s

Frame 00 de 48

Loops
Técnica

Cinco formas de cerrar un loop

  • Repetir los keyframes (fotogramas clave en After Effects en español)

    Para lo que ya está animado a mano: el último keyframe debe ser igual al primero, y la expresión repite el tramo sin fin. Con «pingpong» lo recorre de ida y de vuelta; con «offset», suma cada vuelta a la anterior.

    loopOut("cycle") Tipografía que se transforma
  • Ciclos enteros

    Para lo que se mueve por fórmula, como un seno o un giro: n, las vueltas en el loop, tiene que ser entero.

    Math.cos(2 * Math.PI * n * time / T) Péndulos en ola
  • Módulo

    Un desplazamiento que vuelve a cero cada vez que el patrón se repite. Cierra si el loop recorre un número entero de patrones.

    (time * vel) % ancho Un día en la casa de la alberca
  • Escala exponencial

    El zoom que no termina: cada ciclo multiplica la escala por lo mismo y la escena de dentro ocupa el lugar de la de fuera. Con escala lineal, el acercamiento parece frenar.

    100 * Math.pow(k, time / T) Zoom infinito
  • Circuito

    Una pieza recorre un sistema y vuelve al punto de partida en el último frame. Ninguna fórmula lo garantiza; sale del timing chart.

    frame final = frame 0 Reacción en cadena
Loops
Método

Cómo se trabaja un loop

  1. 1

    Planear

    Un style frame, un frame terminado, con la paleta y el acabado definitivos y un timing chart: qué pasa en cada segundo y cómo cierra.

  2. 2

    Bloquear

    Las poses clave, con linear keyframes y sin pulir. Sirve para revisar el ritmo del conjunto antes de invertir en detalle.

  3. 3

    Pulir

    Curvas, arcos, estirar y encoger, anticipación y acción complementaria: el tramo donde el movimiento gana peso.

  4. 4

    Acabar

    Textura, desenfoque de movimiento y color. Al final, revisar el empalme y exportar el ciclo exacto.

En las cinco prácticas, un script dibuja la escena y la deja quieta. La animación se construye paso a paso, con keyframes y, donde los keyframes no son suficientes, con una expresión.

Práctica 1
Práctica 1 · 15 s

Péndulos en ola

ArcosSincronizaciónCloningOffset & Delay

Doce péndulos cuelgan de una barra y cada uno oscila un poco más rápido que el anterior. Se desfasan, dibujan una ola, se ordenan en grupos y a los 15 s vuelven a alinearse.

Nadie anima la ola: aparece sola del desfase entre frecuencias.

◆ Apaga una parte y mira qué se pierde
000 · 0.0 s

Radiografía

Paleta

Péndulos

Oscilaciones del más largo

Práctica 1
Práctica 1 · Planeación

Lo que entrega el script

La escena completa y quieta: doce péndulos en vertical, la luz y una cámara fija. Cada péndulo cuelga de un nulo «Eje», y ese nulo es lo único que se anima.

Cámarafija, a 40° de la fila; gira en el paso 10
Luzpuntual, arriba; proyecta sombras
Ambienteque nada quede en negro
Eje 1 … 12nulos 3D en el punto de giro: aquí va la Rotación Z
Hilohijo de su eje
Esferahija de su eje; mira siempre a la cámara
Barra y postesproyectan sombra
Pisorecibe sombras y luces

La regla que cierra el loop

El péndulo número n hace 5 + n vaivenes en 15 s. Mientras ese número sea entero, todos terminan donde empezaron.

PénduloVaivenes en 15 sFrames por vaivénHilo
1660520 px
2751.43446 px
3845390 px
4940347 px
51036312 px
61132.73284 px
71230260 px
81327.69240 px
91425.71223 px
101524208 px
111622.5195 px
121721.18184 px

Seis de los doce vaivenes no duran un número entero de frames: con keyframes a mano no cerrarían. Para los doce hace falta una expresión.

Práctica 1
Práctica 1 · After Effects · 1 de 3

Un péndulo a mano

  1. Ejecutar el script. Archivo › Secuencias de comandos › Ejecutar archivo de secuencias de comandos File › Scripts › Run Script File y elegir 01-pendulos-en-ola.jsx. Se abre «Péndulos en ola»: 1080 × 1080, 15 s a 24 fps, con los doce péndulos quietos.
  2. Contar en frames. Ctrl+clic en el tiempo actual, arriba a la izquierda de la línea de tiempo. Se lee 0 en lugar de 0:00:00:00; el último frame es el 359.
  3. Tres keyframes. Seleccionar «Eje 1» y pulsar R. En Rotación Z, activar el cronómetro en el frame 0 con 22; en el 30, −22; en el 60, 22. El primer péndulo va y vuelve una vez.
  4. Frenar en los extremos. Seleccionar los tres keyframes y pulsar F9 Easy Ease. Los linear keyframes se vuelven Easy Ease y el péndulo se frena antes de dar la vuelta.
  5. Repetirlo. Alt+clic en el cronómetro de Rotación Z y escribir la expresión de la derecha. Seis vaivenes en 15 s; el frame 359 enlaza con el 0.
01-pendulos-en-ola.jsx
// 01 · Péndulos en ola · Loops de motion design
// Dibuja la escena y la deja quieta: doce péndulos en 3D colgados de una barra,
// una luz que proyecta sombras, el piso que las recibe y una cámara fija. Cada
// péndulo cuelga de un nulo «Eje», que es lo que se anima en la guía.
// No crea keyframes ni expresiones.
// Archivo › Secuencias de comandos › Ejecutar archivo de secuencias de comandos
(function () {
  var T = 15;          // duración del loop, en segundos
  var N = 12;          // número de péndulos
  var N0 = 6;          // vaivenes del péndulo más largo en el loop: decide el largo de los hilos
  var LMAX = 520;      // largo del hilo más largo, en píxeles
  var SEP = 62;        // separación entre péndulos, en profundidad
  var EJE_Y = 230;     // altura de la barra
  var PISO_Y = 900;    // altura del piso
  var R = 34;          // radio de las esferas
  var COLORES = ["#E2467F", "#7D5FD0", "#3FB7C6"];   // primer péndulo, el de en medio y el último
  var FONDO = "#EFE5D8", PISO = "#E5D5C2", TINTA = "#3B2F3A";

  app.beginUndoGroup("Péndulos en ola");

  var comp = app.project.items.addComp("Péndulos en ola", 1080, 1080, 1, T, 24);
  comp.bgColor = rgb(FONDO);
  try { comp.renderer = "ADBE Advanced 3d"; } catch (e) {}   // 3D clásico: admite sombras

  function rgb(h) {
    return [parseInt(h.substr(1, 2), 16) / 255, parseInt(h.substr(3, 2), 16) / 255, parseInt(h.substr(5, 2), 16) / 255];
  }
  function mezcla(a, b, t) {
    return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];
  }
  function tr(capa) { return capa.property("ADBE Transform Group"); }

  // Cada forma va en su propio grupo. Se vuelve a pedir el grupo por su índice
  // después de cada addProperty: las referencias anteriores dejan de valer.
  function nuevoGrupo(capa, nombre) {
    var raiz = capa.property("ADBE Root Vectors Group");
    var i = raiz.addProperty("ADBE Vector Group").propertyIndex;
    raiz.property(i).name = nombre;
    return function () { return capa.property("ADBE Root Vectors Group").property(i).property("ADBE Vectors Group"); };
  }
  function circulo(capa, nombre, d, pos, color, opacidad) {
    var g = nuevoGrupo(capa, nombre);
    g().addProperty("ADBE Vector Shape - Ellipse");
    g().property(1).property("ADBE Vector Ellipse Size").setValue([d, d]);
    g().property(1).property("ADBE Vector Ellipse Position").setValue(pos);
    g().addProperty("ADBE Vector Graphic - Fill");
    g().property(2).property("ADBE Vector Fill Color").setValue(color);
    g().property(2).property("ADBE Vector Fill Opacity").setValue(opacidad);
  }
  function rectangulo(capa, nombre, tam, color, redondeo) {
    var g = nuevoGrupo(capa, nombre);
    g().addProperty("ADBE Vector Shape - Rect");
    g().property(1).property("ADBE Vector Rect Size").setValue(tam);
    g().property(1).property("ADBE Vector Rect Roundness").setValue(redondeo);
    g().addProperty("ADBE Vector Graphic - Fill");
    g().property(2).property("ADBE Vector Fill Color").setValue(color);
  }
  function linea(capa, nombre, largo, color, grosor) {
    var g = nuevoGrupo(capa, nombre);
    g().addProperty("ADBE Vector Shape - Group");
    var forma = new Shape();
    forma.vertices = [[0, 0], [0, largo]];
    forma.closed = false;
    g().property(1).property("ADBE Vector Shape").setValue(forma);
    g().addProperty("ADBE Vector Graphic - Stroke");
    g().property(2).property("ADBE Vector Stroke Color").setValue(color);
    g().property(2).property("ADBE Vector Stroke Width").setValue(grosor);
    g().property(2).property("ADBE Vector Stroke Line Cap").setValue(2);
  }
  // Una propiedad de un grupo, por el final de su nombre interno
  function buscar(grupo, fin) {
    for (var i = 1; i <= grupo.numProperties; i++) {
      var m = grupo.property(i).matchName;
      if (m.substr(m.length - fin.length) === fin) return grupo.property(i);
    }
    return null;
  }
  function material(capa, proyecta, recibe, luces) {
    var m = capa.property("ADBE Material Options Group");
    m.property("ADBE Casts Shadows").setValue(proyecta ? 1 : 0);
    m.property("ADBE Accepts Shadows").setValue(recibe ? 1 : 0);
    m.property("ADBE Accepts Lights").setValue(luces ? 1 : 0);
  }

  // ── Piso, barra y postes ──
  var piso = comp.layers.addSolid(rgb(PISO), "Piso", 4000, 4000, 1, T);
  piso.threeDLayer = true;
  tr(piso).property("ADBE Rotate X").setValue(90);
  tr(piso).property("ADBE Position").setValue([540, PISO_Y, 0]);
  material(piso, false, true, true);

  var largoFila = (N - 1) * SEP;
  var tinta = rgb(TINTA);

  var barra = comp.layers.addShape();
  barra.name = "Barra";
  rectangulo(barra, "Barra", [14, largoFila + 90], tinta, 7);
  barra.threeDLayer = true;
  tr(barra).property("ADBE Rotate X").setValue(90);
  tr(barra).property("ADBE Position").setValue([540, EJE_Y, 0]);
  material(barra, true, false, false);

  for (var p = 0; p < 2; p++) {
    var poste = comp.layers.addShape();
    poste.name = "Poste " + (p + 1);
    rectangulo(poste, "Poste", [12, PISO_Y - EJE_Y], tinta, 6);
    poste.threeDLayer = true;
    poste.autoOrient = AutoOrientType.CAMERA_OR_POINT_OF_INTEREST;
    tr(poste).property("ADBE Position").setValue([540, (EJE_Y + PISO_Y) / 2, (p ? 1 : -1) * (largoFila / 2 + 45)]);
    material(poste, true, false, false);
  }

  // ── Los péndulos: un nulo «Eje» en el punto de giro, con el hilo y la esfera de hijos ──
  var C0 = rgb(COLORES[0]), C1 = rgb(COLORES[1]), C2 = rgb(COLORES[2]);
  var blanco = [1, 1, 1];

  for (var k = 0; k < N; k++) {
    var u = k / (N - 1);
    var color = u < 0.5 ? mezcla(C0, C1, u * 2) : mezcla(C1, C2, (u - 0.5) * 2);
    var L = LMAX * N0 / (N0 + k);           // el hilo se acorta con la frecuencia
    var z = (k - (N - 1) / 2) * SEP;

    var eje = comp.layers.addNull(T);
    eje.name = "Eje " + (k + 1);
    eje.threeDLayer = true;
    tr(eje).property("ADBE Position").setValue([540, EJE_Y, z]);
    var ancla = tr(eje).property("ADBE Anchor Point").value;

    var hilo = comp.layers.addShape();
    hilo.name = "Hilo " + (k + 1);
    linea(hilo, "Hilo", L, tinta, 2.5);
    hilo.threeDLayer = true;
    hilo.parent = eje;
    tr(hilo).property("ADBE Position").setValue([ancla[0], ancla[1], ancla[2]]);
    material(hilo, true, false, false);

    var esfera = comp.layers.addShape();
    esfera.name = "Esfera " + (k + 1);
    // del frente hacia el fondo: el grupo que se añade primero queda encima
    circulo(esfera, "Brillo", R * 0.32, [-R * 0.38, -R * 0.42], blanco, 85);
    circulo(esfera, "Luz", R * 0.95, [-R * 0.26, -R * 0.3], blanco, 28);
    circulo(esfera, "Base", R * 2, [0, 0], color, 100);
    esfera.threeDLayer = true;
    esfera.parent = eje;
    tr(esfera).property("ADBE Position").setValue([ancla[0], ancla[1] + L, ancla[2]]);
    esfera.autoOrient = AutoOrientType.CAMERA_OR_POINT_OF_INTEREST;
    material(esfera, true, false, false);
  }

  // ── Luz: la que proyecta las sombras, y un ambiente que no deja nada en negro ──
  var luz = comp.layers.addLight("Luz", [540, 540]);
  luz.lightType = LightType.POINT;
  tr(luz).property("ADBE Position").setValue([540, -400, 0]);
  luz.property("ADBE Light Options Group").property("ADBE Light Intensity").setValue(35);
  // «Proyecta sombras» se busca por el final de su nombre interno: Adobe no lo
  // publica para las luces. El grupo se vuelve a pedir después de cada cambio.
  var sombras = buscar(luz.property("ADBE Light Options Group"), "Casts Shadows");
  if (sombras) {
    sombras.setValue(1);
    luz.property("ADBE Light Options Group").property("ADBE Light Shadow Darkness").setValue(32);
    luz.property("ADBE Light Options Group").property("ADBE Light Shadow Diffusion").setValue(30);
  } else {
    alert("La luz quedó sin sombras: en «Luz» › Opciones de luz, activar Proyecta sombras.");
  }

  var amb = comp.layers.addLight("Ambiente", [540, 540]);
  amb.lightType = LightType.AMBIENT;
  amb.property("ADBE Light Options Group").property("ADBE Light Intensity").setValue(72);

  // ── Cámara fija: mira al centro de la fila desde 40° a un lado y 16° arriba ──
  // En la guía se vincula a un nulo en su punto de interés para que gire.
  var POI = [540, 600, 0], D = 1700;
  var a = 40 * Math.PI / 180, b = 16 * Math.PI / 180;
  var cam = comp.layers.addCamera("Cámara", [540, 540]);
  cam.property("ADBE Camera Options Group").property("ADBE Camera Zoom").setValue(1500);
  tr(cam).property("ADBE Anchor Point").setValue(POI);   // en una cámara, el punto de interés
  tr(cam).property("ADBE Position").setValue([
    POI[0] - D * Math.cos(b) * Math.sin(a),
    POI[1] - D * Math.sin(b),
    POI[2] - D * Math.cos(b) * Math.cos(a)
  ]);

  comp.openInViewer();
  app.endUndoGroup();
})();
Expresión · Rotación Z de «Eje 1»
// Repite el tramo entre el primer y el último keyframe
loopOut("cycle")

Cierra porque el tramo dura 60 frames y cabe seis veces en los 360 del loop, y porque el último keyframe vale lo mismo que el primero.

Práctica 1
Práctica 1 · After Effects · 2 de 3

La ola y la cámara

  1. Cambiar los keyframes por una expresión. En la Rotación Z de «Eje 1», sustituir loopOut por la expresión de la derecha y borrar los tres keyframes. El péndulo se mueve igual: el coseno dibuja casi la misma curva que los keyframes.
  2. Copiarla a los otros once. Con Rotación Z seleccionada, Editar › Copy Expression Only. Seleccionar de «Eje 2» a «Eje 12» con Ctrl+clic y pulsar Ctrl+V. Cada eje lee su número en su nombre y oscila un poco más rápido que el anterior: aparece la ola.
  3. Revisar el empalme. Previsualizar con la barra espaciadora; luego comparar el 359 (Fin) con el 0 (Inicio). A los 7.5 s, dos grupos; en el 359, los doce casi alineados, como en el 0.
  4. Un nulo para la cámara. Capa › Nuevo › Objeto nulo Layer › New › Null Object; llamarlo «Órbita», activar su interruptor 3D y darle Posición 540, 600, 0. En «Cámara», columna Parent & Link: elegir «Órbita». Nada se mueve: el nulo está en el punto al que mira la cámara.
  5. La órbita. En «Órbita», Rotación Y: −10 en el 0 y 10 en el 180; F9 en las dos y, con Alt+clic, loopOut("pingpong"). La cámara va y vuelve; en el 359 llega casi a −10, donde empezó.
Expresión · Rotación Z de cada «Eje»
// El número del péndulo sale del nombre de la capa
var num = parseInt(thisLayer.name.split(" ")[1]);   // «Eje 4» → 4
var n = 5 + num;    // vaivenes en 15 s: el 1 hace 6; el 12, 17
var A = 22;         // grados hacia cada lado
A * Math.cos(2 * Math.PI * n * time / 15)

Con n entero, cada péndulo termina en el ángulo en que empezó. En el tiempo 0 el coseno vale 1: arranca en 22°, como los keyframes del primero. pingpong recorre el tramo de ida y de vuelta: 180 frames cada sentido.

Práctica 1
Práctica 1 · After Effects · 3 de 3

Pulir y personalizar

  1. Desenfoque de movimiento. Activar su interruptor en las doce esferas y el botón de la línea de tiempo Motion Blur. Las esferas se arrastran un poco en el tramo rápido del vaivén.
  2. Volumen en una esfera. Desplegar Esfera 1 › Contenido › Base Contents › Base; Agregar › Relleno de degradado Add › Gradient Fill, tipo Radial, con punto inicial −12, −14 y punto final 30, 30 Start Point · End Point; borrar el «Relleno 1» de ese grupo. La esfera tiene luz de un lado y sombra del otro.
  3. Copiar el volumen. Seleccionar el relleno de degradado, Ctrl+C; seleccionar «Base» en otra esfera, Ctrl+V, y borrar su relleno plano. El color de cada una se cambia con Editar degradado. Las doce, con volumen y con su propio color.
  4. Profundidad de campo. Doble clic en «Cámara» › Activar profundidad de campo Enable Depth of Field; Distancia de enfoque 1700 y Apertura 60. Los péndulos de los extremos se desenfocan un poco.
  5. Grano. Capa › Nuevo › Capa de ajuste Adjustment Layer, encima de todo; Efecto › Ruido y granulado › Ruido Noise & Grain › Noise: 4 % y sin Use Color Noise. Una textura fina que cambia en cada frame.
  6. Más amplitud. En la expresión de «Eje 1», A = 30; copiarla otra vez a los once. Los péndulos abren más y el loop sigue cerrando: el ángulo no cambia los vaivenes.
  7. Paleta propia. El color del «Piso» con Ctrl+Mayús+Y Solid Settings, el del fondo con Ctrl+K Composition Settings y el degradado de cada esfera. La misma ola, con otra paleta.
  8. Exportar. Composición › Agregar a la cola de Adobe Media Encoder Add to Adobe Media Encoder Queue Ctrl+Alt+M: H.264 para video, o GIF animado. 360 frames; al repetirse, el empalme no se ve.
Práctica 2
Práctica 2 · 12 s

Reacción en cadena

AnticipaciónEstirar y encogerArcosAcción complementariaOffset & Delay

Una bola sube por un tubo, rueda sobre cinco teclas que se hunden a su paso, rebota en un trampolín y vuelve al tubo por otra rampa. El loop cierra porque termina donde empezó.

En bloqueo la máquina ya funciona; el pulido es lo que le da peso.

◆ Apaga «Pulido» para ver el bloqueo
000 · 0.00 s

Radiografía

Ver

Práctica 2
Práctica 2 · Planeación

Timing chart

Los trece keyframes del bloqueo, a 24 fps. Entre uno y otro, lo que se trabaja al pulir.

FrameSegundoQué pasaSe pule
00.00Se carga en el fondo del tuboAnticipación
100.42Sube por el tuboEasing · Estirar
582.42Sale disparada por arribaArcos
642.67Lo más alto; empieza a caerArcos
723.00Cae en la primera tecla y ruedaEncoger · Easing
1446.00Deja la rampa AArcos
1556.46Toca la membranaEncoger
FrameSegundoQué pasaSe pule
1586.58Lo más hondo de la membranaAnticipación
1626.75Sale disparadaArcos
1767.33Lo más alto del vueloArcos
1928.00Cae en la rampa B y ruedaEncoger · Easing
26411.00Llega al tuboArcos
27211.33Entra al fondo y queda quieta hasta el 287Acción compl.
Práctica 2
Práctica 2 · After Effects · 1 de 3

El bloqueo

  1. Ejecutar el script 02-reaccion-en-cadena.jsx desde Archivo › Secuencias de comandos › Ejecutar archivo de secuencias de comandos, y contar en frames (Ctrl+clic en el tiempo actual). Se abre «Reacción en cadena»: 1080 × 1080, 12 s a 24 fps. La bola espera en el fondo del tubo.
  2. El primer keyframe. Seleccionar «Bola», pulsar P y activar el cronómetro de Posición en el frame 0. Un linear keyframe en el 0, con 930, 962.
  3. Las poses. Ir a cada frame de la tabla (Alt+Mayús+J) y escribir su Posición. En el 10 se repite la del 0 y escribirla no crea keyframe: se pone con el botón central del navegador de keyframes, a la izquierda de Posición Add or Remove Keyframe at Current Time. Trece keyframes en la línea de tiempo.
  4. Linear keyframes. Clic en el nombre «Posición» para seleccionar sus keyframes; Animación › Keyframe Interpolation Ctrl+Alt+K: Temporal Interpolation y Spatial Interpolation en Linear. La trayectoria del visor se dibuja con rectas.
  5. Revisar el bloqueo. Previsualizar y comparar el 287 con el 0. La bola avanza a velocidad constante, como un ascensor. En el 287 y en el 0 está quieta en el fondo del tubo.
02-reaccion-en-cadena.jsx
// 02 · Reacción en cadena · Loops de motion design
// Dibuja la máquina y la deja quieta: un tubo, una rampa de cinco teclas, un
// trampolín y una segunda rampa que vuelve al tubo. La bola espera en el fondo
// del tubo, que es donde empieza y termina el loop.
// Cada tecla tiene el punto de anclaje en su bisagra, y la bola en su centro.
// No crea keyframes ni expresiones.
// Archivo › Secuencias de comandos › Ejecutar archivo de secuencias de comandos
(function () {
  var T = 12;               // duración del loop, en segundos
  var R = 26;               // radio de la bola
  var A0 = [860, 280], A1 = [300, 470];   // rampa A: de la boca del tubo hacia la izquierda
  var B0 = [380, 820], B1 = [880, 960];   // rampa B: del trampolín de vuelta al tubo
  var TRAMP = { x: 160, y: 720, ancho: 70 };

  app.beginUndoGroup("Reacción en cadena");

  var comp = app.project.items.addComp("Reacción en cadena", 1080, 1080, 1, T, 24);
  comp.bgColor = rgb("#E8473F");

  function rgb(h) {
    return [parseInt(h.substr(1, 2), 16) / 255, parseInt(h.substr(3, 2), 16) / 255, parseInt(h.substr(5, 2), 16) / 255];
  }
  function tr(capa) { return capa.property("ADBE Transform Group"); }
  function resta(a, b) { return [a[0] - b[0], a[1] - b[1]]; }
  function unitario(v) { var l = Math.sqrt(v[0] * v[0] + v[1] * v[1]); return [v[0] / l, v[1] / l]; }
  // la normal de una rampa que apunta hacia arriba (la Y crece hacia abajo)
  function normal(a, b) {
    var d = unitario(resta(b, a)), n = [d[1], -d[0]];
    return n[1] > 0 ? [-n[0], -n[1]] : n;
  }
  // la línea que se dibuja va 7 px por debajo de la superficie: su grosor es 14
  function bajo(p, a, b) { var n = normal(a, b); return [p[0] - n[0] * 7, p[1] - n[1] * 7]; }

  function nuevoGrupo(capa, nombre) {
    var raiz = capa.property("ADBE Root Vectors Group");
    var i = raiz.addProperty("ADBE Vector Group").propertyIndex;
    raiz.property(i).name = nombre;
    return function () { return capa.property("ADBE Root Vectors Group").property(i).property("ADBE Vectors Group"); };
  }
  function trazo(capa, nombre, puntos, color, grosor, entradas, salidas) {
    var g = nuevoGrupo(capa, nombre);
    g().addProperty("ADBE Vector Shape - Group");
    var forma = new Shape();
    forma.vertices = puntos;
    if (entradas) { forma.inTangents = entradas; forma.outTangents = salidas; }
    forma.closed = false;
    g().property(1).property("ADBE Vector Shape").setValue(forma);
    g().addProperty("ADBE Vector Graphic - Stroke");
    g().property(2).property("ADBE Vector Stroke Color").setValue(color);
    g().property(2).property("ADBE Vector Stroke Width").setValue(grosor);
    g().property(2).property("ADBE Vector Stroke Line Cap").setValue(2);
    return g;
  }
  function circulo(capa, nombre, d, pos, color, opacidad) {
    var g = nuevoGrupo(capa, nombre);
    g().addProperty("ADBE Vector Shape - Ellipse");
    g().property(1).property("ADBE Vector Ellipse Size").setValue([d, d]);
    g().property(1).property("ADBE Vector Ellipse Position").setValue(pos);
    g().addProperty("ADBE Vector Graphic - Fill");
    g().property(2).property("ADBE Vector Fill Color").setValue(color);
    g().property(2).property("ADBE Vector Fill Opacity").setValue(opacidad);
  }
  function rectangulo(capa, nombre, tam, pos, color, opacidad, redondeo) {
    var g = nuevoGrupo(capa, nombre);
    g().addProperty("ADBE Vector Shape - Rect");
    g().property(1).property("ADBE Vector Rect Size").setValue(tam);
    g().property(1).property("ADBE Vector Rect Position").setValue(pos);
    g().property(1).property("ADBE Vector Rect Roundness").setValue(redondeo);
    g().addProperty("ADBE Vector Graphic - Fill");
    g().property(2).property("ADBE Vector Fill Color").setValue(color);
    g().property(2).property("ADBE Vector Fill Opacity").setValue(opacidad);
  }
  // una capa de forma que dibuja en coordenadas de la composición
  function pieza(nombre) {
    var capa = comp.layers.addShape();
    capa.name = nombre;
    tr(capa).property("ADBE Position").setValue([0, 0]);
    return capa;
  }

  var CREMA = rgb("#F6E7D4"), CREMA_2 = rgb("#EAD3BA"), SOPORTE = rgb("#B8322C");
  var MARINO = rgb("#1F2A44"), AMARILLO = rgb("#FFC94A"), BLANCO = [1, 1, 1];

  // ── Fondo y soportes (lo de más atrás) ──
  comp.layers.addSolid(rgb("#E8473F"), "Fondo", 1080, 1080, 1, T);

  var soportes = pieza("Soportes");
  trazo(soportes, "Rampa A · 1", [[700, 335], [700, 1080]], SOPORTE, 8);
  trazo(soportes, "Rampa A · 2", [[480, 410], [480, 1080]], SOPORTE, 8);
  trazo(soportes, "Rampa B · 1", [[560, 875], [560, 1080]], SOPORTE, 8);
  trazo(soportes, "Rampa B · 2", [[780, 935], [780, 1080]], SOPORTE, 8);

  // ── Rampa B, de una pieza ──
  var rampaB = pieza("Rampa B");
  trazo(rampaB, "Rampa", [bajo(B0, B0, B1), bajo(B1, B0, B1)], CREMA, 14);

  // ── Rampa A: cinco teclas; la capa está en la bisagra, así que la Rotación las hunde ──
  for (var i = 0; i < 5; i++) {
    var p0 = [A0[0] + (A1[0] - A0[0]) * i / 5, A0[1] + (A1[1] - A0[1]) * i / 5];
    var p1 = [A0[0] + (A1[0] - A0[0]) * (i + 1) / 5, A0[1] + (A1[1] - A0[1]) * (i + 1) / 5];
    var bisagra = bajo(p0, A0, A1), fin = bajo(p1, A0, A1);
    var v = resta(fin, bisagra), largo = Math.sqrt(v[0] * v[0] + v[1] * v[1]), k = (largo - 6) / largo;
    var t = comp.layers.addShape();
    t.name = "Tecla " + (i + 1);
    tr(t).property("ADBE Position").setValue(bisagra);
    trazo(t, "Tecla", [[0, 0], [v[0] * k, v[1] * k]], i % 2 ? CREMA_2 : CREMA, 14);
  }

  // ── Trampolín: postes y membrana ──
  var postes = pieza("Trampolín");
  trazo(postes, "Poste izquierdo", [[TRAMP.x - TRAMP.ancho, TRAMP.y], [TRAMP.x - TRAMP.ancho, TRAMP.y + 100]], MARINO, 10);
  trazo(postes, "Poste derecho", [[TRAMP.x + TRAMP.ancho, TRAMP.y], [TRAMP.x + TRAMP.ancho, TRAMP.y + 100]], MARINO, 10);
  trazo(postes, "Base", [[TRAMP.x - TRAMP.ancho - 22, TRAMP.y + 100], [TRAMP.x + TRAMP.ancho + 22, TRAMP.y + 100]], MARINO, 10);

  // La membrana tiene un vértice en medio, con tangentes horizontales: al bajarlo, se curva.
  var membrana = pieza("Membrana");
  var m = TRAMP.ancho * 0.55;
  trazo(membrana, "Membrana", [[TRAMP.x - TRAMP.ancho, TRAMP.y], [TRAMP.x, TRAMP.y], [TRAMP.x + TRAMP.ancho, TRAMP.y]], MARINO, 8,
    [[0, 0], [-m, 0], [0, 0]], [[0, 0], [m, 0], [0, 0]]);

  // ── La bola, quieta en el fondo del tubo ──
  var bola = comp.layers.addShape();
  bola.name = "Bola";
  circulo(bola, "Brillo", R * 0.34, [-R * 0.38, -R * 0.42], BLANCO, 85);
  circulo(bola, "Luz", R * 0.95, [-R * 0.26, -R * 0.3], BLANCO, 26);
  // La franja es lo único que rueda: si girara la capa entera, al aplastarla
  // con la Escala la deformación giraría con ella.
  var franja = trazo(bola, "Franja", [[-18, -14], [18, -14]], BLANCO, 5);
  franja().property(2).property("ADBE Vector Stroke Opacity").setValue(55);
  circulo(bola, "Base", R * 2, [0, 0], AMARILLO, 100);
  tr(bola).property("ADBE Position").setValue([930, 962]);

  // ── El tubo, delante de la bola para que se vea dentro ──
  var tubo = pieza("Tubo");
  trazo(tubo, "Pared izquierda", [[893, 140], [893, 912]], CREMA, 7);
  trazo(tubo, "Pared derecha y fondo", [[967, 140], [967, 995], [878, 995]], CREMA, 7);
  trazo(tubo, "Reflejo", [[905, 170], [905, 420]], BLANCO, 4);
  rectangulo(tubo, "Vidrio", [74, 855], [930, 567], CREMA, 14, 0);

  comp.openInViewer();
  app.endUndoGroup();
})();
Keyframes del bloqueo · Posición de «Bola»
Frame        Posición          Frame        Posición
     0       930, 962               158       160, 734
    10       930, 962               162       160, 694
    58       930, 150               176       300, 560
    64       895, 105               192       407, 801
    72       832, 262               264       867, 929
   144       302, 442               272       930, 962
   155       160, 694

Del 272 al 287 no hay más keyframes: la bola se queda donde estaba en el 0, y por eso el empalme no salta.

Práctica 2
Práctica 2 · After Effects · 2 de 3

La máquina responde

  1. La primera tecla. Seleccionar «Tecla 1», pulsar R y poner los seis keyframes de Rotación de la tabla (el del 82 repite el del 74: navegador de keyframes); seleccionarlos y F9. Se hunde cuando cae la bola, aguanta y rebota dos veces.
  2. Las otras cuatro. Copiar los seis keyframes. Seleccionar «Tecla 2», ir al frame 84 y Ctrl+V; «Tecla 3» en el 99, «Tecla 4» en el 114 y «Tecla 5» en el 129. Cada tecla se hunde justo cuando la bola pasa por su bisagra.
  3. La membrana cede. En «Membrana» › Contenido › Membrana › Trazado 1, cronómetro de Trazado en el 155. En el 158, con la vista al 100 %, seleccionar el vértice del centro y bajarlo con Mayús+↓ cuatro veces. La membrana se curva 40 px bajo la bola.
  4. Y devuelve la bola. Copiar el keyframe del 155 y pegarlo en el 162. En el 165, el vértice 10 px arriba de la recta; en el 168, otra vez el del 155. F9 en todos. Cede, empuja y vibra antes de quedar quieta.
  5. La bola rueda. «Bola» › Contenido › Franja › Transformar: Franja › Rotación: los cuatro keyframes de la tabla; el del 192 repite el del 144: navegador de keyframes. La franja gira en las rampas y en el 264 vuelve al ángulo con que empezó.
Keyframes · Tecla 1 · Franja
Tecla 1 › Rotación
   72 → 0       74 → −8      82 → −8
   86 → 3       90 → −1.2    94 → 0

Bola › Contenido › Franja › Transformar: Franja › Rotación
   72 → 0      144 → −1170   192 → −1170   264 → 0

Gira la franja y no la capa, para que la Escala aplaste la bola siempre en vertical. −1170° son tres vueltas y cuarto: lo que avanza la bola en la rampa A, entre su radio.

Práctica 2
Práctica 2 · After Effects · 3 de 3

Pulir y personalizar

  1. Separar dimensiones. Clic derecho en Posición de «Bola» › Separar dimensiones Separate Dimensions. Aparecen Posición X y Posición Y, cada una con sus trece keyframes.
  2. Que suba acelerando. En Posición Y, el keyframe del 10: Ctrl+Mayús+F9 Easy Ease Out. La bola arranca despacio y sale disparada del tubo.
  3. Que baje acelerando. En X, en Y y en la rotación de la franja, los keyframes del 72 y del 192, donde empieza cada rampa: Ctrl+Mayús+F9. Gana velocidad cuesta abajo y la franja gira con ella.
  4. Arcos. En Posición Y, F9 en las cimas de los dos vuelos, el 64 y el 176; los keyframes de X quedan en linear. Revisarlo en el editor de gráficos Mayús+F3. En el visor, la trayectoria de cada vuelo se curva.
  5. Estirar y encoger. En Escala, quitar el vínculo de proporción. En cada impacto, cuatro keyframes: 100, 100 dos frames antes; 120, 80 al tocar; 95, 105 dos después y 100, 100 cinco después. Impactos en el 72, el 158 (más fuerte: 130, 70) y el 192; los 100, 100 del 156 y del 190, con el navegador de keyframes. La bola se aplasta al caer y se recupera con un rebote.
  6. Anticipación. En Escala: el 2 en 100, 100, con el navegador; el 8 en 115, 85; el 12 en 92, 110 y el 16 en 100, 100. Se aplasta antes de subir y se estira al salir.
  7. Acción complementaria. En Posición Y, después del 272: 946 en el 276, 962 en el 280, 956 en el 283 y 962 en el 286. Dos rebotes cada vez más bajos antes de quedarse quieta.
  8. Acabado y empalme. Efecto › Perspectiva › Sombra paralela Drop Shadow en la bola y en las piezas; activar el desenfoque de movimiento. Comparar el 287 con el 0. En los dos, la bola está quieta en el fondo del tubo.
Práctica 3
Práctica 3 · 12 s

Un día en la casa de la alberca

ArcosAcción secundariaValue changeOffset & DelayParallax

Una casa junto a una alberca vive un día entero en 12 s: el sol y la luna giran, el cielo cambia de color, la sombra se estira y las ventanas se encienden una tras otra.

Cada hora dura 12 frames.

◆ Recorre el día con la barra
06:00 · 000

Radiografía

Práctica 3
Práctica 3 · Planeación

Lo que entrega el script

La escena de día, quieta. Lo que solo se ve de noche ya está en su sitio, en opacidad 0.

Ventana 1 … 5la luz cálida de cada ventana
Estrellas y Lunaencima de «Noche»; la luna, a ras del horizonte
Nochesólido en Multiplicar
Palmera 1 y 2el ancla, en la base del tronco
Hojashijas de su tronco
Reflejos del aguarayas cada 80 px, recortadas por una máscara
Alberca, Casa y Sombrala casa, con sus cinco vidrios
Nubes y Montañasdos capas de nubes, a distinta altura
Sol, Brillo y Cieloel sol, a ras del horizonte, donde amanece

Un día en 12 segundos

Cada hora dura 12 frames: frame = (hora − 6) × 12. La tabla marca los momentos en que caen los keyframes.

HoraFrameQué pasa
6:000Amanece: el sol sale por la izquierda
7:3018Pleno día: el cielo ya es azul
12:0072Mediodía: el sol arriba y la sombra recta
17:00132Empieza el atardecer
18:00144Se oculta el sol, sale la luna y se enciende la primera ventana
19:36163Noche cerrada
0:00216Medianoche: la luna arriba
3:50262Se apaga la primera ventana
Práctica 3
Práctica 3 · After Effects · 1 de 3

El sol y la luna

  1. Ejecutar el script 03-casa-de-la-alberca.jsx y contar en frames. Se abre «Un día en la casa de la alberca»: 12 s, de día, con el sol a ras del horizonte.
  2. Un nulo en el centro del arco. Capa › Nuevo › Objeto nulo; llamarlo «Giro» y darle Posición 540, 640. El nulo queda sobre la casa, a la altura del horizonte.
  3. Vincular los astros. En «Sol» y en «Luna», columna Parent & Link: elegir «Giro». Nada se mueve todavía.
  4. Medio día de giro. En «Giro», R: 0° en el frame 0 y 180° en el 144. Con Alt+clic en el cronómetro, la expresión de la derecha. El sol sube por la izquierda, pasa por lo alto en el 72 y se oculta en el 144; luego sigue el círculo por debajo.
  5. La luna, solo de noche. En «Luna», T: 0 en el 153, 100 en el 163, 100 en el 269 y 0 en el 279. El del 269 repite el valor anterior: se pone con el botón central del navegador de keyframes. Aparece arriba de las montañas y se desvanece antes de tocarlas.
03-casa-de-la-alberca.jsx
// 03 · Un día en la casa de la alberca · Loops de motion design
// Dibuja la escena de día y la deja quieta: cielo, sol, nubes, montañas, la casa
// con su sombra, la alberca, dos palmeras y, en opacidad 0, lo que solo se ve de
// noche: el sólido «Noche», la luna, las estrellas y la luz de las ventanas.
// El sol espera a ras del horizonte, a la izquierda, donde amanece; la luna, a la
// derecha. Los dos están a la misma distancia del punto 540, 640.
// No crea keyframes ni expresiones.
// Archivo › Secuencias de comandos › Ejecutar archivo de secuencias de comandos
(function () {
  var T = 12;                 // un día, en segundos
  var HORIZONTE = 640;        // línea del horizonte
  var CIELO = "#8FD3E8", VIDRIO = "#3B5566", LUZ = "#FFC870";

  app.beginUndoGroup("Un día en la casa de la alberca");

  var comp = app.project.items.addComp("Un día en la casa de la alberca", 1080, 1080, 1, T, 24);
  comp.bgColor = rgb(CIELO);

  function rgb(h) {
    return [parseInt(h.substr(1, 2), 16) / 255, parseInt(h.substr(3, 2), 16) / 255, parseInt(h.substr(5, 2), 16) / 255];
  }
  function tr(capa) { return capa.property("ADBE Transform Group"); }
  function nuevoGrupo(capa, nombre) {
    var raiz = capa.property("ADBE Root Vectors Group");
    var i = raiz.addProperty("ADBE Vector Group").propertyIndex;
    raiz.property(i).name = nombre;
    return function () { return capa.property("ADBE Root Vectors Group").property(i).property("ADBE Vectors Group"); };
  }
  function relleno(g, color, opacidad) {
    g().addProperty("ADBE Vector Graphic - Fill");
    var f = g().property(g().numProperties);
    f.property("ADBE Vector Fill Color").setValue(rgb(color));
    if (opacidad != null) f.property("ADBE Vector Fill Opacity").setValue(opacidad);
  }
  function rect(capa, nombre, x0, y0, x1, y1, color, redondeo) {
    var g = nuevoGrupo(capa, nombre);
    g().addProperty("ADBE Vector Shape - Rect");
    g().property(1).property("ADBE Vector Rect Size").setValue([x1 - x0, y1 - y0]);
    g().property(1).property("ADBE Vector Rect Position").setValue([(x0 + x1) / 2, (y0 + y1) / 2]);
    if (redondeo) g().property(1).property("ADBE Vector Rect Roundness").setValue(redondeo);
    relleno(g, color);
    return g;
  }
  function elipse(capa, nombre, cx, cy, w, h, color, opacidad) {
    var g = nuevoGrupo(capa, nombre);
    g().addProperty("ADBE Vector Shape - Ellipse");
    g().property(1).property("ADBE Vector Ellipse Size").setValue([w, h]);
    g().property(1).property("ADBE Vector Ellipse Position").setValue([cx, cy]);
    relleno(g, color, opacidad);
    return g;
  }
  function poligono(capa, nombre, puntos, color) {
    var g = nuevoGrupo(capa, nombre);
    g().addProperty("ADBE Vector Shape - Group");
    var s = new Shape(); s.vertices = puntos; s.closed = true;
    g().property(1).property("ADBE Vector Shape").setValue(s);
    relleno(g, color);
    return g;
  }
  function trazo(capa, nombre, puntos, entradas, salidas, color, grosor) {
    var g = nuevoGrupo(capa, nombre);
    g().addProperty("ADBE Vector Shape - Group");
    var s = new Shape(); s.vertices = puntos; s.closed = false;
    if (entradas) { s.inTangents = entradas; s.outTangents = salidas; }
    g().property(1).property("ADBE Vector Shape").setValue(s);
    g().addProperty("ADBE Vector Graphic - Stroke");
    g().property(2).property("ADBE Vector Stroke Color").setValue(rgb(color));
    g().property(2).property("ADBE Vector Stroke Width").setValue(grosor);
    g().property(2).property("ADBE Vector Stroke Line Cap").setValue(2);
    return g;
  }
  function transformacion(g) { return g().parentProperty.property("ADBE Vector Transform Group"); }
  // una capa de forma que dibuja en coordenadas de la composición
  function pieza(nombre) {
    var capa = comp.layers.addShape();
    capa.name = nombre;
    tr(capa).property("ADBE Position").setValue([0, 0]);
    return capa;
  }
  function opacidad(capa, valor) { tr(capa).property("ADBE Opacity").setValue(valor); }

  // ── Cielo, brillo del horizonte y sol (lo de más atrás) ──
  var cielo = pieza("Cielo");
  rect(cielo, "Cielo", 0, 0, 1080, 1080, CIELO);

  var brillo = pieza("Brillo del horizonte");
  elipse(brillo, "Brillo", 540, HORIZONTE, 1500, 520, "#FFD29A", 100);
  opacidad(brillo, 0);

  var sol = comp.layers.addShape();
  sol.name = "Sol";
  elipse(sol, "Halo", 0, 0, 170, 170, "#FFE08A", 30);
  elipse(sol, "Sol", 0, 0, 110, 110, "#FFE08A", 100);
  tr(sol).property("ADBE Position").setValue([540 - 470, HORIZONTE]);   // amanece

  // ── Nubes lejanas, montañas y nubes cercanas: el parallax ──
  function nube(nombre, x, y, escala, opacidadNube) {
    var n = comp.layers.addShape();
    n.name = nombre;
    elipse(n, "a", -70 * escala, 10 * escala, 150 * escala, 70 * escala, "#FFFFFF", opacidadNube);
    elipse(n, "b", 10 * escala, -12 * escala, 150 * escala, 100 * escala, "#FFFFFF", opacidadNube);
    elipse(n, "c", 90 * escala, 12 * escala, 130 * escala, 64 * escala, "#FFFFFF", opacidadNube);
    tr(n).property("ADBE Position").setValue([x, y]);
    return n;
  }
  nube("Nubes lejanas", 90, 200, 1, 70);

  var montanas = pieza("Montañas");
  poligono(montanas, "Cercanas", [[0, 640], [0, 600], [180, 560], [330, 605], [480, 575], [640, 612], [800, 570], [960, 605], [1080, 585], [1080, 640]], "#6E93A6");
  poligono(montanas, "Lejanas", [[0, 640], [0, 560], [140, 500], [260, 545], [400, 470], [560, 540], [700, 490], [860, 555], [980, 505], [1080, 540], [1080, 640]], "#9CB8C9");

  nube("Nubes cercanas", 690, 310, 1.3, 90);

  // ── Suelo, sombra, casa y alberca ──
  var suelo = pieza("Suelo");
  rect(suelo, "Terraza", 0, HORIZONTE, 1080, 1080, "#E9D8B8");

  var sombra = pieza("Sombra de la casa");
  poligono(sombra, "Sombra", [[220, 716], [860, 716], [860, 780], [220, 780]], "#2A2F3A");
  opacidad(sombra, 35);

  var casa = pieza("Casa");
  var CENTROS = [315, 427, 540, 653, 765];
  for (var c = 0; c < CENTROS.length; c++) rect(casa, "Vidrio " + (c + 1), CENTROS[c] - 43, 548, CENTROS[c] + 43, 676, VIDRIO, 3);
  rect(casa, "Techo", 210, 494, 870, 520, "#2E3440", 4);
  rect(casa, "Zócalo", 230, 700, 850, 716, "#D9C7A8");
  rect(casa, "Muro", 250, 520, 830, 700, "#F6F1E8");

  var alberca = pieza("Alberca");
  rect(alberca, "Agua", 206, 804, 874, 926, "#3FC1D9", 6);
  rect(alberca, "Borde", 190, 790, 890, 940, "#FFFFFF", 10);

  // Los reflejos son rayas cada 80 px; la máscara los recorta al agua.
  var reflejos = pieza("Reflejos del agua");
  var gReflejos = nuevoGrupo(reflejos, "Reflejos");
  for (var fila = 0; fila < 3; fila++) {
    for (var x = 120 + (fila % 2) * 40; x < 1000; x += 80) {
      gReflejos().addProperty("ADBE Vector Shape - Group");
      var raya = new Shape(); raya.vertices = [[x, 835 + fila * 30], [x + 34, 835 + fila * 30]]; raya.closed = false;
      gReflejos().property(gReflejos().numProperties).property("ADBE Vector Shape").setValue(raya);
    }
  }
  gReflejos().addProperty("ADBE Vector Graphic - Stroke");
  var tReflejos = gReflejos().property(gReflejos().numProperties);
  tReflejos.property("ADBE Vector Stroke Color").setValue(rgb("#E8FBFF"));
  tReflejos.property("ADBE Vector Stroke Width").setValue(5);
  tReflejos.property("ADBE Vector Stroke Line Cap").setValue(2);
  tReflejos.property("ADBE Vector Stroke Opacity").setValue(75);
  var mascara = reflejos.property("ADBE Mask Parade").addProperty("ADBE Mask Atom");
  var forma = new Shape();
  forma.vertices = [[206, 804], [874, 804], [874, 926], [206, 926]]; forma.closed = true;
  mascara.property("ADBE Mask Shape").setValue(forma);

  // ── Palmeras: el tronco tiene el anclaje en la base y las hojas son sus hijas ──
  function palmera(n, base, puntos, entradas, salidas, copa) {
    var tronco = comp.layers.addShape();
    tronco.name = "Palmera " + n;
    trazo(tronco, "Tronco", puntos, entradas, salidas, "#8A5A3C", 16);
    tr(tronco).property("ADBE Position").setValue(base);
    var hojas = comp.layers.addShape();
    hojas.name = "Hojas " + n;
    var angulos = [-170, -135, -100, -60, -25, 12];
    for (var a = 0; a < angulos.length; a++) {
      var g = elipse(hojas, "Hoja " + (a + 1), 80, 0, 170, 40, "#2F7D5B", 100);
      transformacion(g).property("ADBE Vector Rotation").setValue(angulos[a]);
    }
    hojas.parent = tronco;
    tr(hojas).property("ADBE Position").setValue(copa);
  }
  palmera(1, [150, 790], [[0, 0], [-10, -130], [-28, -260]], [[0, 0], [4, 40], [6, 30]], [[0, 0], [-4, -40], [0, 0]], [-28, -260]);
  palmera(2, [950, 760], [[0, 0], [12, -120], [30, -240]], [[0, 0], [-4, 40], [-6, 30]], [[0, 0], [4, -40], [0, 0]], [30, -240]);

  // ── Noche: oscurece todo lo de abajo; luna, estrellas y ventanas van encima ──
  var noche = comp.layers.addSolid(rgb("#1B2347"), "Noche", 1080, 1080, 1, T);
  noche.blendingMode = BlendingMode.MULTIPLY;
  opacidad(noche, 0);

  var luna = comp.layers.addShape();
  luna.name = "Luna";
  elipse(luna, "Luna", 0, 0, 78, 78, "#F4F1E6", 100);
  tr(luna).property("ADBE Position").setValue([540 + 430, HORIZONTE]);
  opacidad(luna, 0);

  var estrellas = pieza("Estrellas");
  var semilla = 7;
  function azar() { semilla = (semilla * 16807) % 2147483647; return semilla / 2147483647; }
  for (var e = 0; e < 24; e++) {
    var d = 3 + azar() * 4;
    elipse(estrellas, "Estrella " + (e + 1), 40 + azar() * 1000, 50 + azar() * 380, d, d, "#FFFFFF", 70 + azar() * 30);
  }
  opacidad(estrellas, 0);

  // La luz de cada ventana, encima del vidrio oscuro de «Casa»
  for (var v = 0; v < CENTROS.length; v++) {
    var ventana = pieza("Ventana " + (v + 1));
    rect(ventana, "Luz", CENTROS[v] - 43, 548, CENTROS[v] + 43, 676, LUZ, 3);
    opacidad(ventana, 0);
  }

  comp.openInViewer();
  app.endUndoGroup();
})();
Expresión · Rotación de «Giro»
// Repite el tramo de 0 a 180° sumándolo cada vez
loopOut("offset")

Con offset el segundo tramo va de 180 a 360°, y un círculo completo deja todo igual que en el frame 0. Con cycle, el sol volvería a la izquierda de golpe.

Práctica 3
Práctica 3 · After Effects · 2 de 3

El día y la noche

  1. El color del cielo. En «Cielo» › Contenido › Cielo › Relleno 1 › Color, los siete keyframes de la tabla; el color se escribe en hexadecimal al hacer clic en la muestra. El 132 y el 270 repiten color: navegador de keyframes. Amanece en durazno, se pone azul, atardece y anochece.
  2. Noche, estrellas y brillo. Los keyframes de Opacidad (T) de «Noche», «Estrellas» y «Brillo del horizonte»; los que repiten el valor anterior, con el navegador. De noche todo se oscurece menos la luna y las estrellas.
  3. Ventanas en cascada. Los cuatro keyframes de Opacidad de «Ventana 1», el del 262 con el navegador; copiarlos y pegarlos en «Ventana 2» en el 149, en la 3 en el 154, en la 4 en el 159 y en la 5 en el 164. Se encienden y se apagan una tras otra.
  4. Nubes en parallax. Alt+clic en la Posición de «Nubes lejanas» y escribir la expresión; copiarla a «Nubes cercanas» y cambiar n = 1 por n = 2. Las cercanas van al doble de velocidad y la escena gana profundidad.
  5. Las palmeras se mecen. «Palmera 1», R: 2.5 en el 0, −2.5 en el 36 y 2.5 en el 72; F9 y loopOut("cycle"). «Palmera 2», con los signos al revés. Cuatro vaivenes por loop, a destiempo.
Keyframes · frame → valor
Cielo › Color   día #8FD3E8 · tarde #F7A072 · noche #2B3A6B
   0 tarde     18 día     132 día     149 tarde
 163 noche    270 noche   287 tarde

Noche › Opacidad
   0 → 35     12 → 0     138 → 0     162 → 70    270 → 70    287 → 35
Estrellas › Opacidad
 156 → 0     174 → 100   270 → 100   282 → 0
Brillo del horizonte › Opacidad
   0 → 75     18 → 0     133 → 0     149 → 80    164 → 0     275 → 0    287 → 75
Ventana 1 › Opacidad
 144 → 0     147 → 100   262 → 100   265 → 0

Los keyframes del 287 repiten el valor del 0: el amanecer sigue del último frame al primero.

Expresión · Posición de «Nubes lejanas»
// El módulo regresa la nube al otro lado
var n = 1;       // vueltas por loop; las cercanas, 2
var W = 1500;    // recorrido: la pantalla más el ancho de la nube
var x = (value[0] + 210 + time / thisComp.duration * n * W) % W - 210;
[x, value[1]]
Práctica 3
Práctica 3 · After Effects · 3 de 3

Pulir y personalizar

  1. El agua. «Reflejos del agua» › Contenido › Reflejos › Transformar: Reflejos › Posición: 0, 0 en el 0 y 80, 0 en el 48; loopOut("cycle"). Las rayas avanzan sin salto: a los 80 px el patrón es idéntico.
  2. Las hojas siguen al tronco. Alt+clic en la Rotación de «Hojas 1», la expresión de la derecha; copiarla a «Hojas 2». La copa llega un poco después y un poco más lejos que el tronco.
  3. La sombra sigue al sol. En «Sombra de la casa» › Contenido › Sombra › Trazado 1, cronómetro en el 72. En el 18, con la vista al 100 %, clic en el vértice de abajo a la izquierda, Mayús+clic en el de abajo a la derecha y Mayús+→ once veces: 110 px. En el 126, Mayús+← once veces. Opacidad: 0, 35, 35 y 0 en el 0, el 14, el 130 (con el navegador) y el 144. La sombra se estira al revés que el sol y desaparece de noche.
  4. Luz que se derrama. Efecto › Estilizar › Resplandor Stylize › Glow en «Ventana 1»; copiar el efecto a las otras cuatro. De noche, la luz pinta un poco el muro.
  5. Acabado y exportación. Desenfoque de movimiento y grano, como en la práctica 1; Ctrl+Alt+M, H.264 o GIF animado. 288 frames: el 287 son las 5:55 y el siguiente, las 6:00.
Expresión · Rotación de «Hojas 1»
// Las hojas siguen al tronco un cuarto de segundo después
var p = parent.transform.rotation;
var t = (time - 0.25 + thisComp.duration) % thisComp.duration;
p.valueAtTime(t) * 1.6 - p

Las hojas son hijas del tronco: su rotación se suma a la de él. Con el módulo, los primeros seis frames leen el final del loop, donde el tronco viene de la misma pose.

Práctica 4
Práctica 4 · 8 s

Tipografía que se transforma

AnticipaciónAcción complementariaMaskingObscurationTransformationOffset & Delay

LOOP sube letra por letra desde una línea base. Sus dos O se convierten en un ∞ que una luz recorre, y todo vuelve a su sitio y se hunde.

Las cuatro letras llevan los mismos keyframes, cada una dos frames después de la anterior.

◆ Apaga «Masking» y mira de dónde salen
000 · 0.00 s

Radiografía

Práctica 4
Práctica 4 · Planeación

Timing chart

Cada letra se mueve con los keyframes de su grupo «Letra». Se animan los de L y se pegan en las demás, dos frames después de la anterior.

La transformación funciona porque el círculo y la gota tienen los mismos cuatro vértices, en el mismo orden.

FramesQué pasaPrincipio
0 – 25Las letras suben una tras otra desde la línea base, se pasan un poco y se asientanMasking · Offset & Delay
48 – 65L y P se hunden; las O se quedan solasObscuration
58 – 72Cada O se convierte en una mitad del ∞Transformation
72 – 115Una luz recorre el ∞ dos veces por segundoloopOut
115 – 142El ∞ vuelve a ser O O y regresan L y PTransformation
151 – 174Cada letra sube un poco y se hunde, una tras otraAnticipación
175 – 191Vacío, igual que el frame 0El empalme
Práctica 4
Práctica 4 · After Effects · 1 de 3

La entrada

  1. Ejecutar el script 04-tipografia-que-se-transforma.jsx y contar en frames. Se abre «Tipografía que se transforma», de 8 s; en «Letras», la palabra quieta y dos gotas guía, tenues.
  2. La máscara de la línea base. En la composición principal, seleccionar «Letras», herramienta Rectángulo Q y arrastrar un rectángulo cualquiera. Pulsar M, clic en Forma… Shape… y escribir arriba 0, izquierda 0, derecha 1080 y abajo 690. Nada cambia: las letras están arriba de la línea, y lo que baje de ella dejará de verse.
  3. L entra. Doble clic en «Letras». En «L» › Contenido › Letra › Transformar: Letra › Posición, los cuatro keyframes de «Entra»; F9 en todos. L sube desde abajo, se pasa un poco y se asienta.
  4. Se hunde y regresa. Los seis keyframes de «Se hunde» y «Regresa»; F9. El del 48 y el del 124 repiten el valor anterior: se ponen con el botón central del navegador de keyframes. Entre el 48 y el 136, L baja, espera y vuelve.
  5. Sale. Los tres keyframes de «Sale», el del 151 con el navegador; F9. En el 168 L ya está debajo de la línea base.
04-tipografia-que-se-transforma.jsx
// 04 · Tipografía que se transforma · Loops de motion design
// Dibuja la palabra LOOP con trazos, quieta sobre su línea base, dentro de la
// precomposición «Letras». Cada letra tiene un grupo llamado «Letra»: así los
// keyframes de una se pueden pegar en las otras. Las O tienen cuatro vértices.
// También deja dos capas guía con la gota en que se convierte cada O (no salen
// en el render) y la «Luz» del ∞, en opacidad 0.
// No crea keyframes, expresiones ni máscaras.
// Archivo › Secuencias de comandos › Ejecutar archivo de secuencias de comandos
(function () {
  var T = 8;                 // duración del loop, en segundos
  var BASE = 690;            // la línea base: lo que baje de aquí no se verá
  var GROSOR = 40;           // grosor del trazo de las letras
  var COLOR = "#5632DA", LUZ = "#A72BC9", FONDO = "#F3F3EB";
  var K = 0.5523;            // la constante que aproxima un círculo con cuatro curvas

  app.beginUndoGroup("Tipografía que se transforma");

  function rgb(h) {
    return [parseInt(h.substr(1, 2), 16) / 255, parseInt(h.substr(3, 2), 16) / 255, parseInt(h.substr(5, 2), 16) / 255];
  }
  function tr(capa) { return capa.property("ADBE Transform Group"); }
  function forma(v, ent, sal, cerrada) {
    var s = new Shape(); s.vertices = v; s.inTangents = ent; s.outTangents = sal; s.closed = cerrada; return s;
  }
  function mover(s, dx, dy) {
    var v = []; for (var i = 0; i < s.vertices.length; i++) v.push([s.vertices[i][0] + dx, s.vertices[i][1] + dy]);
    return forma(v, s.inTangents, s.outTangents, s.closed);
  }
  function nuevoGrupo(capa, nombre) {
    var raiz = capa.property("ADBE Root Vectors Group");
    var i = raiz.addProperty("ADBE Vector Group").propertyIndex;
    raiz.property(i).name = nombre;
    return function () { return capa.property("ADBE Root Vectors Group").property(i).property("ADBE Vectors Group"); };
  }
  function trazar(g, color, grosor) {
    g().addProperty("ADBE Vector Graphic - Stroke");
    var t = g().property(g().numProperties);
    t.property("ADBE Vector Stroke Color").setValue(rgb(color));
    t.property("ADBE Vector Stroke Width").setValue(grosor);
    t.property("ADBE Vector Stroke Line Cap").setValue(2);
    t.property("ADBE Vector Stroke Line Join").setValue(2);
  }
  function capaDeTrazo(comp, nombre, grupo, pos, trazado, color, grosor) {
    var capa = comp.layers.addShape();
    capa.name = nombre;
    var g = nuevoGrupo(capa, grupo);
    g().addProperty("ADBE Vector Shape - Group");
    g().property(1).property("ADBE Vector Shape").setValue(trazado);
    trazar(g, color, grosor);
    tr(capa).property("ADBE Position").setValue(pos);
    return capa;
  }

  // El círculo de las O y la gota en que se convierte cada una: los mismos
  // cuatro vértices, en el mismo orden (derecha, abajo, izquierda, arriba).
  var r = 120, k = r * K;
  var CIRCULO = forma([[r, 0], [0, r], [-r, 0], [0, -r]],
    [[0, -k], [k, 0], [0, k], [-k, 0]], [[0, k], [-k, 0], [0, -k], [k, 0]], true);
  var GOTA_IZQ = forma([[130, 0], [0, 95], [-130, 0], [0, -95]],
    [[-50, -45], [60, 0], [0, 60], [-75, 0]], [[-50, 45], [-75, 0], [0, -60], [60, 0]], true);
  var GOTA_DER = forma([[130, 0], [0, 95], [-130, 0], [0, -95]],
    [[0, -60], [75, 0], [50, 45], [-60, 0]], [[0, 60], [-60, 0], [50, -45], [75, 0]], true);
  var POS_O1 = [413, 540], POS_O2 = [703, 540];
  // Donde termina cada gota: juntas forman el ∞
  var FIN_O1 = [410, 540], FIN_O2 = [670, 540];

  // ── La precomposición «Letras» ──
  var letras = app.project.items.addComp("Letras", 1080, 1080, 1, T, 24);

  var luz = letras.layers.addShape();
  luz.name = "Luz";
  tr(luz).property("ADBE Position").setValue([0, 0]);
  var gLuz = nuevoGrupo(luz, "Infinito");
  gLuz().addProperty("ADBE Vector Shape - Group");
  gLuz().property(1).property("ADBE Vector Shape").setValue(mover(GOTA_IZQ, FIN_O1[0], FIN_O1[1]));
  gLuz().addProperty("ADBE Vector Shape - Group");
  gLuz().property(2).property("ADBE Vector Shape").setValue(mover(GOTA_DER, FIN_O2[0], FIN_O2[1]));
  trazar(gLuz, LUZ, 16);
  tr(luz).property("ADBE Opacity").setValue(0);

  // Guías: cada gota dibujada en la capa de su O, ya en su sitio final
  var guias = [["Gota 1 (guía)", POS_O1, mover(GOTA_IZQ, FIN_O1[0] - POS_O1[0], 0)],
               ["Gota 2 (guía)", POS_O2, mover(GOTA_DER, FIN_O2[0] - POS_O2[0], 0)]];
  for (var q = 0; q < guias.length; q++) {
    var guia = capaDeTrazo(letras, guias[q][0], "Gota", guias[q][1], guias[q][2], LUZ, 6);
    guia.guideLayer = true;
    tr(guia).property("ADBE Opacity").setValue(45);
  }

  capaDeTrazo(letras, "P", "Letra", [888, 660], forma([[0, 0], [0, -240], [60, -240], [60, -110], [0, -110]],
    [[0, 0], [0, 0], [0, 0], [40, 0], [0, 0]], [[0, 0], [0, 0], [40, 0], [0, 0], [0, 0]], false), COLOR, GROSOR);
  capaDeTrazo(letras, "O2", "Letra", POS_O2, CIRCULO, COLOR, GROSOR);
  capaDeTrazo(letras, "O1", "Letra", POS_O1, CIRCULO, COLOR, GROSOR);
  capaDeTrazo(letras, "L", "Letra", [103, 660], forma([[0, -240], [0, 0], [130, 0]],
    [[0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [0, 0]], false), COLOR, GROSOR);
  luz.moveToBeginning();                      // la luz va encima de las letras

  // ── La composición principal ──
  var comp = app.project.items.addComp("Tipografía que se transforma", 1080, 1080, 1, T, 24);
  comp.bgColor = rgb(FONDO);
  comp.layers.addSolid(rgb(FONDO), "Fondo", 1080, 1080, 1, T);

  var linea = comp.layers.addShape();
  linea.name = "Línea base";
  tr(linea).property("ADBE Position").setValue([0, 0]);
  var gLinea = nuevoGrupo(linea, "Línea");
  gLinea().addProperty("ADBE Vector Shape - Group");
  gLinea().property(1).property("ADBE Vector Shape").setValue(forma([[60, BASE + 2], [1020, BASE + 2]], [[0, 0], [0, 0]], [[0, 0], [0, 0]], false));
  trazar(gLinea, COLOR, 4);
  tr(linea).property("ADBE Opacity").setValue(30);

  var capaLetras = comp.layers.add(letras);
  capaLetras.collapseTransformation = true;   // vectores nítidos a cualquier escala

  comp.openInViewer();
  app.endUndoGroup();
})();
Keyframes de «L» · Letra › Posición
Entra       0 → 0, 320     11 → 0, −26    15 → 0, 10    19 → 0, 0
Se hunde   48 → 0, 0       50 → 0, −19    59 → 0, 320
Regresa   124 → 0, 320    132 → 0, −13   136 → 0, 0
Sale      151 → 0, 0      156 → 0, −26   168 → 0, 320

Es la posición del grupo, no la de la capa: en las cuatro letras vale 0, 0 en reposo, así que los mismos keyframes sirven para todas. Dentro de «Letras» la letra se ve también bajo la línea; la máscara está en la composición principal.

Práctica 4
Práctica 4 · After Effects · 2 de 3

La transformación

  1. Cascada. Copiar los trece keyframes de L. Seleccionar «P», ir al frame 6 y Ctrl+V. Las O solo entran y salen: copiar los de «Entra» y «Sale» (Mayús+clic) y pegarlos en «O1» en el 2 y en «O2» en el 4. LOOP entra letra por letra; mientras L y P se hunden, las O se quedan.
  2. El círculo. En «O1» › Contenido › Letra › Trazado 1, activar el cronómetro de Trazado en el 58. Un keyframe con el círculo tal como está.
  3. La gota. En «Gota 1 (guía)» › Contenido › Gota › Trazado 1, seleccionar Trazado y Ctrl+C. Volver a «O1», ir al 72, seleccionar su Trazado y Ctrl+V. Entre el 58 y el 72, el círculo se convierte en gota.
  4. Sostener y regresar. Copiar el keyframe del 72 y pegarlo en el 115; el del 58, en el 130. F9 en los cuatro. La gota se mantiene y vuelve a ser círculo.
  5. La otra O. Lo mismo en «O2», con «Gota 2 (guía)». Las dos gotas forman el ∞.
  6. Una luz en el ∞. En «Luz» › Contenido › Infinito: Agregar › Recortar trazados Trim Paths, Fin en 14 %. Desplazamiento: 0° en el 72 y 360° en el 84, en linear; Alt+clic y loopOut("cycle"). La capa sigue en opacidad 0: todavía no se ve.
  7. Que aparezca con el ∞. En «Luz», T: 0 en el 72, 100 en el 76, 100 en el 110 (con el navegador) y 0 en el 115. Un destello recorre cada mitad del ∞ dos veces por segundo.
Práctica 4
Práctica 4 · After Effects · 3 de 3

Pulir y personalizar

  1. Anticipación de las O. En O1 y O2, sin el vínculo de proporción, keyframes de Escala: 100, 100 en el 53; 112, 88 en el 56; 100, 100 en el 60. Las O se aplastan justo antes de transformarse.
  2. Cascada más abierta. Seleccionar todos los keyframes de O1 y moverlos con Alt+→ dos frames; los de O2, cuatro; los de P, seis. Las letras entran más separadas; sin desfase, entran juntas.
  3. Luz más rápida. En el Desplazamiento de «Luz», el keyframe del 84, de 360° a 720°. El doble de vueltas: loopOut repite el tramo con su nueva pendiente.
  4. Peso de la letra. En cada letra, Trazo 1 › Anchura del trazo Stroke Width, y su color. Con 24 la palabra se aligera; con 56, se vuelve pesada.
  5. Acabado. Desenfoque de movimiento en las letras y en las dos composiciones; grano en la principal, como en la práctica 1. Las letras se arrastran al entrar y al salir.
  6. Otra palabra. En «Letras», apagar el ojo de L, O1, O2, P y «Luz». Con la herramienta Texto Ctrl+T, escribir de cuatro a seis letras en mayúsculas (lo que baje de la línea no se ve), en una tipografía gruesa a 300 px; en el panel Párrafo, Centrar texto Center Text, y Posición 540, 680. La palabra se apoya en la línea base, donde estaba LOOP.
  7. De texto a forma. Clic derecho en la capa de texto › Crear › Crear formas a partir de texto Create Shapes from Text; activar el desenfoque de movimiento de la capa nueva. Una capa de forma con un grupo por letra; la de texto se apaga sola.
  8. La misma cascada. En L, seleccionar los keyframes de «Entra» y «Sale» con Mayús+clic y Ctrl+C. En la capa nueva, Contenido › la primera letra › Transformar › Posición: seleccionar Posición, ir al 0 y Ctrl+V. La segunda letra, en el 2; la tercera, en el 4, y así. La palabra entra y sale letra por letra. El morphing y la luz son propios de LOOP.
  9. Exportar. Ctrl+Alt+M, H.264 o GIF animado. 192 frames; el primero y el último, vacíos.
Práctica 5
Práctica 5 · 12 s

Zoom infinito

SincronizaciónPuesta en escenaDolly & ZoomTransformation

Por la ventana de una fachada se entra a una habitación, por su cuadro a un escritorio y por su pantalla otra vez a la fachada.

Siete escenas encadenadas, cada una al 25 % de la anterior. Solo la primera crece, con escala exponencial.

◆ Apaga «Escala exponencial» y mira cómo frena
000 · 0.00 s

Radiografía

Personalizar

Práctica 5
Práctica 5 · Planeación

Lo que entrega el script

Tres escenas, cada una en su precomposición y con un marco cuadrado de 270 px en el centro: 1080 entre 4. Una escena al 25 % cabe justo en el marco de otra, y la última lleva otra vez a la primera.

A · Fachada
por la ventana →
B · Habitación
por el cuadro →
C · Pantalla
por la pantalla, a A

La cadena

Cada escena es hija de la anterior, con Escala 25 %. Solo la primera crece; las demás la siguen.

CapaEscenaEscalaTamaño real
1A · Fachada100 %100 %
2B · Habitación25 %25 %
3C · Pantalla25 %6.25 %
4A · Fachada25 %1.56 %
5B · Habitación25 %0.39 %
6C · Pantalla25 %0.098 %
7A · Fachada25 %0.024 %

La capa 1 es la de abajo; cada escena nueva va encima de la anterior.

Práctica 5
Práctica 5 · After Effects · 1 de 3

La cadena

  1. Ejecutar el script 05-zoom-infinito.jsx y contar en frames. Aparecen las tres escenas y «Zoom infinito», vacía.
  2. La primera escena. Arrastrar «Escena A · Fachada» del panel Proyecto a la línea de tiempo y activar su interruptor Contraer transformaciones Collapse Transformations, el del sol. La fachada llena la composición.
  3. La segunda, en la ventana. Arrastrar «Escena B · Habitación», que queda encima. Primero el vínculo: columna Parent & Link, la fachada. Después la escala: S y 25 %. La habitación ocupa justo la ventana del centro.
  4. La tercera, en el cuadro. «Escena C · Pantalla», hija de la habitación y al 25 %. El escritorio aparece dentro del cuadro.
  5. Cerrar la cadena. Cuatro escenas más, en el orden A, B, C y A: cada una encima, hija de la anterior y al 25 %. Contraer transformaciones en las siete. Siete capas; la última es un punto en el centro.
05-zoom-infinito.jsx
// 05 · Zoom infinito · Loops de motion design
// Dibuja tres escenas, cada una en su precomposición y con un marco cuadrado en
// el centro: la fachada tiene una ventana, la habitación un cuadro y el
// escritorio una pantalla. El marco mide 1080 / 4 = 270 px, de 405 a 675 en los
// dos ejes: una escena al 25 % cabe justo en el marco de la otra.
// También crea la composición «Zoom infinito», vacía: ahí se encadenan las escenas.
// No crea keyframes ni expresiones.
// Archivo › Secuencias de comandos › Ejecutar archivo de secuencias de comandos
(function () {
  var T = 12;          // duración del loop, en segundos

  app.beginUndoGroup("Zoom infinito");

  function rgb(h) {
    return [parseInt(h.substr(1, 2), 16) / 255, parseInt(h.substr(3, 2), 16) / 255, parseInt(h.substr(5, 2), 16) / 255];
  }
  function tr(capa) { return capa.property("ADBE Transform Group"); }
  function nuevoGrupo(capa, nombre) {
    var raiz = capa.property("ADBE Root Vectors Group");
    var i = raiz.addProperty("ADBE Vector Group").propertyIndex;
    raiz.property(i).name = nombre;
    return function () { return capa.property("ADBE Root Vectors Group").property(i).property("ADBE Vectors Group"); };
  }
  function relleno(g, color) {
    g().addProperty("ADBE Vector Graphic - Fill");
    g().property(g().numProperties).property("ADBE Vector Fill Color").setValue(rgb(color));
  }
  function rect(capa, nombre, x0, y0, x1, y1, color, redondeo) {
    var g = nuevoGrupo(capa, nombre);
    g().addProperty("ADBE Vector Shape - Rect");
    g().property(1).property("ADBE Vector Rect Size").setValue([x1 - x0, y1 - y0]);
    g().property(1).property("ADBE Vector Rect Position").setValue([(x0 + x1) / 2, (y0 + y1) / 2]);
    if (redondeo) g().property(1).property("ADBE Vector Rect Roundness").setValue(redondeo);
    relleno(g, color);
  }
  function elipse(capa, nombre, cx, cy, w, h, color, giro) {
    var g = nuevoGrupo(capa, nombre);
    g().addProperty("ADBE Vector Shape - Ellipse");
    g().property(1).property("ADBE Vector Ellipse Size").setValue([w, h]);
    relleno(g, color);
    var t = g().parentProperty.property("ADBE Vector Transform Group");
    t.property("ADBE Vector Position").setValue([cx, cy]);
    if (giro) t.property("ADBE Vector Rotation").setValue(giro);
  }
  function poligono(capa, nombre, puntos, color) {
    var g = nuevoGrupo(capa, nombre);
    g().addProperty("ADBE Vector Shape - Group");
    var s = new Shape(); s.vertices = puntos; s.closed = true;
    g().property(1).property("ADBE Vector Shape").setValue(s);
    relleno(g, color);
  }
  function linea(capa, nombre, a, b, color, grosor) {
    var g = nuevoGrupo(capa, nombre);
    g().addProperty("ADBE Vector Shape - Group");
    var s = new Shape(); s.vertices = [a, b]; s.closed = false;
    g().property(1).property("ADBE Vector Shape").setValue(s);
    g().addProperty("ADBE Vector Graphic - Stroke");
    g().property(2).property("ADBE Vector Stroke Color").setValue(rgb(color));
    g().property(2).property("ADBE Vector Stroke Width").setValue(grosor);
    g().property(2).property("ADBE Vector Stroke Line Cap").setValue(2);
  }
  // Una escena: precomposición de 1080 × 1080 con una capa de dibujo. Los grupos
  // que se añaden primero quedan delante, así que se dibuja de adelante hacia atrás.
  function escena(nombre, dibujar) {
    var c = app.project.items.addComp(nombre, 1080, 1080, 1, T, 24);
    var capa = c.layers.addShape();
    capa.name = "Dibujo";
    tr(capa).property("ADBE Position").setValue([0, 0]);
    dibujar(capa);
    return c;
  }
  var MARINO = "#1F2A44";

  escena("Escena A · Fachada", function (d) {
    rect(d, "Alféizar", 370, 687, 710, 706, "#D9A43A");
    rect(d, "Ventana del centro", 393, 393, 687, 687, MARINO);
    var CENTROS = [108, 324, 540, 756, 972], encendidas = [2, 5, 9, 13, 16, 21, 22];
    var n = 0;
    for (var f = 0; f < 5; f++) for (var c = 0; c < 5; c++) {
      if (f === 2 && c === 2) continue;
      var x = CENTROS[c], y = CENTROS[f];
      linea(d, "Parteluz " + n, [x, y - 54], [x, y + 54], MARINO, 6);
      linea(d, "Travesaño " + n, [x - 54, y], [x + 54, y], MARINO, 6);
      var encendida = false;
      for (var q = 0; q < encendidas.length; q++) if (encendidas[q] === n) encendida = true;
      rect(d, "Vidrio " + n, x - 52, y - 52, x + 52, y + 52, encendida ? "#FFE7A6" : "#3B5566");
      rect(d, "Marco " + n, x - 60, y - 60, x + 60, y + 60, MARINO);
      n++;
    }
    rect(d, "Cornisa alta", 0, 196, 1080, 212, "#D9A43A");
    rect(d, "Cornisa baja", 0, 868, 1080, 884, "#D9A43A");
    rect(d, "Muro", 0, 0, 1080, 1080, "#F2C14E");
  });

  escena("Escena B · Habitación", function (d) {
    rect(d, "Cojín izquierdo", 330, 772, 530, 830, "#2B3A55", 14);
    rect(d, "Cojín derecho", 550, 772, 750, 830, "#2B3A55", 14);
    rect(d, "Sofá", 300, 760, 780, 880, MARINO, 22);
    for (var h = 0; h < 5; h++) elipse(d, "Hoja " + (h + 1), 930 + (h - 2) * 22, 700 - Math.abs(h - 2) * 18, 46, 130, "#3F9B6B", (h - 2) * 24);
    poligono(d, "Maceta", [[880, 870], [980, 870], [965, 780], [895, 780]], "#E8473F");
    poligono(d, "Pantalla de la lámpara", [[90, 560], [210, 560], [185, 470], [115, 470]], "#F2C14E");
    linea(d, "Pie de la lámpara", [150, 870], [150, 560], MARINO, 8);
    rect(d, "Marco del cuadro", 387, 387, 693, 693, "#E3B04B");
    rect(d, "Sombra del cuadro", 399, 401, 705, 707, "#24495E");
    rect(d, "Zoclo", 0, 868, 1080, 884, MARINO);
    rect(d, "Piso", 0, 880, 1080, 1080, "#C8724A");
    rect(d, "Pared", 0, 0, 1080, 1080, "#2E5E7A");
  });

  escena("Escena C · Pantalla", function (d) {
    linea(d, "Vapor 1", [840, 760], [846, 720], "#F6E7D4", 5);
    linea(d, "Vapor 2", [866, 760], [860, 714], "#F6E7D4", 5);
    rect(d, "Taza", 820, 780, 890, 860, "#F6E7D4", 8);
    linea(d, "Lápiz 1", [190, 770], [176, 700], MARINO, 8);
    linea(d, "Lápiz 2", [212, 770], [222, 690], "#F2C14E", 8);
    rect(d, "Vaso de lápices", 170, 770, 240, 860, "#3FB7C6", 6);
    rect(d, "Base del monitor", 440, 798, 640, 818, MARINO, 8);
    poligono(d, "Brazo del monitor", [[505, 715], [575, 715], [590, 800], [490, 800]], MARINO);
    rect(d, "Monitor", 365, 365, 715, 715, MARINO, 28);
    rect(d, "Borde del escritorio", 0, 860, 1080, 874, "#EAD3BA");
    rect(d, "Escritorio", 0, 860, 1080, 1080, "#F6E7D4");
    rect(d, "Pared", 0, 0, 1080, 1080, "#E8473F");
  });

  // ── La composición principal, vacía ──
  var comp = app.project.items.addComp("Zoom infinito", 1080, 1080, 1, T, 24);
  comp.bgColor = rgb("#F2C14E");

  comp.openInViewer();
  app.endUndoGroup();
})();

El orden importa: al vincular, After Effects conserva el tamaño en pantalla. Si la escala se pone antes, la hija queda al 100 % de su madre.

Práctica 5
Práctica 5 · After Effects · 2 de 3

La escala exponencial

  1. Que crezca la primera. En la capa de abajo, la primera fachada, Alt+clic en el cronómetro de Escala y escribir la expresión de la derecha. Cada 4 s, la escena del marco llena la pantalla; las de dentro la siguen porque son sus hijas.
  2. Revisar el empalme. Comparar el 287 (Fin) con el 0 (Inicio). En el 287 la cuarta capa casi llena la pantalla; en el 0 la llena la primera, y las dos son la fachada.
  3. Romperlo a propósito. Desactivar la expresión con el signo = y poner dos keyframes de Escala: 100 % en el 0 y 6309 % en el 287. El acercamiento arranca rápido y frena. Al terminar, borrar los keyframes y volver a activar la expresión.
  4. Comprobar la nitidez. Contraer transformaciones activo en las siete capas. Al 6000 % las líneas siguen nítidas, porque son vectores.
Expresión · Escala de la primera fachada
// ×4 cada 4 s: el mismo factor que separa una escena de la siguiente
var s = 100 * Math.pow(4, time / 4);
[s, s]

En 12 s son tres niveles: 100 × 4³ = 6400 %, y la cuarta capa ocupa el lugar de la primera. Los keyframes no sirven aquí: el último tendría que caer en el frame 288, fuera de la composición, y una escala lineal frena.

Práctica 5
Práctica 5 · After Effects · 3 de 3

Pulir y personalizar

  1. Rediseñar una escena. En el panel Proyecto, doble clic en «Escena B · Habitación» y cambiar lo que se quiera. La única regla: dejar el marco de 270 × 270 px en el centro, de 405 a 675 en los dos ejes.
  2. Animar dentro de una escena. En la habitación, que la planta se meza o la lámpara parpadee, con un número entero de ciclos en 12 s. La animación sigue al pasar de nivel: cada escena dura lo mismo que el loop.
  3. Giro en espiral. Seleccionar las capas 2 a 7, R y escribir −90: vale para las seis. En la primera, Alt+clic en Rotación y 90 * time / 4. Cada nivel entra girado un cuarto de vuelta, y el que llena la pantalla queda derecho.
  4. Más velocidad. En la expresión de Escala, time / 2, y tres escenas más en la cadena: B, C y A. Si tiene el giro en espiral, también 90 * time / 2. Seis niveles en 12 s, el doble de rápido; sigue cerrando, porque 6 es múltiplo de las tres escenas.
  5. Desenfoque de movimiento. Activarlo en las capas y en la composición. El borde de cada escena se arrastra un poco al crecer.
  6. Grano. Una capa de ajuste con Ruido, como en la práctica 1. La textura queda fija en pantalla mientras todo crece.
  7. Exportar. Ctrl+Alt+M, H.264 o GIF animado. 288 frames: el 287 enlaza con el 0.
Loops
Práctica

Cómo se practica esto

Un loop propio, de 10 a 15 segundos

  • Partir de una de las cinco prácticas y cambiarle el diseño: piezas, paleta y materiales.
  • Combinar al menos dos principios de animación y dos de motion graphics, y poder señalar dónde está cada uno.
  • Planear antes de animar: un style frame y un timing chart.
  • Decidir con cuál de las cinco formas cierra el loop.
  • Exportar el ciclo exacto en MP4 cuadrado de 1080 px.

Antes de darlo por terminado

0 de 7

Loops
Cuestionario

¿Cómo cierra este loop?

1 de 5 · aciertos 0

Cada respuesta explica por qué, acierte o no.

Loops
Referencias

Quién hace loops

  • Escenas con luz, sombra y cámara en las que docenas de elementos se mueven con un mismo tiempo. El acabado lleva grano.

  • Dave Whyte · Bees & BombsLoops geométricos

    Una forma simple clonada en retícula, con el tiempo desfasado según su posición. Pocos colores, para que todo el peso caiga en el movimiento.

  • Geoffroy de CrécyMáquinas en bucle

    Objetos cotidianos convertidos en mecanismos infinitos, que no necesitan a nadie para funcionar, con paletas de mediados del siglo XX.

  • Sus gifathons: un loop diario durante un mes en una ciudad, y un catálogo de técnicas de bucle —rebote, desplazamiento, isométrico, zoom, giro.

  • Ben MarriottTextura y proceso

    Tutoriales del proceso completo: texturas, falso 3D, transformaciones y el acabado que hace que una pieza plana parezca ilustrada.

Loops
En el Aula

Para seguir

Loops
Referencias

Fuentes

Adobe. (s. f.). Expression language reference. After Effects User Guide. https://helpx.adobe.com/after-effects/using/expression-language-reference.html

Lodigiani, V. (s. f.). Work [Portafolio]. https://centolodigiani.com/

Motion Design School. (s. f.). Animated loops with James Curran [Curso en línea]. https://motiondesign.school/courses/animated-loops-with-james-curran

School of Motion. (s. f.). Animation Bootcamp [Curso en línea]. https://www.schoolofmotion.com/animation-bootcamp

Thomas, F. y Johnston, O. (1981). The illusion of life: Disney animation. Abbeville Press.

Ward, C. (s. f.). Motion design inspiration: Loops. School of Motion. https://schoolofmotion.com/blog/loop-inspiration-motion-graphics

Willenskomer, I. (2017, 31 de marzo). Creating usability with motion: The UX in motion manifesto. UX in Motion. https://medium.com/ux-in-motion/creating-usability-with-motion-the-ux-in-motion-manifesto-a87a4584ddc

LaboratoriosConstruidos con Canvas y JavaScript; cada uno reproduce el mecanismo de su escena de After Effects.
Scripts y expresionesEscritos para estas prácticas, en ExtendScript y en el lenguaje de expresiones de After Effects.
TipografíaPoppins, servida desde este mismo dominio.
AutoríaL.D.G. Luis E. Maldonado Gil · CodeLibri
01 / 35

Esta presentación está pensada para pantalla horizontal.