📓

【cluster】CICrafterとかで使いやすいスクリプト置き場

2024/07/04に公開

Unityを使わず、動く床アイテムを作成
の記事みたいな感じで、CICrafterで使えるスクリプトをただポンポン置いていきます。
(別にCICrafterでなくても、普通にクラフトアイテムとかで使えますが)

01くるくる回転


SubNodeを使う必要があります(SubNodeの入れ方は上記記事参照)。
hayasaとjikuは変更可能。

const froce = 30;
const hayasa = 72.0;
const jiku = new Vector3(0.0, 1.0, 0.0);

const node = $.subNode("SubNode000");

$.onStart(() => {
  $.state.tick = 0;
});

$.onUpdate(deltaTime => {
  $.state.tick += deltaTime*hayasa;
  let kaku = jiku.clone().multiplyScalar($.state.tick);
  let quat = new Quaternion().setFromEulerAngles(kaku);
  node.setRotation(quat);
});

02 ベータ機能を使わない「プレイヤーを追いかける」

【ベータ機能】近くのプレイヤーについてくるペットをつくる(プレイヤー情報取得)
こちらの記事に近いものを、ベータ機能を使わずに実現するスクリプトです。
元々はforceは30でしたが、こちらでは1くらいがいいでしょうね。

const force = 1;
const maxDistance = 10;
const minDistance = 1;


const rad2deg = (rad) => rad * 180 / Math.PI;


$.onUpdate(deltaTime => {
    let position = $.getPosition()


    let players = $.getPlayersNear(position, maxDistance);


    let targetPlayer = null;
    let targetDistance = Infinity;


    // それぞれのプレイヤーとの距離を計算し、最も近いものを探す
    players.forEach(player => {
        let playerPosition = player.getPosition();
        let distance = playerPosition.clone().sub(position).length();


        if (distance < targetDistance) {
            targetDistance = distance;
            targetPlayer = player;
        }
    });


    let player = targetPlayer;
    if (player == null) return;

    // 追跡中のプレイヤーがログアウトなどでいなくなっていた場合
    if (player.exists() == false) return;

    let position = $.getPosition();

    // プレイヤーがいる方向の角度を計算してY軸回転
    let direction = player.getPosition().clone().sub(position);
    let angle = rad2deg(Math.atan2(direction.x, direction.z)); // atan2は弧度法での角度を返すため、度数法に変換する
    let rotation = new Quaternion().setFromEulerAngles(new Vector3(0, angle, 0));

    $.setRotation(rotation);

    // プレイヤーと一定より離れているときに、正面方向に押し出す力を加えて前進
    if (direction.length() > minDistance) {
        $.setPosition(position.add(new Vector3(0, 0, force*deltaTime).applyQuaternion(rotation)));
    }
});

03 近づくと追いかけてきて、話しかけてくるNPC (Unity向け?)

これはテキストを使う関係でUnityで作ったほうがいいかもしれません。CICrafterでもできるとは思います。

https://cluster.mu/w/fce9f2be-3dca-4763-b6fe-1ff9089217ce
サンプルはムダにホラーっぽい。どうしてこうなった。



↑こんな感じで、ScriptableItemの子に、「text」という名のTextViewがついた「子」をつけておく必要があります。
ScriptableItemには、セットでMovableItemもつけてください(動くので)。セットでつけられるRigidBodyの「重力を使用」はOFF、IsKinematicはONです。重力ではなく、スクリプトで全部動きをコントロールする時の定番設定ですね。

そして、以下のスクリプトをScriptableItemにつけます。
talkListの内容は好きにいじってください。
ある程度近くなるとユーザーに近づいてきて、ランダムでテキストを表示し続けます。

//表示するテキストの候補(増やすことも可能。カンマで区切るのを忘れずに)
const talkList = [
  "こんにちは",
  "何か面白いことはありましたか?",
  "私は今日も元気です!",
  "ようこそ!",
  "お疲れ様です!",
  "ゆっくりしていってください!"
];

const forwardSpeed = 0.5; //移動スピード
const talkBorder = 3.0; //どの距離で話しかけるか
const nearLimit = 1.5; //どの距離まで近づくか
const findLimit = 5.0; //どの距離まで探すか
const talkTickPer = 3.0; //話しかける間隔

//テキストを表示する「子」
const textObj = $.subNode("text");
//テキストを表示したとき、どれだけずらすか
const textMoveVec = new Vector3(0, -0.3, 0);

$.onStart(() => {
  $.state.findTick = 0;
  $.state.talkTick = 0;
  $.state.chosenTalkNo = -1;
  $.state.textMoveTick = 0;
  $.state.textFirstPos = textObj.getPosition();
  $.state.textClearTick = 0;
});

