【pixi.js】一番簡単な当たり判定を作ってみる

javascript

ゴール判定

こんにちは。継続の錬金術士なおキーヌです。

ブログ毎日更新は101日目になります。

ゲーム作り入門者向けのpixi.js講座【衝突判定編】は全5回になります。

本格的な当たり判定は前回記事の最後に説明したモートン序列を使ったほうが処理は軽くなるのですが、
ゲーム作り初心者のうちはまだうまく扱えないと思うので、シンプルな当たり判定の作り方から学んでいきましょう。

当たり判定についてのおさらい

判定 おさらい

当たり判定は矩形どうしが重なった時に判定されるものということは、前回で理解していただけたと思います。

円形でも問題ないですが、円の場合は少し複雑な計算式になるので、
今回は最も簡単なである矩形を使った当たり判定を解説します。

わかりやすいように1画面アクションでプレイヤーが、
ゴール地点に触れたらクリアというものを作ってみましょう。

ジャンプ処理を作ったときのソースコードを利用していきますので、コピペしてください。

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>pixi.js v4 テスト</title>
    <style>
      * { margin:0; padding:0; }
    </style>
  </head>
  <body>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.5.1/pixi.min.js"></script>
      <script>
        // フラグの定義
        keyFlag = 0;
        jumpFlag = 0;

        // 【追加】ジャンプ用カウント
        jumpCount = 0;

        // 【追加】ジャンプ用配列
        jumpSpeed = [31,29,27,25,23,21,19,17,15,13,11,9,7,5,3,1,1,3,5,7,11,13,15,17,19,21,23,25,27,29,31]

        // pixi.jsのアプリケーションを作成
        const app = new PIXI.Application();

        // bodyにpixi.jsのview(ステージ)を追加する
        document.body.appendChild(app.view);

        // pixi.jsのGprahicsオブジェクトのインスタンスを生成
        const square = new PIXI.Graphics();

        // squareの大きさと位置を設定
        square.width = 50;
        square.height = 50;
        square.x = 50;
        square.y = 550;

        // squareの塗りつぶしと矩形描写
        square.beginFill(0xff00ff);
        square.drawRect(0,0,50,50);
        square.endFill();

        // ステージにsquareとgoalを追加
        app.stage.addChild(square);

        // ステージにsquareを追加
        app.stage.addChild(square);

        // ループ処理の実装
        app.ticker.add(delta => this.gameloop(delta,square));

        // イベントリスナー登録
        window.addEventListener("keydown", (event) => { this.downHandler(event) },false);
        window.addEventListener("keyup", (event) => { this.upHandler(event) },false);

        // downHandlerを定義
        // 【更新箇所】上下の移動を消してxキーによるy座標の減算を記載
        function downHandler(event) {
          switch(event.key) {
            case 'ArrowRight':
            keyFlag = 1;
            break;
            case 'ArrowLeft':
            keyFlag = 2;
            break;
            case 'x':
            keyFlag = 3;
            // ジャンプ中じゃなければジャンプフラグを1にする
            if(jumpFlag === 0) jumpFlag = 1;
          }
        }

        // upHandlerを定義
        function upHandler(event) {
          keyFlag = 0;
        }

        // ゲームループ関数の中身(毎フレーム実行される)
        function gameloop(delta, square) {
          // キー入力監視
          switch(keyFlag) {
            case 1:
              square.x += 10;
              break;
            case 2:
              square.x -= 10;
              break;
            default:
              break;
          }
          // 【追加】ジャンプ中フラグによる処理の切り替え
          switch(jumpFlag) {
            case 1:
              // ジャンプ中になったら
              square.y -= jumpSpeed[jumpCount];
              jumpCount++;
              if(jumpCount === 15) {
                jumpFlag = 2;
              }
              break;
            case 2:
              // 落下中になったら
              square.y += jumpSpeed[jumpCount];
              jumpCount++;
              if(jumpCount === 30) {
                jumpFlag = 0;
                square.y = 550;
                jumpCount = 0;
              }
              break;
            default:
              break;
          }
        }

      </script>
    </body>
  </html>
</html>

ゴールポイントの矩形を作る

では、このソースコードを元にゴールポイントを作ります。

今見たら前回までの矩形のサイズが50×50にしているのに塗りつぶしを100×100にしていましたね。

失礼しました!50×50に修正しておきました。
そのままだと浮いて見えるのでついでにY座標も50加算しました。

