[JavaScript] Three.jsを使った3Dキャラクター移動と衝突判定の実装方法

はじめに

Three.js にハマり今日も開発を続行、ブロック崩しゲームの開発が止まりそうな勢いであれこれ試してます。
昨日、衝突判定を作りましたが、壁に当たった後、進行を止める処理をやってなかったので実装。

ただ、普通に判定処理をすると壁にめり込んで、身動きが出来なくなる問題があり、思い付きで先読み処理など追加で何とか動くようになったので、メモも含めて記事に残しておきます。

実際の動作画面は以下で今日も、GIFアニメにしてみました。

[JavaScript] Three.jsを使った3Dキャラクター移動と衝突判定の実装方法

その他、画像のようにBlenderを使った、キャラクター&アニメーションも実装したので、それも別の記事でまとめる予定です。
一度挫折したBlenderをまた触ると思いませんでした・・・。
3Dはモデリングの処理まで手を出す必要があるので、物凄く大変ですね。。
AIに聞きながらなんとかアニメーションを実装しています。

それ以外にも、カメラの回転とそれにあわせたキャラクターの進行方向を変えるなど、四苦八苦しながら実装しています。

ただ、モデリングのanimationや表示まで出来るようになったのでもう、ここまでのスキルでかなりの事が出来るようになったと思います。

1. キャラクターの移動処理

今回は、Three.jsを使った3Dキャラクターの移動と衝突判定を実装する方法について解説します。特に、衝突時の反発処理と先読み衝突判定を利用して、キャラクターが壁にめり込まないようにする方法に焦点を当てます。

この記事では、以下の2つの主要な要素を実装しました:

  1. キャラクターの移動処理
  2. 衝突判定とその後の反発処理

まず、基本的な移動処理を実装しました。キャラクターはWASDキーで進行方向を指定し、カメラの向きに応じて動きます。移動は、THREE.Vector3を使ってベクトル計算で行い、移動速度を掛けてキャラクターを動かします。

export function moveCharacter() {
  let moveDirection = new THREE.Vector3(0, 0, 0); // 移動方向のベクトル

  // カメラとキャラクターの位置差分を計算
  const directionToCamera = new THREE.Vector3();
  directionToCamera.subVectors(config.camera.position, config.character.box.position);
  directionToCamera.y = 0; // 上下方向は無視(平面移動のみを考慮)

  // 移動方向を設定(カメラの位置に基づいて)
  if (config.keyState["ArrowUp"] || config.keyState["KeyW"]) {
    moveDirection = directionToCamera.clone().negate(); // カメラ方向の逆
  }
  if (config.keyState["ArrowDown"] || config.keyState["KeyS"]) {
    moveDirection = directionToCamera.clone(); // カメラ方向に進む
  }

  // 進行方向が計算されたら、移動ベクトルを正規化して速度を掛けて移動
  if (moveDirection.length() > 0) {
    moveDirection.normalize().multiplyScalar(config.character.speed);
    config.character.box.position.add(moveDirection);

    // キャラクターの回転
    const moveAngle = Math.atan2(moveDirection.x, moveDirection.z);
    config.character.box.rotation.y = moveAngle;
  }
}

ここでは、カメラの向きに合わせてキャラクターが移動するようにしています。directionToCameraでキャラクターとカメラの位置差を計算し、そのベクトルに基づいて進行方向を決めています。


2. 衝突判定と反発処理

次に、衝突判定を追加しました。キャラクターが壁に衝突した場合に壁にめり込むのを防ぐため、衝突が発生した時に反発ベクトルを計算して、キャラクターを壁から押し戻します。

先読み衝突判定

先に進行方向を予測し、衝突するかどうかを事前に確認することで、めり込みを回避します。この方法では、移動する前に衝突判定を行い、衝突がない場合にのみキャラクターを移動させます。

function checkPreCollision(moveDirection) {
  const predictedPosition = config.character.box.position.clone().add(moveDirection);

  const predictedBox = new THREE.Box3().setFromObject(config.character.box); // 現在の衝突範囲を取得
  predictedBox.setFromCenterAndSize(predictedPosition, new THREE.Vector3(1, 2, 1)); // 仮の位置に衝突範囲を設定

  // 衝突する場合は進まない
  if (predictedBox.intersectsBox(wallBox)) {
    return false; // 衝突あり、移動しない
  }

  return true; // 衝突なし、移動可能
}

この関数では、移動先の位置を予測し、その位置で衝突判定を行っています。衝突しない場合はキャラクターが進み、衝突する場合は移動がキャンセルされます。

衝突後の修正

もし衝突が予測された場合、キャラクターを反発方向に少し戻すことで、壁にめり込まずに進むようにします。

if (checkPreCollision(moveDirection)) {
  config.character.box.position.add(moveDirection);
} else {
  // 衝突した場合、進行方向の逆方向に少し戻す
  const reverseDirection = moveDirection.clone().negate();
  config.character.box.position.add(reverseDirection.multiplyScalar(0.5)); // 少しだけ戻す
}

これで、キャラクターは壁に衝突した場合でもめり込まず、壁の隙間に少し戻ることで自然に動き続けることができます。


3. アイテム取得用の衝突判定

衝突判定はアイテムの取得にも利用できます。壁との衝突判定と似たように、アイテムがキャラクターと衝突した場合にアイテムを取得する処理を追加しました。

export function checkItemCollision() {
  if (characterBox.intersectsBox(itemBox)) {
    const characterCenter = new THREE.Vector3();
    const itemCenter = new THREE.Vector3();

    characterBox.getCenter(characterCenter);
    itemBox.getCenter(itemCenter);

    const direction = new THREE.Vector3();
    direction.subVectors(characterCenter, itemCenter);

    // アイテム取得処理
    console.log("アイテム取得!");

    return true;
  }
  return false;
}

この関数では、キャラクターがアイテムに衝突したかを判定し、衝突した場合はアイテム取得処理を実行します。


まとめ

このプロジェクトでは、Three.jsを使ったキャラクターの移動と衝突判定を実装しました。以下が主なポイントです:

  1. 移動処理:カメラの向きに合わせてキャラクターを移動。
  2. 先読み衝突判定:移動前に衝突を予測して、キャラクターのめり込みを防ぐ。
  3. 反発処理:壁に衝突した際にキャラクターを反発させる。
  4. アイテム取得:衝突判定を使って、アイテムを取得する処理。

これで、めり込みや壁の貫通を回避しながら、スムーズにキャラクターが移動できるようになりました。また、アイテム取得にも衝突判定を活用できることが分かりました。


次のステップ

  • さらに複雑な衝突判定や、物理エンジンを使ったリアルな挙動を追加することができます。
  • アニメーションやカメラ制御の改善もできるので、次のステップとして追加していくと良いでしょう。