$.onUpdate((deltaTime) => {
  let currentPos = $.getPosition();
  let targetPos = null;
  let distance = Infinity;

  $.state.findTick += deltaTime;
  //0.3秒に1回だけ近くのプレイヤーを探す
  if ($.state.findTick >= 0.3) {
    $.state.findTick -= 0.3;

    let players = $.getPlayersNear($.getPosition(), findLimit);
    if (players.length > 0) {
      let result = null;
      players.forEach((player) => {
        let pPos = player.getPosition();
        let d = currentPos.clone().sub(pPos).length();
        if (d < distance) {
          distance = d;
          result = player;
          targetPos = pPos;
        }
      });
      $.state.player = result;
    } else {
      //誰も見つからなかったなら、終わり
      $.state.player = null;
      return;
    }
  } else {
    //まだ誰も見つかっていないなら、終わり
    if ($.state.player == null) {
      return;
    }
    //すでに見つけたプレイヤーがいるなら、その位置を取得
    targetPos = $.state.player.getPosition();
    distance = currentPos.clone().sub(targetPos).length();
  }

  //atan2で角度(ラジアン)を求める(くるくる回るだけなので、XとZの差から角度を求める)
  let angle = Math.atan2(targetPos.x - currentPos.x, targetPos.z - currentPos.z);
  //向きを変更(角度をラジアンから度に変換し、Yの値を求める)
  $.setRotation(new Quaternion().setFromEulerAngles(new Vector3(0, angle * 180 / Math.PI, 0)));

  //移動する長さはdeltaTimeで移動速度を調整。deltaTimeには前回のUpdateからの時間が入っている
  //deltaTimeは、たいていは約0.03(30FPS)、約0.015(60FPS)などの値が入る
  //これを考慮しないと、モバイル版など30FPSの環境とPCなど60FPSの環境で移動速度が変わってしまう
  let moveLength = forwardSpeed * deltaTime;

  //近づきすぎないようにする
  if (distance - moveLength < nearLimit) {
    moveLength = distance - nearLimit;
  }
  //そもそも移動の必要がないほど近づいているなら、何もしない
  if (moveLength > 0) {
    //角度から進行方向を求める
    let forward = new Vector3(0, 0, 1).applyQuaternion($.getRotation());
    //現在の位置に進行方向をかけて移動
    let movedPos = currentPos.add(forward.multiplyScalar(moveLength));
    $.setPosition(movedPos);
  }

  //話しかける距離に近づいたら、話しかける
  if (distance <= talkBorder) {
    talkProc(deltaTime);
  }

  //テキストが表示される度に、テキストを動かす
  if ($.state.textMoveTick > 0) {
    $.state.textMoveTick -= deltaTime * 5;
    if ($.state.textMoveTick <= 0) {
      $.state.textMoveTick = 0;
    }

    //徐々に初期位置に近づけていく
    let easedVal = easeOutQuint($.state.textMoveTick);
    //最初はtextMoveVecぶんだけ丸ごとズレているが、それを徐々に元の位置に戻す
    //そのとき、イージング関数を使って、なめらかに値を戻すようにする
    let textMoveCurrentVec = textMoveVec.clone().multiplyScalar(easedVal);
    textObj.setPosition($.state.textFirstPos.clone().add(textMoveCurrentVec));
  }

  //一定時間が経過したら、テキストを消す
  if ($.state.textClearTick > 0) {
    $.state.textClearTick -= deltaTime;
    if ($.state.textClearTick <= 0) {
      textObj.setText("");
    }
  }
});

//なめらかに移動するためのイージング関数
const easeOutQuint = (x) => {
  return 1 - Math.pow(1 - x, 5);
}

const talkProc = (deltaTime) => {
  $.state.talkTick -= deltaTime;
  //まだ話しかける時間ではないなら、終わり
  if ($.state.talkTick > 0) {
    return;
  }
  $.state.talkTick += talkTickPer;

  let no;

  //リストからランダムに選ぶ
  //ただし、前回と同じものを選ばないようにする
  do {
    no = Math.floor(Math.random() * talkList.length);
  } while (no == $.state.chosenTalkNo);
  $.state.chosenTalkNo = no;

  let chosenText = talkList[no];
  //テキストを表示
  textObj.setText($.state.player.userDisplayName + "さん、" + chosenText);

  //テキストを動かす+消すための変数を設定
  $.state.textMoveTick = 1;
  $.state.textClearTick = 5;
};

Discussion