【pixi.js】スーパーマリオ風ジャンプの作り方

javascript

放物線 ジャンプ

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

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

pixi.js講座第四弾はスーパーマリオ風のジャンプの実装方法になります。

あくまでスーパーマリオ風です。

細かい調整はしていないので今回作るジャンプの仕組みは、
今後自分自身への課題としてやってみるのも面白いですよ。

ジャンプに関しては今回の記事で一旦終わりになります。
マリオ以外のジャンプの挙動などもそのうちジャンプの応用として記事にしても面白そうですね。

今回はコードよりも文章が多めになりそうです。
マリオ風のジャンプは色々な実装方法がありますが、計算式をあまり使わずにわかりやすく実装してみたいと思います。

何度も言いますがあくまでマリオ風なので、マリオ完コピジャンプではありません。

ちなみに今回はボタンちょい押しで出来るような小ジャンプは実装しません。
小ジャンプの実装法としてはジャンプボタンを押している時間をカウントしておけば条件で切り分けられます。

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

この記事の内容
ジャンプをマリオ風ジャンプに改善する
ジャンプ処理について考えてみる

マリオ風ジャンプとは

マリオ ジャンプ

ここまでの記事で何回か説明をしましたがもう一度説明しておきます。

マリオ風のジャンプとは、ボタンを押すと加速して上昇し、上に行くほど速度が下がって
最大地点まで到達したら重力に引っ張られるように降下します。

グラフで言うと、放物線を描くようにジャンプする感じですね。

放物線ジャンプの実装方法はどうするか

放物線であれば計算式を使ってもいいのですが、あらかじめ決まっている連続した値を使ってしまえば
計算式が無くても放物線ジャンプを実装出来ます。

連続した値……そう、配列ですね。

実際に配列を使ってジャンプ処理を書き換えてみましょう。

// フラグの定義
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]

~~~省略~~~
// 【編集】ジャンプ中と落下中の処理を大きく変化
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;
      jumpCount = 0;
      square.y = 500;
    }
    break;
  default:
    break;
}

分かりやすいようにちょっとゴリ押し的な書き方で美しくありませんが、
やっていることはこういうことです。

1つずつ解説してみます。

ジャンプ用カウントの追加

ジャンプ用というかジャンプスピード用の添え字に使うカウント変数ですね。

ジャンプ中の1ループごとにカウントしていきます。

ジャンプ用配列の追加

ジャンプ用配列の中身は数値が高い状態から低い状態になって、また高い状態に戻しています。

ジャンプ用のカウントを使ってジャンプ用配列の添え字に使って入っている数値を座標から減算していき、
最大地点まで到達したら前回通りに落下中フラグに切り替えます。

そして落下中もジャンプ用カウントは維持されたままなので、そのままカウントアップをしていきつつ
ジャンプ用配列の中身を今度は加算していくことで落下させています。

ジャンプ中と落下中の処理の変更

追加した変数と配列を使ってジャンプ処理を変更しました。

ジャンプ中も落下中もジャンプ用カウントは加算し続けますが、配列の半分になったら
落下中フラグに切り替えて着地したらジャンプ用カウントは0に戻しています。

実際に動かしてみると、ちょっとジャンプっぽくなりましたよね。

しかしなんかマリオっぽくないですよね。

実はマリオを完全再現しようとするとちょっと数学的な知識が必要になるみたいで今回は完全にはしません。

申し訳ないですがジャンプのコーディングはここまでになります。
これ以上突き詰めようとすると複雑な解説が必要になるので、初学者向けの記事になるので難しい話は無しにします。

というか、私が上手く説明できる気がしなかったというのもありますが(笑)

現在のコーディングだとジャンプ中に左右に上手く動けないと思います。

そのあたりも条件式を分けたりしてジャンプ中にも左右に動けるようにしたりして調整してみてください。

ジャンプについて少し考えてみる

ジャンプについて考える

ファミコン時代はまだ開発者もゲーム作りに慣れていないのか、ジャンプ性能が酷いゲームがあったりしました。

私の中で記憶に強いのがアトランチスの謎ですね。

ゲームセンターCXの有野課長もプレイしたので結構有名になっているゲームなのですが、
あのゲームはジャンプ中は左右のキーが無効化されてしまいマリオみたいに着地までに距離の微調整が出来ません。

