起動時、定期処理編
ヒューマンネット上本町センターの利用者さんは個性的なクリエーターばかりで、中にはゲームを開発しているメンバーもいます。利用者さんが作ったゲームは当事業所のホームページでも公開しておりますが、遊んでもらうだけではもったいない!ということで、プログラミングの内容についてもご紹介していこうと思っています。コードの中でも特に重要と思われる個所については「Point」として解説しています。自作でゲームを作ってみたいと思っている方やプログラミングを学習中の方のご参考になれば嬉しいです。
初回は「避けゲー」のプログラミングをご紹介します。
「避けゲー」は障害物を避けながら、ひたすら前進し、ステージをクリアしていく単純なゲームですが、ステージによって障害物の形や動き方が違ったり、自キャラと障害物との当たり判定があったりとプログラミングのエッセンスが満載です。
「避けゲー」で遊びたい方は以下よりアクセスしてください。
避けゲーのプログラムは600行を超えますので、何回かに分けてご紹介していく予定です。コードの中の変数名や関数名が分かりにくかったり、Upper caseとLower caseが混在していたり、もっと良いコーディング方法があったりするとは思いますが、温かい目で見守ってください。
ソースコードはGitHub上にアップしています。
関数定義一覧
避けゲーで使用している関数一覧です。
関数名 | 処理概要 |
---|---|
draw() | 自キャラの表示 |
restartGame() | リセット時の処理 |
test() | デバッグ用の処理 |
game() | ゲームのメイン処理 |
keyDownHandler(e) | キー押下時のイベントハンドラー |
keyUpHandler(e) | キー開放時のイベントハンドラー |
enemy1() | 第一ステージの左側の障害物の表示 |
enemy2() | 第一ステージの右側の障害物の表示 |
enemy3() | 第二ステージの障害物の表示 |
enemy4() | 第三ステージの内側の円形障害物の表示 |
enemy5() | 第三ステージの外側の円形障害物の表示 |
enemy6() | 第四ステージの障害物表示 |
reenemy6() | 復路の第四ステージの障害物表示 |
enemy7() | 第五ステージの障害物の表示 |
tre() | 最終ステージの障害物の表示 |
ReE() | 右から追いかけてくる障害物の表示 |
clr() | ステージクリア時の処理 |
グローバル変数定義一覧
避けゲーで使用しているグローバル変数一覧です。
変数名 | 内容 | 変数名 | 内容 |
---|---|---|---|
canvas | キャンバスの取得 | em3 | 障害物を点滅判定をクリア |
ctx | コンテキストの取得 | et3 | 障害物の点滅用カウント |
x | スタート時の自キャラのx座標 | er4 | 障害物の回転の移動量 |
stage | ステージ番号 | er5 | 障害物の回転の移動量 |
Pressed | スペースキーが押されたかの判定フラグ | e6 | 障害物の仕掛けの稼働許可をクリア |
Re | 往路側(右に進んでいくステージ)にセット | ex6 | 移動する障害物のベースとなるx座標 |
Rex | 障害物のx座標 | em6 | 移動する障害物の演出用のカウンタ |
Rem | 障害物を揺らす演出用カウンタ | et6 | 障害物の仕掛け用のカウンタ |
deatheffect | 自キャラが倒れた時の演出用カウンタ | re6 | 障害物の仕掛けの稼働許可をクリア |
gamewait | ゲームの一時停止フラグ | rex6 | 移動する障害物のベースとなるx座標 |
gameover | ゲームオーバー | rem6 | 移動する障害物の演出用のカウンタ |
img | 自キャラの画像用オブジェクト生成 | ret6 | 障害物の仕掛け用のカウンタ |
ex1 | 障害物のy軸 | ex7 | 障害物のベースのx座標 |
exm1 | 障害物の上下方向の移動量 | ey7 | 障害物の初期の頂点の位置 |
ex2 | 障害物の初期のy座標 | eh7 | 隣の障害物との間隔 |
ey2 | 障害物の初期のx座標 | em7 | 上下移動の移動量 |
em2 | 正方向(横軸は右、縦軸は下)への移動量 | et7 | 第五ステージ用のカウンタ |
e3 | 障害物が有効・無効判定を無効にセット | evt | 復路のイベント用カウンタをクリア |
起動時の処理
起動時の処理について、avoider.jsから抜粋したコードとそれをUMLで図示したものを掲載しておきます。
JavaScriptは特に指定がない限り、htmlファイル内のscriptタグでリンクされたjsファイルが読み込まれたタイミングでjsファイルに書かれているコードを上から順番に実行します。htmlファイル自体も上から順番に読み込まれるため、jsファイルのリンクは基本的にはhtmlファイル内のbodyの閉じタグの直前に設定することが多いです。
避けゲーでも同様にhtmlコードを先に読んで、次にJavaScriptのコードの読み込む順番としています。jsファイルの読み込むタイミングの指定方法は他にもいろいろなやり方があります。
起動時のアクティビティ図
起動時処理のコード(avoider.jsから抜粋)
var canvas = document.getElementById("myCanvas"); //キャンバスの取得
var ctx = canvas.getContext("2d"); //コンテキストの取得
let x=20; //スタート時の自キャラのx座標
let stage=1; //ステージ番号
let Pressed = false; //スペースキーが押されたかの判定フラグ
let Re = false; //往路側(右に進んでいくステージ)にセット
let Rex = 0; //障害物のx座標
let Rem = 2; //障害物を揺らす演出用カウンタ
let deatheffect = 0; //自キャラが倒れた時の演出用カウンタ
let gamewait = false; //ゲームの一時停止フラグ
let gameover=false; //ゲームオーバー
var img = new Image(); //自キャラの画像用オブジェクト生成
document.addEventListener("keydown", keyDownHandler, false); //キー押下時のイベント登録
document.addEventListener("keyup", keyUpHandler, false); //キー開放時のイベント登録
setInterval(game, 10); //メイン処理を定期的(10msec毎)に実行
let ex1=0; //障害物のy軸
let exm1=3.9; //障害物の上下方向の移動量
let ex2 = 100; //障害物の初期のy座標
let ey2 = 800; //障害物の初期のx座標
let em2 = 1.9; //正方向(横軸は右、縦軸は下)への移動量
let e3 = false; //障害物が有効・無効判定を無効にセット
let em3 = 0; //障害物を点滅判定をクリア
let et3 = 0; //障害物の点滅用カウント
let er4 = 0; //障害物の回転の移動量
let er5 = 0; //障害物の回転の移動量
let e6 = false; //障害物の仕掛けの稼働許可をクリア
let ex6 = 200; //移動する障害物のベースとなるx座標
let em6 = 150; //移動する障害物の演出用のカウンタ
let et6 = 0; //障害物の仕掛け用のカウンタ
let re6 = false; //障害物の仕掛けの稼働許可をクリア
let rex6 = 900; //移動する障害物のベースとなるx座標
let rem6 = 150; //移動する障害物の演出用のカウンタ
let ret6 = 0; //障害物の仕掛け用のカウンタ
let ex7 = 500; //障害物のベースのx座標
let ey7 = 120; //障害物の初期の頂点の位置
let eh7 = 160; //隣の障害物との間隔
let em7 = 4; //上下移動の移動量
let et7 = 0; //第五ステージ用のカウンタ
let evt=0; // 復路のイベント用カウンタをクリア
addEventListener("keydown", keyDownHandler, false)
addEventListener()はWeb APIでEventTargetインタフェースが提供しているメソッドです。第一引数で指定したイベントが発生すると、第二引数で指定した関数をコールバックします。addEventListenerの詳細について以下のサイトをご参考ください。
setInterval(func, delay);
setInterval()はWeb APIでWindowインタフェースが提供しているメソッドです。第二引数で指定したdelayミリ秒ごとに第一引数で指定したfunc関数を実行します。setIntervalはオーバーロードされており、いくつかのパターンが用意されています。setIntervalの詳細について以下のサイトをご参考ください。
window.requestAnimationFrame(update);
setInterval()と似たようなメソッドにrequestAnimationFrame()メソッドがAPIとして提供されています。このメソッドは基本的には60FPSでコールバック関数(第一引数update)を呼んでくれる仕様となっていますので便利なメソッドですね。仕様の詳細は以下をご確認ください。
10msec毎の定期処理
次にメインとなる処理を見ていきましょう。メイン処理(game()関数)は起動時に10msec毎にコールバックされるように設定しています。プログラム中にたくさんのカウンタを用意していますが、これらのカウンタは10msec毎にカウントアップすることになります。
メイン処理では
- キャンバスのクリア
- ステージ毎の描画
- 往路と復路に分けて、自キャラの移動とキー操作
を行っています。
描画するタイミングは今回は10msecに1度に設定しています。これは1秒間に100回更新することになるため100FPSに相当します。人間には残像現象がありますので、一般的には30FPS以上であればカクつくことはありません。フレームレート(FPS)は高ければ滑らかな描画になりますが、その分、処理の負担が大きくなってしまいます。フレームレートをどのくらいに設定するかは重要なポイントですね。
今回フレームレートを10msecとしているのはキャラクタや障害物を移動させるときの計算が楽だから、という理由もあります。
10msec毎の定期処理のアクティビティ図
10msec毎の定期処理のコード(avoider.jsから抜粋)
//メイン処理
function game() {
ctx.clearRect(0, 0, canvas.width, canvas.height); //キャンバスのクリア
//ステージ毎の処理
switch(stage){
case 0:
clr(); //ステージクリア時の処理
break;
case 1:
//第一ステージの障害物表示
enemy1(); //左側の障害物の表示
enemy2(); //右側の障害物の表示
break;
case 2:
//第二ステージの障害物表示
enemy3();
break;
case 3:
//第三ステージの障害物表示
enemy4();
enemy5();
break;
case 4:
//第四ステージの障害物表示
if (Re) {
reenemy6();
}else{
enemy6();
}
break;
case 5:
//第五ステージの障害物表示
enemy7();
break;
case 6:
//第六ステージの障害物表示
tre();
break;
}
window.focus(); //画面をフォーカスさせる
draw();
if (Re) {
//復路側の処理
Rex=Rex+Rem;
evt++; //復路時のイベント用のカウントアップ
if (stage>=1&&stage<=5) { //第一ステージから第五ステージのとき
ReE(); //右から追いかけてくる障害物の表示
}
//障害物をゆらゆら揺らして少し難易度を上げる
if (Rex>=10) {
Rem=-1;
}else if (Rex<=-10) {
Rem=1;
}
if (!gameover&&!gamewait){ //ゲーム進行中のとき
if (Pressed) { //スペースキーが押されているとき
x-=2; //自キャラを左に移動する
}
if (x<=20) { //ステージの左端に到達したか(ステージをクリアしたか)
x=1180; //次のステージの初期のx座標をセット
stage--; //ステージ数を減らす(次のステージにセットする)
evt=0; //イベント用のカウンタをクリア
}
}
}else{
//往路側の処理
if (!gameover&&!gamewait){ //ゲームが進行中のとき
if (Pressed) { //スペースキーが押されているとき
x+=2; //自キャラを右に移動する
}
if (x>=1180) { //ステージの右端に到達したか(ステージをクリアしたか)
x=20; //次のステージの初期のx座標をセット
stage++; //ステージ数を増やす(次のステージにセットする)
}
}
}
}
多方向分岐を行いたいときはswitch文が強力です。
プログラム内では多くの状態を持つこともあるため(今回でいうと各ステージ)、状態毎に処理を分岐したいときにswitch文をよく使います。
if (Re) {
変数ReはBoolean型の変数ですからif文の中で直接参照するだけでOKです。Reがtrueであればif文の中の処理が実行されますし、Reがfalseであればif文の処理をスキップします。プログラミング初心者はよく「if (Re == true)」と書きがちですが、「Re」の実行結果も「Re == true」の実行結果もどちらも「true」ですので、前者の書き方の方がスマートです。
変数を参照した場合、変数に格納されているデータの値が実行結果となることを覚えておきましょう。
最後までご覧いただきありがとうございます。
長くなってきましたので続きはまた次回のブログでご紹介します。
グローバル変数の数がちょっと多いですね(;^_^A。オブジェクト指向で設計すれば各ステージ毎のグローバル変数は減らせそうですね。
今回は初回のゲーム開発だったこととJavaScriptにも初めて取り組んでもらったため、自由にコーディングしてもらいました。ブログで公開することも当初は決めてなかったので、本人にしかわからないようなコードになっていると思いますがご容赦ください。