この解説はThree.jsでES2015のclassを利用する(継承)からの続きです。クラスのメソッドを呼び出すベスト・プラクティスな例を学んでいきましょう。

クラスのメソッドを利用する

時間経過でグループのアニメーションをさせたい場合の方法を紹介します。

時間経過はrenderer.setAnimationLoop()で管理するのが基本です。クラス内部で独自にrequestAnimationFrame()を回しはじめると、更新順序が分かりづらくなり、レンダリングとの前後関係も不明確になります。ループの起点は1箇所に絞りましょう。

悪い例

良くないコード例から見てみます。

/** メッシュを継承した独自グループのクラスです。 */
class MyGroup extends THREE.Object3D {

  /** コンストラクターです。 */
  constructor() {
    super();
    // 任意の処理
    this.update();
  }

  /** 更新命令を定義します。 */
  update() {
    requestAnimationFrame(this.update);
  }
}

// 独自グループを作る
const myGroup = new MyGroup();
scene.add(myGroup);

renderer.setAnimationLoop(tick);

// 毎フレーム時に実行されるループイベントです
function tick() {

  // レンダリング
  renderer.render(scene, camera);
}

良くないのは親となるコードはrenderer.setAnimationLoop()、子クラスはrequestAnimationFrame()で別々にループを持っているところです。うまく動く場合もありますが、どちらの更新が先に実行されるかを保証できません。結果として、renderer.render(scene, camera);の前後でMyGroupの更新がずれる可能性があります。

改善例

メインとなるコードに一つだけrenderer.setAnimationLoop()を用意し、そこからツリー構造で独自メソッドを呼び出すのが安全です。

良い例

/** メッシュを継承した独自グループのクラスです。 */
class MyGroup extends THREE.Object3D {

  /** コンストラクターです。 */
  constructor() {
    super();
    // 任意の処理
  }

  /** 更新命令を定義します。 */
  update() {
  }
}

// 独自グループを作る
const myGroup = new MyGroup();
scene.add(myGroup);

renderer.setAnimationLoop(tick);

// 毎フレーム時に実行されるループイベントです
function tick() {

  // 更新命令を実行します。
  myGroup.update();

  // レンダリング
  renderer.render(scene, camera);
}

こうすれば、ループの登録箇所はメインコードの1箇所だけになります。renderer.render(scene, camera);の前にMyGroupクラスのupdate()メソッドが実行されることを保証できます。