動くクラフトアイテムをつくってみよう!

10/20のCluster Conference 2022で新機能「スクリプト」が発表されました!Cluster Creator KitやワールドクラフトでJavaScriptを使えるようになりました。
スクリプトを使うことで、ワールドクラフトでも「動くクラフトアイテム」をつくれるようになります!

今回はワールドクラフトでのスクリプト付きアイテムのつくり方を紹介します!

Cluster Creator Kitでの使い方は下記の記事をご覧ください!

「Scriptable Item」をつけたクラフトアイテムをアップロードする

動くクラフトアイテムにするためには、対象のクラフトアイテムに「Scriptable Item」がついている必要があります。
「Scriptable Item」をアップロードすることで、そのアイテムをワールドクラフト上でも編集することができるようになります。

クラフトアイテムのアップロード等については、下記の記事を参考にして下さい。

ワールドクラフトを開く

ワールドクラフトでもスクリプトを編集することができます。

ワールドクラフト上でのスクリプト編集はPC版のみの機能になります

  • まずはいつも通りワールドクラフトを開いてください!
  • 開いたら先ほどアップロードしたクラフトアイテムを設置しましょう。
    • アップロードしたクラフトアイテムはストアに公開する前に自分だけが使えるようになります

「設定」をしてスクリプトエディタを使えるようにする

準備できたら、ワールドクラフト内でスクリプトを編集できるようにスクリプトエディタが使えるように設定していきます。

  • 左上の三本線からメニューを開いてください。
  • メニューから「設定」を選択してください。
  • 「設定」内の「その他」を選択すると「ワールド/アイテムクリエイター向け機能」というカテゴリがあるので、その中の「コンソール」と「スクリプトエディタ」をオン(青色になっている状態)にしてください。

これで設定は完了です!
スクリプトを編集したいクラフトアイテムを注視すると、編集が可能なクラフトアイテムは案内が出ます。

Scriptable Itemがついていないクラフトアイテムを注視しても案内は出ません!

F12を押すと左上にスクリプトエディタが表示されます。こちらでコードを編集していきます。

改変して使えるサンプルコードを用意しました!

リリースに伴い、改変して使えるサンプルコードを用意しました。
「動くクラフトアイテムってどんなのができるんだろう?」ってすぐに試してみたい方はこちらのコードを改変して試してみてください(使用するには少し改変が必要なので、そちらもチェックを忘れずに!)。
改変していくことで、どのように書けばいいのか分かるようになるかもしれません。

ドア

△タップやクリックをすると扉が開閉するようになっています
△「Scriptable Item」は一番親のオブジェクトにつけてください
const door = $.subNode("Model");
const axis = new Vector3(0, 1, 0);

$.onInteract(() => {
  let isOpen = $.state.isOpen;
  isOpen = !isOpen;
  $.state.isOpen = isOpen;
  const rot = new Quaternion()
    .setFromAxisAngle(axis, isOpen ? -90 : 0);
  door.setRotation(rot);
});

△インタラクションのシンプルなサンプルです。間の動きはないので、 onInteract 内で処理しています。

移動する床

△左右の動きを繰り返す内容になっています(方向を変えれば前後移動でも使えます)
△「Scriptable Item」は一番親のオブジェクトにつけてください
const tile = $.subNode("Tile");
const period = 3;
const width = 4;

const trapezoidalWave = (t) => {
  if (t < 0.25) {
    return t * 4;
  } else 
  if (t < 0.5) {
    return 1;
  } else
  if (t < 0.75) {
    return 3 - t * 4;
  } else {
    return 0;
  }
};

$.onUpdate(deltaTime => {
  let time = $.state.time ?? 0;
  time += deltaTime;
  $.state.time = time;
  const pos = new Vector3(
    trapezoidalWave(time % period / period) * width - width / 2, 
    0, 
    0);
  tile.setPosition(pos);
});

