【Three.js】Tweenで複雑なアニメーション

JavaScript,Three.js

次はうまくやる・・・

はじめに

もっとおしゃれでカッコいいアニメーションができないものかと頭を悩ませていました。

そしたらTweenには"chain()"っていう関数があるそうじゃないですか。これを使うと2つのアニメーションをつなげる事ができるとか。さっそく使ってみました。

できた

デモサイトはこちら

アニメーションのコード

function clickButton() {

  TWEEN.removeAll();

  // ボタンのイベントリスナー削除(アニメーションの途中で押されると挙動がおかしくなるため)
  document.querySelector('.plus').removeEventListener('click', clickButton);

  // 開始時のカメラの動き
  var startCamera = new TWEEN.Tween(camera.position)
    .to({x: camera.position.x, y: -200, z: camera.position.z}, 1000)
    .easing(TWEEN.Easing.Quadratic.InOut)
    .start();
  
  // 終了時のカメラの動き
  var endCamera = new TWEEN.Tween(camera.position)
    .to({x: camera.position.x, y: 0, z: camera.position.z}, 1000)
    .easing(TWEEN.Easing.Quadratic.InOut);

  // 質問文を裏返す
  var reverse = new TWEEN.Tween(qObj.rotation)
    .to({x: Math.PI, y: qObj.rotation.y, z: qObj.rotation.z}, 1500)
    .easing(TWEEN.Easing.Exponential.InOut)
    .start();
  
  // 質問文(裏側)を裏返す
  var reverse2 = new TWEEN.Tween(qObj2.rotation)
    .to({x: Math.PI * 2, y: qObj2.rotation.y, z: qObj2.rotation.z}, 1500)
    .easing(TWEEN.Easing.Exponential.InOut)
    .start();

  // 尾翼を回転させる
  var rotateTail = new TWEEN.Tween(pObj.rotation)
    .to({x: Math.PI / 180 * 90, y: pObj.rotation.y, z: pObj.rotation.z}, 1000)
    .easing(TWEEN.Easing.Exponential.InOut)
    .start();

  // 尾翼を移動させる(上)
  var moveZTail = new TWEEN.Tween(pObj.position)
    .to({x: pObj.position.x, y: pObj.position.y, z: 16}, 1000);
  
  // 尾翼を移動させる(左)
  var moveXTail = new TWEEN.Tween(pObj.position)
    .to({x: 100, y: pObj.position.y, z: 16}, 1000);

  // 右大翼を広げる
  var moveZlwr = new TWEEN.Tween(lwrObj.position)
    .to({x: lwrObj.position.x, y: lwrObj.position.y, z: -1}, 100);
  var openlwr = new TWEEN.Tween(lwrObj.rotation)
    .to({x: lwrObj.rotation.x, y: lwrObj.rotation.y, z: Math.PI / 180 * 60}, 1000);

  // 左大翼を広げる
  var moveZlwl = new TWEEN.Tween(lwlObj.position)
    .to({x: lwlObj.position.x, y: lwlObj.position.y, z: -1}, 100);
  var openlwl = new TWEEN.Tween(lwlObj.rotation)
    .to({x: lwlObj.rotation.x, y: lwlObj.rotation.y, z: Math.PI / 180 * -60}, 1000);

  // 右小翼を広げる
  var moveZswr = new TWEEN.Tween(swrObj.position)
    .to({x: swrObj.position.x, y: swrObj.position.y, z: -1}, 100);
  var openswr = new TWEEN.Tween(swrObj.rotation)
    .to({x: swrObj.rotation.x, y: swrObj.rotation.y, z: Math.PI / 180 * -100}, 1000);

  // 左小翼を広げる
  var moveZswl = new TWEEN.Tween(swlObj.position)
    .to({x: swlObj.position.x, y: swlObj.position.y, z: -1}, 100);
  var openswl = new TWEEN.Tween(swlObj.rotation)
    .to({x: swlObj.rotation.x, y: swlObj.rotation.y, z: Math.PI / 180 * 100}, 1000);

  // 離陸
  var takeOff = new TWEEN.Tween(plane.position)
    .to({x: -2000, y: plane.position.y, z: 1000},4000)
    .easing(TWEEN.Easing.Exponential.In)
    .onComplete(function() {
      plane.position.x = 2000;
      if (qElem.textContent == 'Q.テイクアウトはできますか?') {
        qElem.textContent = 'A.できません';
        pElem.textContent = '-';
      } else {
        qElem.textContent = 'Q.テイクアウトはできますか?';
        pElem.textContent = '+';
      }
    });
  
  // 着陸
  var landing = new TWEEN.Tween(plane.position)
    .to({x: 0, y: plane.position.y, z: 0},4000)
    .easing(TWEEN.Easing.Exponential.Out);

  // 右の大翼を閉じる
  var openlwr2 = new TWEEN.Tween(lwrObj.rotation)
    .to({x: lwrObj.rotation.x, y: lwrObj.rotation.y, z: 0}, 1000);
  var moveZlwr2 = new TWEEN.Tween(lwrObj.position)
    .to({x: lwrObj.position.x, y: lwrObj.position.y, z: -11}, 1000);

  // 左の大翼を閉じる
  var openlwl2 = new TWEEN.Tween(lwlObj.rotation)
    .to({x: lwlObj.rotation.x, y: lwlObj.rotation.y, z: 0}, 1000);
  var moveZlwl2 = new TWEEN.Tween(lwlObj.position)
    .to({x: lwlObj.position.x, y: lwlObj.position.y, z: -11}, 1000);

  // 右小翼を閉じる
  var openswr2 = new TWEEN.Tween(swrObj.rotation)
    .to({x: swrObj.rotation.x, y: swrObj.rotation.y, z: 0}, 1000);
  var moveZswr2 = new TWEEN.Tween(swrObj.position)
    .to({x: swrObj.position.x, y: swrObj.position.y, z: -11}, 1000);

  // 左小翼を閉じる
  var openswl2 = new TWEEN.Tween(swlObj.rotation)
    .to({x: swlObj.rotation.x, y: swlObj.rotation.y, z: 0}, 1000);
  var moveZswl2 = new TWEEN.Tween(swlObj.position)
    .to({x: swlObj.position.x, y: swlObj.position.y, z: -11}, 1000);
  
  // 尾翼を移動させる(右)
  var moveXTail2 = new TWEEN.Tween(pObj.position)
    .to({x: 135, y: pObj.position.y, z: 16}, 1000);

  // 尾翼を移動させる(下)
  var moveZTail2 = new TWEEN.Tween(pObj.position)
    .to({x: pObj.position.x, y: pObj.position.y, z: 0}, 1000);

  // 尾翼を回転させる
  var rotateTail2 = new TWEEN.Tween(pObj.rotation)
    .to({x: 0, y: pObj.rotation.y, z: pObj.rotation.z}, 1000)
    .easing(TWEEN.Easing.Exponential.InOut);

  // 質問文を裏返す
  var reverse3 = new TWEEN.Tween(qObj.rotation)
    .to({x: 0, y: qObj.rotation.y, z: qObj.rotation.z}, 1500)
    .easing(TWEEN.Easing.Exponential.InOut)
    .onComplete(function() {
      document.querySelector('.plus').addEventListener('click', clickButton);
    });

  // 質問文(裏側)を裏返す
  var reverse4 = new TWEEN.Tween(qObj2.rotation)
    .to({x: Math.PI, y: qObj2.rotation.y, z: qObj2.rotation.z}, 1500)
    .easing(TWEEN.Easing.Exponential.InOut);

  /***** アニメーションをつなぐ *****/
  // 離陸前
  rotateTail.chain(moveZTail);
  moveZTail.chain(moveXTail);
  reverse.chain(openlwr, openlwl, openswr, openswl, moveZlwr, moveZlwl, moveZswr, moveZswl);
  moveXTail.chain(takeOff);

  // 離陸⇀着陸
  takeOff.chain(landing);

  // 着陸後
  landing.chain(openlwr2, moveZlwr2, openlwl2, moveZlwl2, openswr2, moveZswr2, openswl2, moveZswl2, moveXTail2);
  moveXTail2.chain(moveZTail2);
  moveZTail2.chain(rotateTail2, endCamera);
  rotateTail2.chain(reverse3, reverse4);
}

