【pixi.js】重なることが出来ない障害物を作ってみる基礎訓練

javascript

障害物 当たり判定

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

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

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

ゴールは見た目では重なり合ってしまうので判定が効いているかわかり辛かったので
javascriptのconsole.logメソッドを使ってデバッグメッセージで確認をしてみました。

今回は見た目でもぶつかっているとわかるように障害物とその衝突判定作ってみたいと思います。

ぶつかっているように見えるにはどうするか

進めない 衝突判定

ゴールの時の当たり判定はゲームループの中で常に重なるかどうかを調べていました。
障害物を作る場合も基本的には同じ処理を行うのですが、プレイヤーが障害物にぶつかる状態になったら位置補正をしなければいけません。

位置補正の方法はジャンプの着地の時にもやりましたね。

なので今回は当たり判定の基礎とジャンプ処理(着地時の処理)を組み合わせた応用処理となります。

ゲームプログラミングはこういった既存の処理を組み合わせることが結構多かったりするので
基礎能力がどれだけ大事かが分かって良いですね。

障害物を作って当たり判定の処理を追加する

前回のゴールを作ったときと同じようにPIXI.Graphicsを使って障害物オブジェクトを作りましょう。

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

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

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

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

~~~省略~~~

前回特に変わらずですね。
サイズと位置と色を変更しただけです。

障害物ということなので壁にしてみました。

衝突判定の処理を追加する

実際に作る前にまずどうやって位置補正をするかを考えなければいけません。
左に居るオブジェクトに右からぶつかってるのに、ぶつかったオブジェクトの左に位置補正してしまってはワープしてるように見えますよね。

なので障害物にぶつかった位置がとても大事になってきます。

今回は障害物にぶつかったときの基礎訓練として
左方向からぶつかったときの処理だけを追加してみましょう。

全方向の衝突判定を作るとなるとかなり複雑になってくるので、
段階を踏んで改良していったほうが理解が深まります。

        // 衝突監視関数
        function wallCollisiton(x,y) {
          if(y === 0) {
            // 対象の左からぶつかろうとした場合
            if((square.x + square.width) - x < wall.x) {
              // 対象にめり込んでいるかチェック
              if ( (square.x + square.width + x) > wall.x  ) {
                // 対称のx座標からめり込んだ分
                return x - ( (square.x + square.width + x) - wall.x);
              }
            }
            return x;
          }
          else if (x === 0) {
            return y;
          }
        }

衝突監視の新しい関数を作るので、今の作り方であればソースコードのどこに作成しても大丈夫です。

この関数は移動する時に使う関数となっています。

関数を設置する場所は、現在キー移動した時に座標を加算しているところです。

その際に関数に移動する値を引数に渡します。

衝突判定関数はxとyを引数に取っていますが、左右に移動する時はxの移動値を渡してyには0を渡します。

xかyどっちかに0を渡すことで上下に移動しているのか左右に移動しているのかを判定できます。

今回は訓練ということで左からぶつかったときの判定しか作っていません。

斜め移動の時の衝突判定処理は結構面倒くさい

上下左右の4方向からの衝突判定は結構簡単に作れますが、斜め方向からの判定も追加すると
結構コードが長くなってしまって見通しが悪くなります。

こういう時は同じような処理を1つの関数にして使いまわすのが基本になってきます。

キー判定処理に作った衝突判定関数を埋め込もう

現在ではx座標に直接数値を加減算していると思いますので加算方向、
つまり右キーを押したときの加算処理を関数に置き換えてみましょう。

          // キー入力監視
          switch(keyFlag) {
            case 1:
              square.x += wallCollisiton(10,0); // 変更
              break;
            case 2:
              square.x -= 10;
              break;
            default:
              break;
          }

+=10を関数に置き換えて、返り値を加算するようにしてます。

例えばxが10進むとして、壁にxが5めり込んだ場合にめり込んだ分(5)は加算しないように数値を修正して
その数値(10からめり込んだ数を減らした数値)を返してプレイヤーの座標に加算することで、
壁より右にいけないようにしてぶつかっているようにみせています。

ちょっと文字にすると説明がわかりづらいかもしれませんね。

もうちょっと言うと、真隣にいる場合に右キーを押したら本来ならxが10加算されますが
めり込んだ値が10になるので、それを補正したら関数は0という数値を返すので
結果的にプレイヤーの座標は変更されず、見た目的に壁にぶつかってそれ以上動けないという処理になってます。

一定の位置から右に進めない

ここまでのコードを動かすと緑の四角までは進めますがそれ以上右に進むことはできません。

なぜかというと、現在は横の軸しか見ていないので縦の範囲は一切見ていません。

なので見た目はどちらも小さくてジャンプで乗り越えられそうですが、
条件はx座標でしか見ていないので乗り越えられません。

この衝突判定の作り方はあまりよろしくない

ここまで書いていおいてなんですが、この衝突判定の処理の書き方は数が少なければ特に問題はありませんが
シューティングなどの弾幕ゲームとかになると数が増える分コード量が尋常じゃない量になってしまいます。

なので関数化して呼び出すだけで使えるようにシンプルな書き方にしなければ処理が重くなりそうです。

グリッド移動型のRPGはもっと衝突判定のコードをシンプルに書ける

グリッド移動、つまりドット絵時代のFFやドラクエ等の1マス単位で動くタイプのRPGは
マップ用の2次元配列があって、その配列を参照して隣のマスに障害物があれば検知できるので
結構シンプルな書き方にすることが出来ます。

ドット移動タイプの衝突判定から作っていこう

Motherシリーズやクロノトリガーのようにマス目移動ではなく自由に動き回れる(ドット移動タイプ)
のゲームの場合は、今回作ったようにオブジェクト毎に判定を作る必要があります。

今回の衝突判定チュートリアルはアクションゲームを作るためにドット移動タイプの判定を作りたいので
何回かに分けて使いまわしやすいコードに改良していきましょう。

重力があるタイプのアクションと倉庫番やゼルダの伝説のような上下左右に動けるアクションゲームとでは
y座標の衝突判定の作り方が少し変わってきます。

今回はまだジャンプが出来るからy座標の判定を作っていませんが、次回からは上下左右に自由に動けるタイプに
変更して上からぶつかったときの判定も作っていこうと思います。

最後にジャンプ機能を削ってY座標も自由に移動できるように改良した完全版のコードを乗せておきます。

次回は、【pixi.js】見おろし型2Dゲームの衝突判定を作るになります。

それでは。

<!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;

        // 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();
        const wall = 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();

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

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

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

        // ループ処理の実装
        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 'ArrowDown':
            keyFlag = 3;
            break
            case 'ArrowUp':
            keyFlag = 4;
            break
            default:
            break;
          }
        }

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

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

          // キー入力監視
          switch(keyFlag) {
            case 1:
              square.x += wallCollisiton(10,0); // 変更
              break;
            case 2:
              square.x -= 10;
              break;
            case 3:
              square.y += 10;
              break;
            case 4:
              square.y -= 10;
              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;
        }

        function wallCollisiton(x,y) {
          if(y === 0) {
            // 対象の左からぶつかろうとした場合
            if((square.x + square.width) - x < wall.x) {
              // 対象にめり込んでいるかチェック
              if ( (square.x + square.width + x) > wall.x  ) {
                // 対称のx座標からめり込んだ分
                return x - ( (square.x + square.width + x) - wall.x);
              }
            }
            return x;
          }
          else if (x === 0) {
            return y;
          }

        }


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