2018/07/03

ゲーム開発初心者が教えるブラウザゲームのつくり方(後編)

この記事は、2018年6月29日にさくらインターネットで行われた「さくらの勉強会 フロントエンドナイト」で発表した内容を文字起こししたものの「後編」です。


※ [ディレクターズ・カット版]と書かれたスライドが、この記事を公開するにあたり追加したものです。
※ この記事には多くの画像が貼れれているため、読み込みに時間がかかる可能性があります

続き)シンプルなブラウザゲームのつくり方


次にCanvas APIをつかったレンダリングについてです。

オブジェクトを描画するためには、Canvas APIの基本的な知識が必要です。

また、レンダリングするときは一度画面をオールクリアしてから、表示するオブジェクトをひとつずつ描画するという手順を踏みます。これを1サイクルとして、60fpsであれば1秒間に60回行います。

こうすることで、パラパラ漫画の要領でオブジェクトが動いてみるというわけです。

ひとつずつ描画するためには、図のように背景、オブジェクト、プレイヤーをそれぞれ1枚の画として描画していきます。ただし、あとから描画するオブジェクトが前面にくるので、プレイヤーなどを描画してから背景を描画するとすべて消えてしまいますので描画順には注意が必要です。

レンダリングの概要について説明いたしましたので、次に具体的なCanvas APIの使い方を説明します。
まずCanvas要素を生成、またはDOMから取得します。次にCanvas要素から、CanvasRendering2DContextを取得します。このContextにはCanvasの状態や、描画に必要なメソッドが含まれており、これを持ち回ってオブジェクトを描画することになります。

そして、clearRectを使って範囲指定した部分をクリアします。

クリアまで終わったら、オブジェクトを描画していきます。
矩形を描画するにはfillRect、画像を描画するにはdrawImageを使います。ただ、Canvasで簡単に描画できるのは矩形と画像だけで、多角形や円はパスを使って描いていきます。

[ディレクターズ・カット版]
オブジェクトをどこに描画すればよいか計算するときは、みんな大好きExcel方眼紙をつかうとかなりわかりやすくなるのでオススメです。

さきほど描画は、オールクリアしてひとつずつオブジェクトを描画すると説明しましたが、このままではかなり効率が悪いです。背景などほとんど変更がない場所もすべて再描画する必要があるためです。

たとえば15個のオブジェクトからなる背景があったとして、ひとつのCanvasを使わない場合ですと、1フレームにつき背景のために15回も描画処理を行わなくてはなりません。そこで、負荷の高い描画処理を減らすためにオフスクリーンを活用します。

このオフスクリーンはメモリ上で管理しているだけですので、高負荷なブラウザへの描画処理は行いません。背景用にオフスクリーンを用意し、あらかじめ背景をレンダリングしておき、あとは毎フレーム、可視領域にあるCanvasに背景用のオフスクリーンを描画すれば、15回描画していた処理が1回だけになります。

このようにオフスクリーンを活用することでパフォーマンス向上に繋がります。

ゲームがゲームたるには「自分で操作できる」ことが重要です。次はユーザーの入力を受け付ける処理について説明します。

Playerを動かすためにはキーボードやマウスなどから入力を受け付ける必要があります。そのためにはWebアプリケーションと同様にaddEventListenerでkeydownやclickイベントを使います。

ちなみにゲームパッドに対応する場合は、Gamepad APIが使えます。

keydownイベントが発生したら、入力したキーをどこかに格納しておきます。あとはswitch文などを使って、ArrowUpならy軸方向に-5px、ArrowDownならy軸方向に+5pxのように処理を書きます。CSSをご存知であればわかると思いますが、Canvas APIの原点は左上のため、y軸方向にプラスなら下へ、マイナスなら上に移動します。

ゲームではプレイヤー以外に、時間経過とともにオブジェクトを移動させたいときがあります。ここから数学・物理色が濃くなってきますが、中学・高校時代を思い出しながら見てください。

