【中級者向け】第四回 メモ帳だけでゲーム作ってみた〈ぷよぷよ編〉

2020年10月5日

今回も、メモ帳だけを使って、あの有名なパズルゲームの「ぷよぷよ」をつくっていきたいとおもいます。

前回の記事をまだ読んでいない方は、ぜひそちらを先に読んでください!


今回作るもの

前回は、ぷよを左右と下方向に操作できるようにしましたね。

  1. ぷよぷよのフィールドを作ろう!
  2. ぷよを上から落としてみよう!
  3. ぷよを操作できるようにしてみよう! ←前回
  4. ぷよを回転できるようにしてみよう! ←今回
  5. ぷよが下に落ちるようにしよう!
  6. 連鎖をできるようにしよう!
  7. 次のぷよが何なのかわかるようにしてみよう!

今回は、ぷよを回転させる操作を可能にしていきたいと思います!

回転ができるようになると、ぷよぷよの操作はすべてできるようになりますね!

ゲームの完成が徐々に近づいてきましたね!

完成イメージは以下の通りです。

ぷよを設置した後に落とすようにはまだしていないので、浮いているものもできてしまいますが、回せるようになると少しずつ面白くなってきますね!


コードを書いていこう!

ここからは、前回と同様にコードをメモ帳に書いていきます!

今回も、条件分岐を使っていきますが、コード自体がすごく前回のと似ているので、理解はしやすいと思います!


キーの設定をする

まずは、今回の回転の操作に必要なキー設定と同時に、前回設定したキーボード操作にもう一つキーを設定します。

function movePuyo(e){
  if (e.key == "ArrowDown" || e.key == "s") {
    down_speed = 0.2;
  }else if (e.key == "ArrowRight" || e.key == "d") {
    …省略…
  }else if (e.key == "ArrowLeft" || e.key == "a") {
    …省略…
  }else if (e.key == "z" || e.key == "i") {
    
  }else if (e.key == "x" || e.key == "o") {
   
  }
}

function resetFall(e){
  if (e.key == "ArrowDown" || e.key == "s") {
    down_speed = 0.02;
  }
}

else if文を追加して、またif文の中の()の中にキーを一つずつ増やしました。

下矢印キーとSキーで下方向
右矢印キーとdキーで右方向
左矢印キーとaキーで左方向

zキーとiキーで左回転(反時計回り)
xキーとoキーで右回転(時計回り)

という操作にしました!

もちろん自分の好きなキーにあててもらってもOKです!

回転の操作は、else if文の右回転と左回転の部分に書いていけばいいですね!


変数を作る

まずは、1つだけですが、回転に必要な変数を作ります。

それは角度 ( = direction ) です!

…
let color = [Math.floor(Math.random() * 4 + 1), Math.floor(Math.random() * 4 + 1)];
let direction = -90;

ぷよの設定の下に、「direction」というものを作りましょう!

子ぷよは最初下向きにあるので、-90(度)としています!

念のため、子ぷよの位置と角度を示した図を置いておきますので、参考にしてください!


回転のコードを書いていく前に…

先ほどの子ぷよの位置と角度で気づいたことはありませんか?

高校生で習った数学を思い出してみましょう。

角度によって、-1から1の数字を出すもの…
何か思い出せませんか?

そうですね!
サイン(sin)とコサイン(cos)ですよね!

実はプログラミングなどで回転を実現するときになんとサインとコサインを使います!

あの時習ったのはこうやって使うんだなと感じるかもしれません笑

一応念のため復習をしておきますが、

sin0° = 0, cos0° = 1
sin90° = 1, cos90° = 0
sin180° = 0, cos0° = -1
sin270° = -1, cos270° = 0

となりますね!

サインとコサインを使うということがわかったうえで進めていきましょう!

サイン・コサインをまだ習ってない人・忘れた人は

サイン・コサインというのは高校で習う数学の範囲です。

なので、高校数学を学んでない人や忘れてしまった人は回転のコードが難しいと思います。

ですが、サイン・コサインを使いのは今回だけなので、「回転にはサイン・コサインが要る」ということだけ理解してくだされば、コードを写すだけでいいと思います!

もしまた今度ゲームなどを作っていて、回転を考えないといけないときに、サインコサインを自分で詳しく勉強してみましょう!


回転のコードを書いていこう!

では本題の回転のコードを書いていきましょう。

といっても実は先ほど説明したサインとコサインをうまく使うだけで、あとは前回の左右の移動のコードと同じなんですよね。

前回の移動のコードは、

・移動した先の場所にぷよがすでに置かれていないか確認する
・なければ移動を完了させる

といった感じでした。

それが今回は、

・子ぷよの移動の場所をサインとコサインを用いて計算する
・そこにすでにぷよが置かれているか確認する
・なければ移動を完了させる

というように一手間増やすだけですね。

なのでまずは、

・子ぷよの移動の場所をサインとコサインを用いて計算する

から始めましょう。


移動後の子ぷよの位置を考える

まずは左回転、つまり「z」か「i」を押したときを考えて行きます。

最初は、-90度の場所から始まり、右回転を一回行うと、0度になりますね。

ほかも同様で、右回転させると、directionという変数を90増やして計算すればよさそうです。

先ほども書きましたが、ここでサインとコサインを用います。

子ぷよ(=childという変数)のx座標は、コサインがうまく対応していますね。

