【pixi.js】キー入力処理の応用でジャンプ処理を作ってみよう

javascript

ジャンプ処理 作り方

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

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

pixi.js講座第三弾はキー入力の応用でジャンプ処理を作ってみようのコーナーです。

残念ながら長くなってしまったので今回もマリオのようなきれいなジャンプにはたどり着けませんでした。

マリオ風ジャンプの実装は前回で一気にやりたかったのですが、
思ったよりも記事が長くなってしまったので切り分けることにしました。

今回はジャンプアクションゲームの要でもあるジャンプの実装について学んでいきましょう。

ゲーム作り入門者向けのpixi.js講座は全4回になります。

この記事の内容
ジャンプの基礎処理を作る
ジャンプの処理を改善してみる

一番簡単なジャンプ処理を実際に作ってみる

ジャンプ処理作成

マリオジャンプを作る前にまずはジャンプの基礎となる動きを作ってみましょう。

マリオのジャンプはグラフにすると放物線を描くジャンプなのでいきなり作ろうとしても難しいですね。
数学が得意な人は少し計算すればできると思いますが、順を追って説明してきましょう。

最初に作るものはジャンプとは言い難いレベルのものですが、基礎となるのでしっかりと覚えましょう。

ではまず最初に作り方を羅列してみます。

  • 地面の位置を決定する(地面の実装)
  • キーを押している間はプレイヤーを上昇させる(位置上昇の実装)
  • キーが離されていてプレイヤーが地面より上にいると地面まで落下するようにする(重力の実装)

この3つがあればジャンプの基礎を作ることが出来ます。

ただ、このままだとまるで宇宙空間にいるような動きになりますね。
その状態から段階的にジャンプぽくしていこうと思います。

ソースコードは前回「」の最後に記述したコードが最終版となっていますので
そちらをコピペしてから下記のコードに書き換えてください。

変更している部分だけ記載していることに注意してください。
もし、わからなくなった場合は、記事の最後に完成版のソースコードを乗せているのでそちらを見てください。


~~~省略

  // 【更新箇所】四角の大きさとY座標を変更
  square.width = 50;
  square.height = 50;
  square.x = 50;
  square.y = 500;

~~~省略

  // downHandlerを定義
  // 【更新箇所】上下の移動を消してxキーによるy座標の減算を記載
  function downHandler(event) {
    switch(event.key) {
      case 'ArrowRight':
      keyFlag = 1;
      break;
      case 'ArrowLeft':
      keyFlag = 2;
      break;
      case 'x':
      keyFlag = 3;
    }
  }

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


  // ゲームループ関数の中身(毎フレーム実行される)
  function gameloop(delta, square) {
    switch(keyFlag) {
      case 1:
        square.x += 10;
        break;
      case 2:
        square.x -= 10;
        break;
      case 3:
        square.y -= 1;
      default:
        break;
    }

    //【追加】keyFlagが0の場合、地面に居ない場合は重力を適用する
    if(keyFlag === 0) {
      // y座標が500より小さければy座標を加算する
      if(square.y < 500 ) {
        square.y += 1
        // y座標が500以下になったときに500に補正する(※大事)
        if(square.y >= 500) {
         square = 500; 
        }
      }
    }
  }

~~~省略~~~

少し複雑かもしれませんが、やっていることは結構単純です。

追加した部分を簡単に解説していきます。

四角の座標と大きさの変更

少し大きさを変更して、y座標を変更しました。

画面の一番下に四角が来るようにしています。

downHandlerを定義変更

最初は上下左右を設定していましたが、上方向はxキーに置き換えました。

下方向は重力で自動的に下がるので削除しています。

ゲームループ関数の中身の変更

downHandlerを変更したことでここの条件式も変更しましょう。

重要なのは条件式の下に追記した重力の設定です。

仕組み的にはキーが何も押されていない時かつ、四角が地面より高い(y座標500未満)位置に居た場合にy座標を+1する。

ここで重要なのが、着地した時に元の数値に補正してあげることです。

今回は+1と-1なので着地した時はきれいにy座標500となりますが、
小数点などをくわえた場合に地面にめり込んでしまう可能性があります。

なのでこうやって着地点以上になったら座標を補正するということはゲーム作りにおいてかなり大切なことになってきます。

少し詳しく話すと、四角が移動する前に1つ先に移動するとぶつかるかどうかをチェックして補正しておけば、
不自然に地面にめり込んだ状態にならなくなります。

実行してみる

これで一旦実行してみましょう。

四角が一番下にいる状態でXキーを押し続けてください。

四角が上昇していくので、キーを話すと下降していき地面に付くと止まります。

……はい、全然ジャンプっぽくないですね。
ジャンプというよりもホバーに近い感じがします。

これを応用するとパイロットウィングスのジェットパックみたいなゲームを作ることも出来ます。

ですが今回作りたいのはマリオのような自然に見えるジャンプですね。

ホバリングジャンプをジャンプぽく改造する