単一方向に等速直線運動させるには、毎フレーム座標を加算・減算するだけです。たとえばオブジェクトを右方向に5pxずつ移動させたいなら、x軸方向に+5pxしていきます。

前説明したようにゲームでは1秒間に60回更新されますので、この場合は1秒間かけて右に300px動いているように見えます。

[ディレクターズ・カット版]
ブロック崩しゲームなど、壁にあたったオブジェクトを反射させたい場合は、壁にあたったときに速度を反転させることで実現できます。入射角と反射角を同じにするため、左右の壁に当たったらx軸方向の速度を反転させ、同様に上下の壁に当たったらy軸方向の速度を反転させます。

ただ、このままではある問題が生じます。

[ディレクターズ・カット版]
それは、斜めに移動したとき通常の上下左右の移動より速く動いているように見えてしまう点です。

なぜ斜めに移動したときに速く動いているように見えてしまうのかは、ピタゴラスの定理を思い出してみてください。x軸方向に+1、y軸方向に+1の速度で動いた場合、ピタゴラスの定理より斜めの移動速度が√2(約1.41)になります。そのため、斜めの移動速度は通常と比べて1.41倍速く動いているように見えるというわけです。

そのため、斜め方向に速度+1で動かすためには、x軸方向の速度とy軸方向の速度に1/√2をかけて速度を補正してあげる必要があります。

単一方向の等速直線運動であれば簡単なのですが、ちょっと違う動きをさせるためには数学や物理の知識が必要になってきます。

たとえばオブジェクトを反時計回りにぐるぐる回したい場合には、三角関数のcos/sinを使います。x軸にcos、y軸にsinを当てはめることで回転できます。

たとえば1秒で1回転させたい場合は、このような実装になります。1周360°をラジアンで表すと2πですので、2πを60で割った値が1フレームに進む角度になります。その角速度に現在のフレームをかけた値をcon/sin関数にわたすことで、1秒間に1回転させることができます。

ただし、円周率のような小数点を含む値を使っていますので誤差が発生し、ピッタリ1や0にならないことに注意してください。

アクションゲームなどキャラクターをジャンプさせるためには、ゲーム内に重力を実装します。y軸方向には鉛直投げ上げの公式を使うことで、重力のある世界でジャンプできます。またy軸にsin関数を使うことで擬似的なジャンプも表現できます。

左の「重力あり」では、投げ上げの公式を使っており重力があるため、頂点でゆっくりになって、落下するときに加速していきます。ただ、我々の住む世界の重力加速度(9.8m/s^2)をそのまま使っても良いジャンプにはならないので、ゲーム用に調整する必要があります。

右の「擬似重力」ではsin関数を使っています。sinは0→1→0→-1→0のような値をとるので、プラスになる部分だけを使い、それに係数をかけることで放物線状にジャンプできます。

次に衝突判定、いわゆる当たり判定です。アクションゲームやシューティングゲームには必要不可欠で、プレイヤーが被弾したかどうかなどを判定する方法です。

衝突判定は、オブジェクトが「何か」にぶつかったかどうかを判定する処理です。矩形同士の衝突判定はわりと簡単なのですが、円形や3Dになると数学の知識が必要になります。

矩形同士の場合は、selfオブジェクトとtargetオブジェクトが重なったときに衝突したと判定できます。ただ、いきなりこの条件式を見せられてもわらかないと思いますので、詳しく説明いたします。

考え方のポイントは「衝突していない場合」から先に考えることです。まず縦方向で考えると①と②になります。おなじく横方向だと③と④になり、これらすべてを式であらわすと(self.top >= target.bottom || target.top >= self.bottom || self.left >= target.right || target.left >= self.right)となります。

これは「衝突していない」ときの条件なので、「衝突している」条件に書き換えると!(条件)になり、ド・モルガンの法則をつかって展開するとself.top < target.bottom && target.top < self.bottom && self.let < target.right && target.left < self.rightとなり、さきほどのサンプルコードと同じになります。

[ディレクターズ・カット版]
矩形同士の衝突判定は簡単でしたが、円オブジェクトが入ってくるとちょっとだけ難しくなります。

円と矩形が重なったと判定するためには、ピタゴラスの定理を使います。円の中心と矩形の角を頂点として直角三角形をつくって考えると、円の半径より斜辺の長さが短くなった場合、円と矩形が衝突したと判定できます。

最後に復習です。

ループはrequestAnimationFrameでフレームスキップ対策をする。ユーザー入力はaddEventListenerを使う。座標の更新は等速直線運動や重力などを考えたり、レンダリングはCanvasAPIを使ったり…。

今回紹介したことを使えば、みなさんも今すぐブラウザゲームがつくれます!ですので、この発表でちょっとでも興味を持っていただいた方はぜひゲーム開発を試してみてください。



おまけ: 「さくらのINFRA WARS」のサウンドデザイン

[ディレクターズ・カット版]
ここからJavaScriptと全然関係ない話をします。

[ディレクターズ・カット版]
実はゲーム開発の話とかはどうでもよくて、「サウンドデザイン」の話が一番伝えたかった内容ですw
ということで、「さくらのINFRA WARS」のサウンドデザインについてちょっとだけ話そうと思います。

[ディレクターズ・カット版]
もともと音響エンジニアになりたくて上京し、大学では音響効果やMA(映画やアニメーションに音をつける作業)を専攻していました。しかし、ゲームのサウンドデザインについては初挑戦だったので、自分にいくつか課題をかしました。

ひとつは、弊社は「さくらインターネット」なのでBGMはすべて「さくらさくら」のメロディをモチーフにすること。ひとつは、ファミコンは矩形波2つ、三角波1つ、ノイズ1つしか同時に発音できないので、この制約を守ること。ひとつは、ゲームの邪魔にならないけど耳に残りやすい楽しいサウンドにすることです。

[ディレクターズ・カット版]
すべてのBGMに「さくらさくら」のモチーフを使うため、いくつかのアレンジパターンを考えました。リズムを変える、リハーモナイズする(コードを変える)、短調から長調に移調する、モチーフを反転させるなどです。

※ 「さくらさくら」は正確には短調ではなく日本音楽で使われている陰音階

[ディレクターズ・カット版]
メインテーマは「さくら、さくら、やよいのそらーは」の部分をモチーフとして使用し、リズムを変えてアレンジしました。ゲーム中ずっと流れている曲ですので、曲に意識を奪われないように音を詰め込みすぎないようにしたり、サイバーなカッコいい雰囲気を出すために短調のまま利用したりしています。

ゲームオーバーは、最後の「みにゆーかん」の部分をモチーフとして使用し、sus4→sus2→マイナーで終止することでゲームオーバー時の沈む心情を表現しました。

[ディレクターズ・カット版]
コーヒーブレイクは、「いざや、いざや、みにゆーかん」の部分をモチーフとして使用しています。この曲はステージ間の小休止する部分で、効果音とは一緒に発音させないため、細かなシーケンスフレーズで楽しそうな雰囲気を演出しています。また、かわいいキャラクターたちが登場するのに合わせて、短調→長調に移調しています。

[ディレクターズ・カット版]
ゲーム中の効果音はプレイヤーの動きに依存する部分が多く、発音タイミングが予期できません。そのため、不協和音にならないBGMと同じスケール内に収まる音のみを使用しました。ですので、どんなタイミングで発音されても、音は濁らないはずです。

ファミコンには厳しい同時発音数の制約がありますが、ブラウザゲームには制約はありません。好きなだけ音を出せてしまうのです。しかし、それをしてしまうとレトロゲームの雰囲気を壊しかねません。そのため、すべての効果音に優先順位をつけ、優先度が高い効果音が発音されるときは、優先度の低い効果音を止めるなど最大同時発音数を超えないように工夫しています。

皆さんも、ぜひブラウザゲームをつくってみてください!



関連記事






以上


written by @bc_rikko

0 件のコメント :

コメントを投稿