それでは、わかりやすいようにプレイヤーと同じPIXI.Graphicsを使って矩形を新たに作り、
プレイヤーと見分けがつくように色とサイズを変更しておきましょう。

~~~省略~~~
        // pixi.jsのGprahicsオブジェクトのインスタンスを生成
        const square = new PIXI.Graphics();
        const goal = new PIXI.Graphics(); // 追加分
~~~省略~~~

        // 【追加】goalの大きさと位置を設定
        goal.width = 100;
        goal.height = 20;
        goal.x = 650;
        goal.y = 500;

        // 【追加】goalの塗りつぶしと矩形描写
        goal.beginFill(0x00ffff);
        goal.drawRect(0,0,100,20);
        goal.endFill();

        // ステージにsquareとgoalを追加
        app.stage.addChild(square);
        app.stage.addChild(goal);

~~~省略~~~

開始地点はプレイヤーが左端にいてゴールが右端にあるように座標の指定をしています。
もちろん逆にしてもかまいませんが、説明は私の書いている方でしますので注意してください。

しかし配置しただけでは当たり判定はまだつけていないので重なり合っても衝突したと判定できません。

当たり判定を作ってみる

それではプレイヤーとゴールに当たり判定をつけてみましょう。
少しコードが長くなりますが、コメントで細かく解説していくので安心してください。

        // 衝突監視関数
        function collisionJudge() {
          // プレイヤーの右端がゴールの左端以上 かつ プレイヤーの左端がゴールの右端以内であればY座標の判定に入る
          if( (square.x + square.width) >= goal.x && square.x <= (goal.x + goal.width) ) {
            // プレイヤーの下端がゴールの上端以上 かつ プレイヤーの上端がゴールの下端以内であればヒットしているとみなす
            if( (square.y + square.height) >= goal.y && square.y <= (goal.y + goal.height) ) {
              return true;
            }
          }
          return false;
        }

コメントにも説明は書いていますが当たり判定の作り方を簡単に文字で説明すると、
オブジェクト(今回で言うとプレイヤーとゴールの矩形)の左上が始点で右下が終点ですね。

オブジェクトは始点座標は保持していても終点座標は保持していません。

しかし自分自身の幅と高さは保持しているので、
始点の座標に幅と高さの数値を足せば終点の数値を割り出すことができます。

上記の計算を利用してまず最初の条件式は、プレイヤーのx座標の終点(右端)が
ゴールのX座標の始点(左端)より右に居るかどうかを調べています。

それだけではゴールより右に居るとどこでも衝突判定とされてしまうので、
AND演算子を使い複数の条件を設定しています。

2つ目の条件式はプレイヤーのx座標の始点(左端)がゴールのx座標の終点(右端)より左に居るかどうかを調べています。

この二つの条件式を満たしていれば、プレイヤーがゴールの始点と終点のどこかにいることが分かります。

次にY座標も同じように条件式を記述すれば、ゴールの矩形内にプレイヤーのどこかが触れているという判定が出来ます。

別にY座標から調べても問題ありませんが、xとyではアルファベット順だとxの方が先なのでxを先に条件を書いています。

これが最もシンプルなアタリ判定の書き方になります。

変数を使えばもうちょっと見やすく書けると思いますが、今回はあえて変数を使わずに書いています。

なぜかは次々回ぐらいの記事で判るかと思います。

勘のいいひとはソースコードの冗長な書き方を見てすでにわかっているかもしれませんね。

デバッグ表示でゴールに当たっているか調べる

このままでは当たっているのにもかかわらず、ジャンプすればゴールにめり込めますし
衝突判定をされていても何も表示されたりしないので衝突判定をされているのかわかり辛いですね。

javascriptではブラウザのデベロッパーツールにデバッグメッセージを表示することが出来ます。

大体のブラウザではF12キーを押すとデベロッパーツールが起動します。

その中にあるコンソール(console)のタブ開くと発生しているエラーや、
javascriptで書いたconsole.log関数を使うとメッセージを表示できます。

衝突判定の関数は真偽値をreturnで返しているので衝突判定をしたらfalse、衝突したらtrueを返します。

console.log()の引数に衝突判定の関数を渡すとデベロッパーツールのコンソール画面に表示されます。

console.log()は処理負荷が結構高いので開発時以外は使わないようにしましょう。