ジャンプボタンを押した瞬間、押した強さと押している方向キーによってもう着地地点が決まってしまいます。

ファミコンだったから許されていた挙動でしたね。

逆にあえてあのジャンプにしたお陰でアトランチスは難易度が上がって、見ている方は面白いかもしれません。

現実のジャンプを絡めて考えてみる

私も理系ではないので感覚的にしか言えないのですが、
現実でジャンプをするときは地面を両足で踏み込んで地面をけり上げて跳躍(ジャンプ)します。

けり上げる瞬間の力は強いので、初動は早く地面から離れる程重力に引っ張られるので速度は落ちます。
ジャンプ用配列の中身の数値はこれを考慮しての数値になっています。

今回配列に格納している数値は1、3、5…と+2ずつ大きくなっていますね。

規則的な数値のせいでまだまだジャンプが機械的に見えてしまいます。

現実のジャンプを考慮した上昇値と下降値を入れるとなると結構大変ですので
ある程度は妥協して自分の作りやすいようにした方が良いです。

私はゲームに現実を取り入れすぎるとつまらなくなると思っているので
ある程度非現実的な要素を取り入れたほうが爽快感も増します。

マリオのジャンプを現実的に考えると、まずあの跳躍力はありえないですからね。

だからと言って現実の高さしか飛べないマリオなんてまったく面白くないですし、
そもそもジャンプというのはマリオのアイデンティティでもあるのでそこを現実化しちゃうと面白さが激減します。

もしマリオが超絶フォトリアルになってあのジャンプをそのまま再現するとすさまじく違和感のあるゲームになります。

コミカルなグラフィックだからこそ現実離れしたジャンプが受け入れられるというのも大いにあると思いますね。

スパイダーマンのようなリアル頭身のゲームは違和感を覚えないような作りにしているのは凄いですよね。

なのでゲームの見た目をリアルにすればするほど非現実的な要素を取り入れづらくなって
ゲーム作りが大変になってくるのだと思います。

非現実を納得させるためには現実的な要素を混ぜて脳を説得する

以上のことから何でもかんでもリアルな要素を取り入れるとつまらなくなってしまうので
非現実的な要素はバンバン取り入れていくと面白いゲーム作りの可能性が高まります。

かといって何でもかんでも非現実的な要素を入れ過ぎると不自然感が強まりすぎて違和感しかなくなります。

人間というか人間の脳は実にワガママですね。

ジャンプの話に戻すと、直立不動のまま自分の背の高さの3倍ジャンプできてしまう作りだと
いくらゲームとは言え不自然な動きになるので脳を納得させることはできません。

非現実的な要素に現実的な要素を混ぜると人間の脳を説得させることが出来ます。

人間がジャンプするときにはまず踏み込んで飛びあがりますよね。

ゲームでもその要素を取り入れると自然にジャンプしているように見えます。

それでもそのジャンプ力はおかしいだろってなってしまいますよね。

そこで「このキャラクターはジャンプが得意」という設定を設けると、
説得力が増して脳を納得させることが出来て自然に受け入れられます。

私は脳科学者じゃないので詳しいことは言えませんが、
視覚情報から現実的な動作をしているものの、ジャンプ力が現実的じゃない。

そこにジャンプが得意という設定、キャラクターの見た目が身軽そう。
という情報をくわえることにより高くジャンプできるキャラクターなんだなって脳が理解します。

──冒頭でも言ったようにジャンプの作り方については一旦今回で終了となります。

私自身がマリオジャンプを完全再現できるようになった場合また記事にしようと思います。

最後に完全版ソースコードを載せておきます。

<!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 = 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 -= jumpSpeed[jumpCount];
              jumpCount++;
              if(jumpCount === 15) {
                jumpFlag = 2;
              }
              break;
            case 2:
              // 落下中になったら
              square.y += jumpSpeed[jumpCount];
              jumpCount++;
              if(jumpCount === 30) {
                jumpFlag = 0;
                square.y = 500;
                jumpCount = 0;
              }
              break;
            default:
              break;
          }
        }
      </script>
    </body>
  </html>
</html>

それでは。