ジャンプ処理改善

今はキーを押し続けていると上昇しますが、ジャンプは1回押したらジャンプしますよね。

次はジャンプ中フラグを実装することでキーを1回押したら上昇するようにできます。

しかしそれでは上昇し続けてしまうので、最大地点になったら降下させるように作ってみましょう。

ジャンプフラグ変数を作って状態を管理する

TrueかFalse(Boolean型という)で管理してもいいのですが、今回は「着地中」「ジャンプ中」「落下中」と3つの状態が欲しいので、
Boolean型では2つしか管理できないので3つ以上の状態があるときは数値で管理するのが基本です。

それではフラグを追加してジャンプを制御してみましょう。

~~~省略~~~

// フラグの定義
  keyFlag = 0;
  jumpFlag = 0; // #追加分

~~~省略~~~

  // downHandlerを定義
  // 【更新箇所】keyFlagが3の時ジャンプフラグが0であれば1にする
  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) {

    // 【削除】キーフラグによるy座標の減少を削除
    switch(keyFlag) {
      case 1:
        square.x += 10;
        break;
      case 2:
        square.x -= 10;
        break;
      default:
        break;
    }

    // 【追加】ジャンプ中フラグによる処理の切り替え
    switch(jumpFlag) {
      case 1:
        // ジャンプ中になったら
        square.y -= 10;
        if(square.y < 200) {
          jumpFlag = 2;
        }
        break;
      case 2:
        // 落下中になったら
        square.y += 10;
        // 着地したら位置補正とフラグを着地状態にする
        if(square.y >= 500) {
          square.y = 500;
          jumpFlag = 0;
        }
        break;
      default:
        break;
    }
  }
~~~省略~~~

それでは上から解説していきます。

ジャンプフラグの追加

keyFlagと同じようにjumpFlagを作って0で初期化しておきましょう。

キーが押された時、xキーであればジャンプ中フラグをONにする

xキーが押された時の処理を動かすときにジャンプフラグを0だった場合
ここでジャンプフラグを1にしてジャンプ中状態にします。

ここの処理は押されている間ずっと動いてしまうので
ジャンプ中フラグを変更だけにすると毎回代入処理が発生してしまうので処理が重たくなります。

条件式を設けていると、ここで条件であるjumpFlagが0であるときの条件から外れるので
1回だけ処理を行うと着地するまでは条件を突破できないようにしています。

ゲームループの中を修正

前回まではキーフラグによって四角を上昇させていましたが、jumpFlagを追加したいので
keyFlagから上昇する処理を削除します。

その下にjumpFlagによる条件分岐を新たに作成しました。

条件がBooleanじゃないものはswitchを使えば切り分けが楽ですね。

ジャンプ中の場合、つまりjumpFlag1の場合は四角のy座標を減少させていきます。

そのままでは上昇し続けてしまうので限界値を決めておきます。

限界値に到達したら、jumpFlagを2に切り替えて落下中に処理を切り替えます。

落下中の処理も考え方は同じで着地点を決めておいてたどり着くまで落下させます。

着地したら位置補正をした後にjumpFlagを0にして着地状態にしましょう。

まだまだ不自然なジャンプ

このままマリオジャンプまで作ってしまうと記事が長くなってしまうので一旦ここで切ります。

前回のホバリングよりはジャンプっぽくなりましたがまだまだエレベーターのようで不自然ですね。

今回のジャンプの欠点は、上昇率も降下率もすべて一定なので不自然に感じます。

マリオのジャンプは放物線を描くようなジャンプなので、計算式が必要になります。

次回はマリオジャンプの解説をしつつ、マリオジャンプを完成させてみましょう。

【余談】ゲームのジャンプ処理は昭和時代に既に完成されていた

マリオジャンプ

ジャンプと一口に言ってもゲームによって挙動が違います。
2Dゲームで最も人気のあるのはスーパーマリオブラザーズのジャンプですね。

ゲームをしたことがある人ならほとんど人があのジャンプを体験したことがあると思います。

多少処理は変わっているとは思いますが、スマブラ最新作にもマリオジャンプの仕組みは健在しています。

スーパーマリオブラザーズが発売されたのが1985年ということは、
私が生まれる1年前なのでその時にジャンプの処理が完成されていたということになります。

凄いですよね。

もしかするとマリオの前に同じようなジャンプが完成されていたかもしれません。

そして最後に今回のソースコードを貼っておきます。

そろそろgithub使うべきですね……

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

        // 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 = 500;

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

        // ステージに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 -= 10;
              if(square.y < 200) {
                jumpFlag = 2;
              }
              break;
            case 2:
              // 落下中になったら
              square.y += 10;
              // 着地したら位置補正とフラグを着地状態にする
              if(square.y >= 500) {
                square.y = 500;
                jumpFlag = 0;
              }
              break;
            default:
              break;
          }
        }
      </script>
    </body>
  </html>
</html>

それでは。