特にループ関数の中でconsole.logを使っているので1秒間に何回もconsole.logを読んでいるので
古いパソコンだと重たくなってしまう可能性がありますが、最近のパソコンであれば大丈夫でしょう(たぶん

以下、今回のソースコード完全版になります。

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>pixi.js v4 テスト</title>
    <style>
      * { margin:0; padding:0; }
    </style>
  </head>
  <body>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.5.1/pixi.min.js"></script>
      <script>
        // フラグの定義
        keyFlag = 0;
        jumpFlag = 0;

        // 【追加】ジャンプ用カウント
        jumpCount = 0;

        // 【追加】ジャンプ用配列
        jumpSpeed = [31,29,27,25,23,21,19,17,15,13,11,9,7,5,3,1,1,3,5,7,11,13,15,17,19,21,23,25,27,29,31]

        // pixi.jsのアプリケーションを作成
        const app = new PIXI.Application();

        // bodyにpixi.jsのview(ステージ)を追加する
        document.body.appendChild(app.view);

        // pixi.jsのGprahicsオブジェクトのインスタンスを生成
        const square = new PIXI.Graphics();
        const goal = new PIXI.Graphics();

        // squareの大きさと位置を設定
        square.width = 50;
        square.height = 50;
        square.x = 50;
        square.y = 550;

        // squareの塗りつぶしと矩形描写
        square.beginFill(0xff00ff);
        square.drawRect(0,0,50,50);
        square.endFill();

        // 【追加】goalの大きさと位置を設定
        goal.width = 100;
        goal.height = 20;
        goal.x = 650;
        goal.y = 500;

        // 【追加】goalの塗りつぶしと矩形描写
        goal.beginFill(0x00ffff);
        goal.drawRect(0,0,100,20);
        goal.endFill();

        // ステージにsquareとgoalを追加
        app.stage.addChild(square);
        app.stage.addChild(goal);

        // ステージにsquareを追加
        app.stage.addChild(square);

        // ループ処理の実装
        app.ticker.add(delta => this.gameloop(delta,square));

        // イベントリスナー登録
        window.addEventListener("keydown", (event) => { this.downHandler(event) },false);
        window.addEventListener("keyup", (event) => { this.upHandler(event) },false);

        // downHandlerを定義
        // 【更新箇所】上下の移動を消してxキーによるy座標の減算を記載
        function downHandler(event) {
          switch(event.key) {
            case 'ArrowRight':
            keyFlag = 1;
            break;
            case 'ArrowLeft':
            keyFlag = 2;
            break;
            case 'x':
            keyFlag = 3;
            // ジャンプ中じゃなければジャンプフラグを1にする
            if(jumpFlag === 0) jumpFlag = 1;
          }
        }

        // upHandlerを定義
        function upHandler(event) {
          keyFlag = 0;
        }

        // ゲームループ関数の中身(毎フレーム実行される)
        function gameloop(delta, square) {
          // 衝突監視
          console.log(collisionJudge());

          // キー入力監視
          switch(keyFlag) {
            case 1:
              square.x += 10;
              break;
            case 2:
              square.x -= 10;
              break;
            default:
              break;
          }
          // 【追加】ジャンプ中フラグによる処理の切り替え
          switch(jumpFlag) {
            case 1:
              // ジャンプ中になったら
              square.y -= jumpSpeed[jumpCount];
              jumpCount++;
              if(jumpCount === 15) {
                jumpFlag = 2;
              }
              break;
            case 2:
              // 落下中になったら
              square.y += jumpSpeed[jumpCount];
              jumpCount++;
              if(jumpCount === 30) {
                jumpFlag = 0;
                square.y = 550;
                jumpCount = 0;
              }
              break;
            default:
              break;
          }
        }


        // 衝突監視関数
        function collisionJudge() {
          // プレイヤーの右端がゴールの左端以上 かつ プレイヤーの左端がゴールの右端以内であればY座標の判定に入る
          if( (square.x + square.width) >= goal.x && square.x <= (goal.x + goal.width) ) {
            // プレイヤーの下端がゴールの上端以上 かつ プレイヤーの上端がゴールの下端以内であればヒットしているとみなす
            if( (square.y + square.height) >= goal.y && square.y <= (goal.y + goal.height) ) {
              return true;
            }
          }
          return false;
        }


      </script>
    </body>
  </html>
</html>

今回は判定が2つしかないのでシンプルに書けていますが、沢山の判定があるとコードも複雑になってしまいます。

この書き方ではソースコードが酷いことになってしまうので効率よく書く方法も覚えていかなければいけません。

次回は、【pixi.js】重なることが出来ない障害物を作ってみる基礎訓練になります。

それでは。