Учебник Theatre.js — это подробное руководство о том, как создавать потрясающие анимационные последовательности. Мы покажем, как анимировать куб Three.js, интегрировать привлекательные визуальные эффекты, изменять цвета, экспериментировать с элементами HTML и синхронизировать анимацию с воспроизведением звука через определенные промежутки времени.

Оглавление

  1. Установка и настройка
  • Добавьте в проект Theatre.js
  • Создать куб и пол
  • Импортируйте театр и создайте проект
  1. Объекты и реквизит
  2. Анимация куба
  • Добавление ключевых кадров
  • Графический редактор
  1. Изменение цветов
  • Коробка светится при растяжении
  1. Эффект скоростных линий
  2. Эффект комического текста
  3. Игра с положением указателя: Звуковые эффекты
  4. Тональное отображение и кодировщик
  5. Развертывание в производство

Установка и настройка

Прежде всего нам нужен начальный шаблон с Three.js и базовая сцена. Theatre.js имеет два основных пакета:

  1. @theatre/studio – это графический интерфейс редактора, который мы используем для создания анимации.
  2. @theatre/core воспроизводит созданные нами анимации.

Мы можем добавить пакеты Theater.js следующим образом:

# with npm
npm install --save @theatre/core @theatre/studio
# with yarn
yarn add @theatre/core @theatre/studio

Кроме того, вы можете загрузить этот начальный шаблон, который поставляется со всеми необходимыми зависимостями, и выполнить следующие команды:

# Install the dependencies 
yarn install

# Start the server
yarn run dev.

Создать куб и пол

Использование моего начального шаблона дает нам простой куб, пол, некоторые элементы управления освещением и орбитой.

// Cube
  const geometry = new THREE.BoxGeometry(10, 10, 10);
  const material = new THREE.MeshPhongMaterial({ color: 0x049ef4 });
  const box = new THREE.Mesh(geometry, material);
  box.castShadow = true;
  box.receiveShadow = true;
  scene.add(box);

// Floor
  const floorGeometry = new THREE.CylinderGeometry(30, 30, 300, 30);
  const floorMaterial = new THREE.MeshPhongMaterial({ color: 0xf0f0f0 });
  const floor = new THREE.Mesh(floorGeometry, floorMaterial);
  floor.position.set(0, -150, 0);
  floor.receiveShadow = true;
  scene.add(floor);

// Lights
  const ambLight = new THREE.AmbientLight(0xfefefe, 0.2);
  const dirLight = new THREE.DirectionalLight(0xfefefe, 1);
  dirLight.castShadow = true;
  dirLight.shadow.mapSize.width = 1024;
  dirLight.shadow.mapSize.height = 1024;
  dirLight.shadow.camera.far = 100;
  dirLight.shadow.camera.near = 1;
  dirLight.shadow.camera.top = 40;
  dirLight.shadow.camera.right = 40;
  dirLight.shadow.camera.bottom = -40;
  dirLight.shadow.camera.left = -40;
  dirLight.position.set(20, 30, 20);
  scene.add(ambLight, dirLight);

// OrbitControls
  controls = new OrbitControls(camera, renderer.domElement);
  controls.enableZoom = true;
  controls.enableDamping = true;
  controls.autoRotate = false;
  controls.dampingFactor = 0.1;
  controls.minDistance = 2.4;
  controls.target.set(0, 20, 0);

Импортируйте театр и создайте проект