△ループの動きのサンプルです。onUpdate内で毎フレームの位置を更新しています。

エレベーター

△動き方としては扉の開閉と上下の移動を繰り返す内容になっています
△「Scriptable Item」は一番親のオブジェクトにつけてください
const container = $.subNode("Container");
const doorLeft = $.subNode("Door_L");
const doorRight = $.subNode("Door_R");

const waitTimeSec = 10;
const transitTimeSec = 10;
const period = (waitTimeSec + transitTimeSec) * 2;

const targetHeight = 4;
const offsetHeight = 0.275;
const doorWidth = 0.5;

const easeInOutQuad = (t) => {
  return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
};

const upwardsCagePosYRatio = (time) => {
  if (time <= waitTimeSec) {
    return 0;
  } else {
    return easeInOutQuad((time - waitTimeSec) / transitTimeSec);
  }
};

const cagePosYRatio = (time) => {
  time = time % period;
  if (time < period / 2) {
    return upwardsCagePosYRatio(time);
  } else {
    return 1 - upwardsCagePosYRatio(time - period / 2);
  }
};

const doorPosXRatio = (time) => {
  time = time % (period / 2);
  if (time <= waitTimeSec) {
    return 1;
  } else {
    return 0;
  }
};

$.onUpdate(deltaTime => {
  let time = $.state.time ?? 0;
  time += deltaTime;
  $.state.time = time;
  container.setPosition(
    new Vector3(
      0, 
      cagePosYRatio(time) * targetHeight + offsetHeight, 
      0));
  doorLeft.setPosition(
    new Vector3(doorPosXRatio(time) * doorWidth, 0, 0));
  doorRight.setPosition(
    new Vector3(doorPosXRatio(time) * -doorWidth, 0, 0));
});

△ループの動きを少し発展させたサンプルです。easing をかけると動きがそれっぽくなります。

倒れる壁

△タップやクリックをすると壁が倒れるようになっています
△「Scriptable Item」は一番親のオブジェクトにつけてください
const container = $.subNode("Container");
const minAngle = 0.0;
const maxAngle = 90.0;
const openTimeSec = 0.8;
const closeTimeSec = 2;

const lerp = (a, b, t) => {
  if (b == a) return a;
  t = Math.min(Math.max(t, 0), 1);
  return a + t * (b - a);
};

const easeInQuart = (t) => {
  return t * t * t * t;
};

const easeOutBounce = (t) => {
  const n1 = 7.5625;
  const d1 = 2.75;
  
  if (t < 1 / d1) {
      return n1 * t * t;
  } else if (t < 2 / d1) {
      return n1 * (t -= 1.5 / d1) * t + 0.75;
  } else if (t < 2.5 / d1) {
      return n1 * (t -= 2.25 / d1) * t + 0.9375;
  } else {
      return n1 * (t -= 2.625 / d1) * t + 0.984375;
  }
};

const easeInBounce = (t) => {
  t = Math.min(t, 1);
  return 1 - easeOutBounce(1 - t);
};

$.onUpdate(deltaTime => {
  let time = $.state.time ?? 0;
  time += deltaTime;
  $.state.time = time;

  let angle = minAngle;
  if ($.state.open) {
    angle = lerp(minAngle, maxAngle, easeInQuart(time / openTimeSec));
    if (time > 5) {
      $.state.time = 0;
      $.state.open = false;
    }
  } else {
    angle = lerp(maxAngle, minAngle, easeInBounce(time / closeTimeSec));
  }

  const rot = new Quaternion()
    .setFromEulerAngles(new Vector3(angle, 0, 0));
  container.setRotation(rot);
});

$.onInteract(() => {
  if (!$.state.open && $.state.time > closeTimeSec) {
    $.state.time = 0;
    $.state.open = true;
  }
});

△ドアを更に発展させたようなイメージです。こちらも easing をかけて動きに緩急をつけています。

記事をシェアしてワールド制作を盛り上げよう!