地味な作業をひたすら繰り返しています。翼の1枚1枚にTweenを使ってアニメーションを作っていって、最後に"chain()"でアニメーションをつなげます。

ポイントを解説します。

onComplete関数でアニメーションの終了をつかまえる事ができる

// 離陸
var takeOff = new TWEEN.Tween(plane.position)
  .to({x: -2000, y: plane.position.y, z: 1000},4000)
  .easing(TWEEN.Easing.Exponential.In)
  .onComplete(function() {
    plane.position.x = 2000;
    if (qElem.textContent == 'Q.テイクアウトはできますか?') {
      qElem.textContent = 'A.できません';
      pElem.textContent = '-';
    } else {
      qElem.textContent = 'Q.テイクアウトはできますか?';
      pElem.textContent = '+';
    }
  });

飛行機が離陸する"takeOff"という名前を付けたアニメーションです。(x:-2000, y:今と同じ位置, z:1000)の座標に4秒かけて移動します。

この中で使っている"onComplete()"はアニメーションが終わったときに実行される関数です。コレは便利ですね。なにやってるか紙に書いたのでそちらをご覧ください。

chain関数でアニメーション同士をつなげる事ができる

// 離陸⇀着陸
takeOff.chain(landing);

これで「"takeOff"アニメーションが終了したら"landing"アニメーションを開始してくださいねー」ということになります。

// 着陸後
landing.chain(openlwr2, moveZlwr2, openlwl2, moveZlwl2, openswr2, moveZswr2, openswl2, moveZswl2, moveXTail2);

“chain()"の引数には複数のアニメーションを指定することができます。この例だと"landing"が終了したら、引数で指定した9個のアニメーションが同時に開始されます。

そんな感じでアニメーションを組み立てていきましたが、途中でワケが分からなくなりました。右の翼の設定したつもりなのに左が動いてるとか、隠した翼が見えちゃうからz軸方向にも動かさないとダメじゃん!とかまぁ大変。

変数の名前が良くなかったのと、アニメーションを関数化できればもっと楽に作れたのかなぁと思います。でもそう思った時にはもう手遅れ。今から変数名変えたりとかイヤすぎるだろうが。なぜなら眠たいから。明日やれよって感じですが、コレを明日に持ち越すぐらいならこのまま突っ走ってしまおう、と。

そんなこんなで「とりあえずできた」感がすごくて、なんかモヤモヤ。次からは簡単な設計図みたいなの作ってからコード書くようにしようかなと思います。

あとがき

「完成させたい」という欲望に負けた結果、完成度の低いものができあがってしまいました。ちょっとスッキリしませんが、まぁまぁ面白いものは作れたかなぁと思います。予想外の動きで「おっ!?」って言わせるようなwebサイトを作ってみたいなぁと思う午前3時なのでした。ありがとうございました。

おしゃれ度

★★★☆☆

Posted by ナカタ