0度の時は、cos0° = 1 で、x座標は1
90度の時は、cos90° = 0 で、x座標は0
180度の時は、cos180° = -1 で、x座標は1
270度の時は、cos270° = 0 で、x座標は0

JavaScriptでコサインを使うときには、Math.cos(ラジアン) というものを使いますが、括弧の中が、「ラジアン」という90度などの「度」とは違った単位になっています。

これは高校2年生で習いますが、このように変換します。

( θ° ÷ 360 * 2π )ラジアン

なので、例えば180度が何ラジアンか知りたければ、θに180を入れて計算すると、1ラジアンと出てきますね。

あとは、まずは新しい変数「temp_x」(「仮のx」という意味)というものを作り、計算しましょう。

…
}else if (e.key == "z" || e.key == "i") {
  let temp_x = Math.cos((direction + 90) / 360 * 2 * Math.PI);
}else if (e.key == "x" || e.key == "o") {
…

*JavaScriptでπを使うときも、最初に「Math.」を付けて、「Math.PI」として使います。

これでもいいんですが、cosの計算でたまに、0.00000000008 などの誤差が出ることがあるので、四捨五入の「Math.round(数値)」というものを使って…

}else if (e.key == "z" || e.key == "i") {
  let temp_x = Math.round(Math.cos((direction + 90) / 360 * 2 * Math.PI));
}else if (e.key == "z" || e.key == "i") {
…

と大きくくくりましょう!

では次に、子ぷよ(=childという変数)のy座標を考えましょう。

sinが対応していますが、気を付けないといけないのが、上下が逆ですね。

なのでマイナスを付けて、

0度の時は、-sin0° = 0 で、x座標は0
90度の時は、-sin90° = -1 で、x座標は-1
180度の時は、-sin180° = 0 で、x座標は0
270度の時は、-sin270° = 1 で、x座標は1

と、しましょう!

後はコード自体は「-sin」に変えるだけなので…

…
}else if (e.key == "z" || e.key == "i") {
  let temp_x = Math.round(Math.cos((direction + 90) / 360 * 2 * Math.PI));
  let temp_y = -Math.round(Math.sin((direction + 90) / 360 * 2 * Math.PI));
}else if (e.key == "z" || e.key == "i") {
…

こうなりますね!

最初にマイナスを付けるのを忘れないようにしましょう!


移動後の場所にぷよが既にないか確認して、なければ移動させる

移動後の子ぷよの位置は計算できたので、あとは移動させましょう!

コード自体は前回の左右に移動させるコードを同じで、しかも子ぷよだけ確認すればいいので、前回より分かりやすいと思います。

いきなりコードを書いちゃうので、少し考えたい人は先に考えましょう!

…
}else if (e.key == "z" || e.key == "i") {
  let temp_x = Math.round(Math.cos((direction + 90) / 360 * 2 * Math.PI));
  let temp_y = -Math.round(Math.sin((direction + 90) / 360 * 2 * Math.PI));
  if (position[0] + temp_x >= 0 && position[0] + temp_x <= 5 && position[1] + temp_y >= 0 && position[1] + temp_y <= 11) {
    if (field[Math.ceil(position[1]) + temp_y][position[0] + temp_x] == 0) {
      child = [temp_x, temp_y];
      direction = direction + 90;
    }
  }
}else if (e.key == "z" || e.key == "i") {

前回と違った点は、子ぷよが枠の外に行かないかを、上下左右全て確認している点と移動完了後に「direction」を90だけ増やしているぐらいですね。

さああとは、右回転に対応させるだけです!

もう少し頑張りましょう!


右回転のコードを作る

さて最後は右回転です。

といっても実は左回転のコードとほとんど同じです。

違う点は「direction」を90減らす部分だけです。

なのでいきなりコードを書いちゃいます!

…
}else if (e.key == "x" || e.key == "o") {
  let temp_x = Math.round(Math.cos((direction - 90) / 360 * 2 * Math.PI));
  let temp_y = -Math.round(Math.sin((direction - 90) / 360 * 2 * Math.PI));
  if (position[0] + temp_x >= 0 && position[0] + temp_x <= 5 && position[1] + temp_y >= 0 && position[1] + temp_y <= 11) {
    if (field[Math.ceil(position[1]) + temp_y][position[0] + temp_x] == 0) {
      child = [temp_x, temp_y];
      direction = direction - 90;
    }
  }
}
…

これで完成です!

ブラウザで確認してみましょう!

右回転左回転が指定のボタンで動くようになっているはずです!


うまくいかなかった方向け

うまくいかなかった方は、まずエラーが出ていないか確認しましょう!

エラーの確認方法については、別の記事で詳しくまとめたので、ぜひそちらを参考にしてください!

それでもうまくいかなければ、下のサンプルコードをダウンロードして、自分のコードを比較してみましょう!


今回はここまで!

というわけで第四回はここまでとしたいと思います!

操作が前回より増えて少しずつゲーム完成に近づいてきましたね!

次回は、今回浮いたままのぷよを下に落ちるようにしたり、次々回の連鎖の準備をしていきたいと思います。

長くなってしまいましたが、

最後まで読んでいただきありがとうございました!

ご感想などあれば、コメントやTwitterにどしどしお寄せください!

第五回はこちらから!

第五回は制作中です。
もう少しお待ちください。