Нам нужно импортировать { getProject, types из theatre/core. После этого нам также нужно импортировать studio из @theatre/studio и инициализировать studio.

Проект в Theatre.js подобен сохраненному файлу. Проекты хранятся в локальном хранилище, поэтому вы не потеряете свой прогресс, если закроете и снова откроете браузер. Создайте новый театральный проект и дайте ему имя.
Затем давайте создадим новый лист. лист содержит все объекты, которые можно анимировать.

import { getProject, types } from '@theatre/core';
import studio from '@theatre/studio';
studio.initialize();

// Create a project for the animation
const project = getProject('TheatreTutorial_1');

// Create a sheet
const sheet = project.sheet('AnimationScene');

Предметы и реквизит

Каждый объект, который необходимо анимировать, имеет соответствующий Объект театрального листа. Эти объекты листа содержат свойства, или Props, которые можно анимировать для создания движения и других динамических эффектов в сцене.

Давайте создадим новый boxObject и назовем его «Box».

const boxObj = sheet.object('Box', {});

Пропсы соответствуют определенным характеристикам объекта, который можно анимировать. У реквизита могут быть разные типы, которые можно импортировать с помощью import {types} из @theatre/core.

Мы добавим реквизит. Давайте начнем с rotation, создав свойство составного типа и добавим xR, yR и zR числа типа, значение: 0 и диапазон: [-Math.PI, Math.PI].

Точно так же давайте добавим реквизиты для положения и масштаба. Добавление к ним nudgeMultiplier дает нам более детальный контроль.

const boxObj = sheet.object('Box', {
    rotation: types.compound({
      xR: types.number(0, { range: [-Math.PI, Math.PI] }),
      yR: types.number(0, { range: [-Math.PI, Math.PI] }),
      zR: types.number(0, { range: [-Math.PI, Math.PI] }),
    }),
    position: types.compound({
      x: types.number(0, { nudgeMultiplier: 0.1 }),
      y: types.number(0, { nudgeMultiplier: 0.1 }),
      z: types.number(0, { nudgeMultiplier: 0.1 }),
    }),
    scale: types.compound({
      xS: types.number(1, { nudgeMultiplier: 0.1 }),
      yS: types.number(1, { nudgeMultiplier: 0.1 }),
      zS: types.number(1, { nudgeMultiplier: 0.1 }),
    }),
});

Теперь мы видим, что у нас есть новый объект Box под нашим листом.

Анимация куба

Пришло время оживить наш куб. Нам нужен способ повернуть нашу сетку куба на основе значений реквизита boxObj. Это можно сделать, прослушивая изменения boxObj с помощью хука onValuesChange().

boxObj.onValuesChange((values) => {
    const { xR, yR, zR } = values.rotation;
    box.rotation.set(xR, yR, zR);
    const { x, y, z } = values.position;
    box.position.set(x, y, z);
    const { xS, yS, zS } = values.scale;
    box.scale.set(xS, yS, zS);
});

Перемещение ползунков теперь влияет на наш куб в режиме реального времени.

Добавление ключевых кадров

Давайте добавим несколько ключевых кадров. Вы можете щелкнуть правой кнопкой мыши любой реквизит и выбрать последовательность или последовательность всех.

Это вызывает редактор последовательности с последовательностью реквизита. Мы можем изменить размер временной шкалы последовательности, увеличить или уменьшить масштаб, а также использовать синий указатель для пролистывания последовательности.

Перетащите, чтобы переместить указатель, и нажмите желтую кнопку, чтобы добавить ключевой кадр.

Давайте изменим размер временной шкалы последовательности, сделав ее чуть более 2 секунд. Затем добавьте ключевые кадры, чтобы анимировать позицию y нашего куба. Точно так же давайте упорядочим шкалы и добавим к ним ключевые кадры. Следуйте этим значениям или экспериментируйте, пока они не будут выглядеть хорошо для вас.

Затем нажмите пробел, чтобы воспроизвести последовательность.

Графический редактор

Нажатие кнопки рядом с каждым из наших реквизитов в редакторе последовательности открывает редактор графиков или редактор многодорожечных кривых. Это удобно, когда мы хотим улучшить анимацию, отредактировав кривую скорости одного или нескольких треков вручную.

Нажмите на связи между ключевыми кадрами, чтобы открыть список кривых ослабления по умолчанию, которые можно использовать.

Изменение цветов

Давайте продолжим и посмотрим, как мы можем изменить цвета с помощью Theater.js. Давайте создадим новый объект и назовем его colors. backgroundColor относится к типу types.rgba(). Точно так же мы создаем реквизиты для цвета пола и цвета коробки.

const colorObj = sheet.object('Colors',{
    backgroundColor: types.rgba(),
    floorColor: types.rgba(),
    boxColor: types.rgba(),
})

В хуке onValuesChange() мы можем установить либо scene.background, либо цвет фона базового HTML-элемента. С помощью setRGB() мы устанавливаем цвет материалов пола и ящика. Нажмите и перетащите палитру цветов, чтобы изменить цвет.

colorObj.onValuesChange((values)=>{
    // scene.background = new THREE.Color(values.backgroundColor.toString());
    // @ts-ignore
    document.body.style.backgroundColor = values.backgroundColor;
    floorMaterial.color.setRGB(values.floorColor.r,values.floorColor.g,values.floorColor.b)
    boxMaterial.color.setRGB(values.boxColor.r,values.boxColor.g,values.boxColor.b)
})

Свечение при растяжении

Было бы неплохо сделать так, чтобы куб светился при растяжении. Давайте создадим новый объект театра: boxEffects.

Затем мы добавляем параметр boxGlow, чтобы задать цвет излучения.

const boxEffectsObj = sheet.object('Effects',{
    boxGlow:types.rgba(),
})
boxEffectsObj.onValuesChange((values)=>{
    boxMaterial.emissive.setRGB(values.boxGlow.r,values.boxGlow.g,values.boxGlow.b);
})

Давайте упорядочим это и добавим два ключевых кадра с эмиссией #000000 на первых нескольких кадрах и выберем цвет для сжатого состояния. Затем вернитесь в нормальное состояние на последнем кадре.

Эффект линий скорости

Чтобы добавить мультяшные линии скорости vFx, давайте создадим три куба и масштабируем их, чтобы они выглядели как линии, и добавим их в сцену в группе.

// Swoosh Effect Objects
const swooshMaterial = new THREE.MeshBasicMaterial({color:0x222222,transparent:true,opacity:1});
const swooshEffect = new THREE.Group();

const swooshBig = new THREE.Mesh(geometry, swooshMaterial );
swooshBig.scale.set(0.02,2,0.02)
swooshBig.position.set(1,0,-2)

const swooshSmall1 = new THREE.Mesh(geometry, swooshMaterial );
swooshSmall1.scale.set(0.02,1,0.02)
swooshSmall1.position.set(1,0,3)

const swooshSmall2 = new THREE.Mesh(geometry, swooshMaterial );
swooshSmall2.scale.set(0.02,1.4,0.02)
swooshSmall2.position.set(-3,0,0)

swooshEffect.add( swooshBig, swooshSmall1, swooshSmall2 );
swooshEffect.position.set(0,20,0)
scene.add(swooshEffect)

Давайте добавим больше свойств к объекту boxEffect, чтобы поиграть с масштабом, положением и непрозрачностью линий. Попробуйте поиграть с ключевыми кадрами для этого, чтобы получить желаемый эффект.

const boxEffectsObj = sheet.object('Effects',{
    boxGlow:types.rgba(),
    swooshScale:types.number(1,{nudgeMultiplier:0.01}),
    swooshPosition:types.number(0,{nudgeMultiplier:0.01}),
    swooshOpacity:types.number(1,{nudgeMultiplier:0.01})
})
boxEffectsObj.onValuesChange((values)=>{
    boxMaterial.emissive.setRGB(values.boxGlow.r,values.boxGlow.g,values.boxGlow.b);
    swooshEffect.scale.setY(values.swooshScale);
    swooshEffect.position.setY(values.swooshPosition);
    swooshMaterial.opacity=values.swooshScale;
})

Эффект комического текста

Пришло время добавить комический текстовый эффект: «Блин!»

Импортируйте {CSS2DRenderer,CSS2DObject} из THREE и создайте textRenderer. Давайте установим для style.position значение «absolute» и обновим domElement в orbitControls.

Создайте новый объект CSS2Dobject, добавьте его на сцену, а затем добавьте HTML-элемент, представляющий его. Добавление текста в поле заставляет его следовать положению поля на экране.

<div id="boink">Boink!!</div>
import {CSS2DRenderer,CSS2DObject} from 'three/examples/jsm/renderers/CSS2DRenderer'
let textRenderer = new CSS2DRenderer();
textRenderer.setSize(window.innerWidth,window.innerHeight);
textRenderer.domElement.style.position = 'absolute';
textRenderer.domElement.style.top = "0";
textRenderer.domElement.style.left = "0";
textRenderer.domElement.style.width = "100%";
textRenderer.domElement.style.height = "100%";
textRenderer.domElement.style.zIndex = "2";
document.body.appendChild(textRenderer.domElement)

// OrbitControls
controls = new OrbitControls(camera, textRenderer.domElement);

// Text Effects
const boinkDom = document.getElementById('boink');
const boinkText = new CSS2DObject(boinkDom);
boinkText.position.set(-25,0,0)
box.add(boinkText);

// add this to your render()/tick() function
// textRenderer.render(scene, camera);

Создайте новый объект Theatre.js: textEffectObj с параметрами непрозрачности, текста и масштаба.

С помощью onValuesChange() обновите innerText элемента HTML. Это забавная особенность Theatre.js: его также можно использовать для изменения и анимации текста. Упорядочите все реквизиты и добавьте ключевой кадр, чтобы текст всплывал, когда коробка подпрыгивает.

const textEffectObj = sheet.object('text',{
    opacity:1,
    text:"",
    scale: 1
});

textEffectObj.onValuesChange((values)=>{
    if(!boinkDom)return;
    boinkDom.innerText = values.text;
    boinkDom.style.opacity = ""+values.opacity
    boinkDom.style.fontSize = ""+values.scale+"px";
})

Игра с положением указателя: Звуковые эффекты

Наконец, чтобы все оживить, давайте добавим звуковые эффекты. Я поискал на Pixabay несколько бесплатных звуков и импортировал их в проект. Затем я загрузил их с помощью Three.js AudioLoader. Вот как я добавляю звук в свои проекты Three.js:

// importing my sounds as urls
import swooshSound from '../assets/sounds/whoosh.mp3';
import boinkSound from '../assets/sounds/boink.mp3';
import thudSound from '../assets/sounds/loud-thud-45719.mp3';

const listener = new THREE.AudioListener();
const loader = new THREE.AudioLoader(loadingMgr);
let soundReady = false;
const swoosh = new THREE.Audio(listener)
const boink = new THREE.Audio(listener)
const thud = new THREE.Audio(listener)

setupSounds();

function setupSounds() {
  camera.add(listener);

  audioSetup(swoosh,swooshSound,0.3,loader)
  audioSetup(boink,boinkSound,0.2,loader)
  audioSetup(thud,thudSound,0.5,loader)
}

function audioSetup(sound:THREE.Audio, url:string, volume:number, loader:THREE.AudioLoader){
  loader.load(
    url,
    // onLoad callback
    function ( audioBuffer ) {
      sound.setBuffer( audioBuffer );
      sound.setVolume(volume)
      sound.loop=false;
    },
  );
}

После завершения настройки мы можем приступить к воспроизведению звуков в зависимости от положения указателя в последовательности. Мы можем добиться этого, используя хук onChange() и отслеживая изменения положения указателя, чтобы запускать воспроизведение звука через определенные промежутки времени.

// play the audio based on pointer position
onChange(sheet.sequence.pointer.position, (position) => {
    if(!soundReady)return;
    if(position > 0.79 && position < 0.83){
        if(!thud.isPlaying){
            thud.play();
        }
    }
    else if(position > 1.18 && position < 1.23){
        if(!boink.isPlaying){
            boink.play();
        }
    }
    else if(position > 0.00 && position<0.04){
        if(!swoosh.isPlaying){
            swoosh.playbackRate= 1.7;
            swoosh.play();
        }
    }
})

Чтобы добавить новый прослушиватель событий для «щелчка», установите soundReady в значение true и используйте sheet.sequence.play() для воспроизведения анимации с количеством итераций Infinity и диапазоном 0-2.

<style>
.enterSceneContainer{
  z-index: 4;
  position: absolute;
  display: block;
  width: 100%;
  height: 100%;
  text-align: center;
  transition: all 0.5s ease;
}
</style>
<div class="enterSceneContainer" id="tapStart">
    <p>Tap to start</p>
</div>
// Play sequence on click once all assets are loaded
const tapStart = document.getElementById('tapStart');

tapStart.addEventListener(
    'click',
    function () {
        soundReady = true;
        tapStart.style.opacity = "0";
        setTimeout(()=>{
          tapStart.style.display = "none";
        },400)
        sheet.sequence.play({ iterationCount: Infinity, range: [0, 2] });
    }
);

Тональное отображение и кодировщик

Чтобы улучшить цвета сцены, вы можете указать разные toneMappings и outputEncoding для рендерера.

Поэкспериментировав с различными параметрами, я решил установить для них значения LinearToneMapping и sRGBEncoding для этого конкретного проекта.

renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.LinearToneMapping;

Чтобы добавить туман и синхронизировать его с фоном сцены, вы можете включить соответствующий код в хук `colorObj.onValuesChange()`.

scene.fog = new THREE.FogExp2(0xffffff, 0.009);
colorObj.onValuesChange((values)=>{
    // @ts-ignore
    scene.fog.color = new THREE.Color(values.backgroundColor.toString());
    // ... rest of the code here ...
})

Развертывание в рабочей среде

Вот и все! Чтобы завершить проект, нам нужно экспортировать анимацию и развернуть ее в рабочей среде. Просто щелкните имя проекта в пользовательском интерфейсе студии и выберите «Экспорт в JSON», а затем сохраните состояние.

Импортируйте сохраненное состояние в main.js и в getProject передайте сохраненное состояние.

import projectState from '../assets/Saved_TheatreState.json';

project = getProject('TheatreTutorial_1', { state: projectState });

После того, как анимация будет экспортирована, вы можете удалить `studio import` и `studio.initialize()`, поскольку они не требуются для производства. Кроме того, вы можете удалить или включить их условно по мере необходимости.

let project;
// Using Vite
if (import.meta.env.DEV) {
    studio.initialize();
    // Create a project from local state
    project = getProject('TheatreTutorial_1');
}
else {
    // Create a project from saved state
    project = getProject('TheatreTutorial_1', { state: projectState });
}

Не забудьте проверить окончательный код. Кроме того, вы можете следовать видеоуроку, перейдя по этой ссылке.

Вот несколько вариантов надувного куба, анимированного с помощью theatre.js: