Chapter 99

トゥイーン

miku
miku
2021.11.23に更新

今までオブジェクトを動作させるのにパーティクルのようなランダムな動きを多用していたが、この座標からあの座標まで何秒で移動させる、のような正確な動きを指定したい場合がある。このような動きを作るシステムのことをトゥイーンと呼ぶ。

トゥイーン

const obj = {
  x: 100,
  y: 100,
  r: 100,
};

Tween.play(obj, {}, { x: 300, y: 300 }, 1);

トゥイーンの対象となるオブジェクトを用意する。

Tween.play(obj, from, to, time) で、time 秒掛けてオブジェクトのプロパティを現在の値から to の値に変化させる。上記コード例だと、1秒掛けて obj.x, obj.y の値を現在の値から { x: 300, y: 300 } に変化させる。変化の方法は線形補間である。obj には r というプロパティもあるが、tor が書かれていないので、このトゥイーンによって r は何も変化しない。

const obj = {
  x: 100,
  y: 100,
  r: 100,
};

Tween.play(obj, { r: 10 }, { x: 300, y: 300, r: 200 }, 1);

Tween.play(obj, from, to, time)from にプロパティ名と値が書かれると、それを初期値としてトゥイーンさせる。上記コード例だとトゥイーン開始時に obj.r10 になり、1秒かけて r200 になるように線形補間される。x, y は先程と同様の動きになる。

トゥイーンの実装

const Tween = {
  data: [],

  play: function (obj, from, to, time) {
    for (const key of Object.keys(to)) {
      if (!(key in from)) {
        from[key] = obj[key];
      }
    }
    Tween.data.push({ obj, from, to, time: time * 1000, startTime: performance.now() });
  },
}

複数のトゥイーンを管理できるように data[] を定義する。

Tween.play(obj, from, to, time) を呼び出すと、to に書かれているプロパティがトゥイーンの対象になるので、Object.keys(to) でキー名を一つずつ取り出し、from にそのキーが書かれていないならば初期値は現在のオブジェクトの値になるので from[key] = obj[key] のようにコピーする。

トゥイーンデータを data[] に追加するために Tween.data.push({ obj, from, to, time: time * 1000, startTime: performance.now() }) を実行する。startTime には現在の経過時間を入れておく。performance.now() の単位はミリ秒なので、指定した時間である time の値も 1000 を掛けて単位を秒からミリ秒に変換しておく。

function draw() {
  Tween.update();
}

const Tween = {
  // 省略

  update: function () {
    for (const data of Tween.data) {
      const elapsedTime = performance.now() - data.startTime;
      const ratio = min(elapsedTime / data.time, 1);

      for (const key of Object.keys(data.to)) {
        data.obj[key] = lerp(data.from[key], data.to[key], ratio);
      }

      if (ratio === 1) {
        Tween.data.splice(i, 1);
        i--;
      }
    }
  },
};

Tween.update() では Tween.data から一つずつトゥイーンデータを取り出し経過時間に応じたプロパティの線形補間を行う。

const elapsedTime = performance.now() - data.startTime;

performance.now() - data.startTimedata.startTime からの経過時間を取得する。

const ratio = min(elapsedTime / data.time, 1);

線形補間で必要な0~1の割合を取得するため、elapsedTime / data.time でトゥイーン時間に対する経過時間の割合を計算する。1 を超えてしまうと to に指定した値を超えてしまうため 1 より大きい値にならないようにクランプする。

for (const key of Object.keys(data.to)) {
  data.obj[key] = lerp(data.from[key], data.to[key], ratio);
}

トゥイーン対象となるキーを一つずつ取り出し線形補間を行う。

トゥイーンの実装例

const obj = {
  x: 100,
  y: 100,
  r: 100,
};

function setup() {
  createCanvas(windowWidth, windowHeight);
  stroke(240);
  noFill();

  Tween.play(obj, { r: 10 }, { x: 300, y: 300, r: 200 }, 30);
}

function draw() {
  Tween.update();

  clear();
  circle(obj.x, obj.y, obj.r * 2);
}

const Tween = {
  data: [],

  play: function (obj, from, to, time) {
    for (const key of Object.keys(to)) {
      if (!(key in from)) {
        from[key] = obj[key];
      }
    }
    Tween.data.push({ obj, from, to, time, startTime: performance.now() });
  },

  update: function () {
    for (const data of Tween.data) {
      const elapsedTime = performance.now() - data.startTime;
      const ratio = min(elapsedTime / (data.time * 1000), 1);

      for (const key of Object.keys(data.to)) {
        data.obj[key] = lerp(data.from[key], data.to[key], ratio);
      }

      if (ratio === 1) {
        Tween.data.splice(i, 1);
        i--;
      }
    }
  },
};