こんにちはgrandstreamです。
今回からはJavaScript初心者である僕が、YouTube動画を見ながら「スーパーマ◯オブラザーズ」を作成してみるシリーズをお届けします。
作成時に学んだことをまとめていければと思います。
なお、途中で挫折したらそこでおしまいにします。笑
参考動画
参考にさせていただいたのが「Akichonプログラミング講座」さんの動画です。
とても丁寧に解説してくださるので、なんとかやっていけそうかなと思い選びました。
第一回〜第三回までのまとめ
すでに動画の第一回〜第三回まで終わっていますので、一旦ここまでで完成しているコードを掲載し、各コードを自分なりに解説してみたいと思います。
見た目
コードを見ていく前に、現段階での見た目はこんな感じです。
青の背景、左上におじさん、そしてフレームカウント(詳細は後述)を表示しています。
なお、この青背景のサイズは512×448ピクセルで、ブラウザの左上に位置している状態です。
index.html
では早速コードの説明に移ります。
index.htmlではbody内に以下2つのコードを記述。
jsファイルの読み込みと、canvas要素です。
canvas要素を使えば、JavaScriptを使ってゲーム画面を描画することができます。
wikipediaによれば、”期待されている利用法としては、ゲーム、アニメーション、グラフ作成、画像構築などを含む。”
ということですね。僕も最近まで知りませんでした。
main.js
では、JavaScriptファイルのコードを順に見ていきましょう。
定数の定義(画面サイズ、フレームレート)
定数「GAME_FPS」ではフレームレートを60fpsに設定しています。
フレームレートとは、「1秒間に何回画面が更新されるか」みたいなことですね。
パラパラ漫画をイメージするとわかりやすいですが、紙の枚数が多いほうが絵の動きがより細かく表現できますよね。
したがって、FPSの数字が大きいほどなめらかな表示になります。
最近の動きが激しいゲームなんかでは、200~300fpsなんてこともあるみたいです…!
次に、SCREEN_SIZE_W(画面幅)、SCREEN_SIZE_H(画面高)を指定します。
ファミコンゲームのピクセルが256 × 224なのだそうです。
キャンバスに画を描く下準備
canvas要素を操作するために、getContext()というメソッドを使用します。
getContext(‘2d’)はつまり、「canvas上に2dで描画しますよー」ということです。
そして、仮想画面と実画面に分けているのはなぜかと言うと、拡大表示したいからです。
というのもどうやら実画面だけでは拡大できず、「256 × 224」の画面サイズが等倍で表示されるようです。(結構小さい)
なので、でっかい画面でゲームができるように、「仮想画面にもう一つcanvasを作って、それを拡大させたものを実画面に表示する」という手順をとっています。
キャンバスサイズ設定
仮想画面(vcan)はもとのサイズ、実画面(can)のサイズは2倍サイズとして設定しておきます。
描画をなめらかにしないようにする
おじさん画像を表示させた際に、デフォルトで輪郭がなめらかになってしまうようです。
そこで、「imageSmoothingEnabled」をfalseにして「なめらかにしない」ことで、元画像のようなピクセルがくっきりした表示にできます。
フレームレート維持
1コマごとに描画を繰り返すための下準備です。
素材画像を取得
今回はおじさん、敵キャラ、障害物などの画像が1枚に詰め込んである「sprite.png」という画像ファイルを切り出して使用します。
そのために、このコードではimg要素を作成してsrc属性でそのファイルを指定しています。
後ほど、ここで取得した画像からほしい部分の画像を切り出して使用します。
更新処理
ここには画面が更新された際(コマ送りされた際)に、おじさんや敵キャラが動く部分の処理を記述していくと思われます。
描画処理
ここでは、背景画像やおじさんを描画しています。
使用したメソッドをまとめてみます。
- fillStyle…描画した図形の内側を指定の色で塗りつぶす。
- fillRect…canvas上に長方形を描画
- drawImage…canvas上に画像を描画する
- fillText…canvas上にテキストを描画
「//背景拡大表示」部分のコードでは、「vcan(仮想画面)を切り出して、con(実画面)上に2倍で表示する」ということを行なっています。
ループ開始
ブラウザが更新されたら時間計測を開始するとともに、関数mainLoop()(後ほど記載)を実行します。
なぜ時間計測をするのかと言うと、後ほど、ループが開始されてからの時間と比較して、更新・描画処理のタイミングを制御するのに使用するためです。
メインループ
ここで設定している関数「mainLoop()」は先程も説明しましたが、ブラウザが更新されたタイミングで呼び出されます。
具体的にどんな処理を行なっているのか見てみましょう。
そもそもメインループでは「画面の描画を1秒間に60回行う」ということをやっています。
ゲーム画面の背景やキャラクター等を動かすために必須の部分ですね。
実は、「setInterval(mainLoop, 1000/60);」とすれば簡単に「1秒間に60回ループを回す」処理ができるようなのですが、これには色々と問題があるそうです。(描画と関係なくループが呼ばれたり、少しズレたり…)
なので、その代わりにrequestAnimationFrame()というのを使っているようです。
こちらを使うと、描画が終わるたびに再び自分自身(mainLoop)を呼び出すというのを繰り返すことができます。
ただ、「1秒間に60回ループを回す」という指示は自分で記述しないといけないです。
そのために「nowFrame」で現在のフレームが何フレーム目かを算出し、フレームカウントを上回った時点で描画処理を行います。
描画処理内のwhile文では、「nowFrame > frameCount」である限りは、更新処理を4倍速で行うという処理を行なっています。
というのも、PCをスリープにしていたり、PC上で別の作業をしたりなんかして、再びゲーム画面に戻った際に「nowFrame」が大きすぎる(ゲームを放置したがために)場合があるからです。
その場合は、更新を素早く行なって、現在の表示に追いつかせようというわけです。(敵キャラが一瞬めっちゃ早く動く感じですかね。)
まとめ
コードを解説するのはなかなか大変でした。
が、最初は自分の言葉で自分なりに落とし込んでいかないとなかなか理解できませんので、次回以降も続けていきたいと思います。
ではまた。
コメント
[…] 【JavaScript】ゲーム制作をしてみた①【スーパーマ◯オ】 […]
[…] 【JavaScript】ゲーム制作をしてみた①【スーパーマ◯オ】 […]