#さあ、はじめよう#
##はじめに##
本稿は掲題の通り javascript を用いて[ シューティングゲーム的な何か ]を作ろうという試みについて解説するテキストの第四回です。
##想定する読者##
- 割と暇である
- プログラミングに興味がある
- ゲーム作りに興味がある
- javascriptの基本をマスターしたけど特に作るものがない
- javascriptを使った動きのある処理を実装してみたい
- canvas でなんか作ってみたい
##本連載の狙い##
本連載はどちらかというと初心者向けです。
このページに検索からやってきた「ゲーム作りてえええええ」と日に三十回くらい叫んでいる小中学生諸君は、まずjavascriptの基本をお勉強してから本連載を読みましょう。
また、最終的に出来上がる[ シューティングゲーム的な何か ]は、そんなに大層なものではありません。シューティングゲームがどのような感じで作られていくのか、その過程を眺めていろいろ考えていただくキッカケを作ることが本連載の狙いです。
また、本連載では伝わりやすさ優先でテキストを書きます。たとえば javascript には厳密にはクラスはありませんが、連載内でクラスという言葉を使って解説します。このあたりは理解しやすさや、雰囲気を伝えることを優先して書きます。
##オンラインサンプル##
本連載は全10回を予定しています。
各回にはその時点までの[ シューティングゲーム的な何か ]のサンプルが付属します。
各テキストからリンクを張っておきますので、オンラインで実際に動作確認が行えます。
サンプルプログラム一式については著作権とかライセンスとかそういったものは一切ありません。
ちなみに、最終的に完成する[ シューティングゲーム的な何か ]はこんな感じです。
マウスによる移動、クリックによるショットが可能です。ESC キーでプログラムを停止します。
javascriptで作るシューティングゲーム的な何か(1)
javascriptで作るシューティングゲーム的な何か(2)
javascriptで作るシューティングゲーム的な何か(3)
javascriptで作るシューティングゲーム的な何か(4)
javascriptで作るシューティングゲーム的な何か(5)
javascriptで作るシューティングゲーム的な何か(6)
javascriptで作るシューティングゲーム的な何か(7)
javascriptで作るシューティングゲーム的な何か(8)
javascriptで作るシューティングゲーム的な何か(9)
javascriptで作るシューティングゲーム的な何か(最終回)
##書いてる人##
書いてる人はdoxasという人です。
こんな企画もやってますので、少しでも javascript でシューティングゲームを作成することに興味がわいたら、ぜひ参加してください。待ってますよ!!
#さて、つくろう#
前回は自機キャラクターをクラスで管理することについて考えました。
今回は、この自機キャラクターを管理するクラスを拡張することで、自機キャラクターがショットを撃てるようにしてみましょう。
ちなみに、ショットはマウスのクリックを検知して発射されるようにします。
##グローバル変数と定数の追加##
さて、まずは今回のサンプルで追加されるいくつかの変数と定数を見てみます。
// - global -------------------------------------------------------------------
var screenCanvas, info;
var run = true;
var fps = 1000 / 30;
var mouse = new Point();
var ctx;
var fire = false;
// - const --------------------------------------------------------------------
var CHARA_COLOR = 'rgba(0, 0, 255, 0.75)';
var CHARA_SHOT_COLOR = 'rgba(0, 255, 0, 0.75)';
var CHARA_SHOT_MAX_COUNT = 10;
今回新たに追加されたのがfire
という変数、さらにふたつの定数です。
変数fire
は、ショットを発射するのか、あるいはしないのか――これを真偽値で保持します。
先ほども書いたように、マウスのクリックを検知してショットを発射するようにしたいわけですから、クリックイベントから呼ばれる関数側では、この変数fire
の値をtrue
に設定してやります。ショットをひとつ発射したら、再度fire
にはfalse
を設定するわけです。
定数は、前回も登場したCHARA_COLOR
と同じように、自機ショットの色の指定に利用するCHARA_SHOT_COLOR
と、もうひとつCHARA_SHOT_MAX_COUNT
を定義します。
CHARA_SHOT_MAX_COUNT
のほうは、自機キャラクターが発射できるショットの上限を決めるために使います。
この上限値の意味は、あくまでも 画面上に出せるショットの最大値 です。ゲーム内でショットが 10 回しか撃てないという意味ではありませんので注意しましょう。
また前回のテキストで、ちょっとだけですがあえて定数を使ったほうがいい場合がある、という話をしました。
今回は自機キャラクターのショットの最大上限を定数で 10 と指定しましたね。これを、将来的に増やしたり減らしたりする際に、定数として管理していれば修正個所は初期化している部分の一か所だけで済みます。
キャラクターに固有の値はクラスのプロパティとして設定したほうがいい場合が多いです。しかし、定数を用いたほうが都合がいい場合もありますので、よく考えたうえで必要に応じて上手に定数扱いの変数を利用しましょう。
##イベント用関数の追加##
続いて、マウスクリックを検知する関数を追加します。
function mouseDown(event){
// フラグを立てる
fire = true;
}
非常に簡素です。フラグを立てているだけですね。
この関数は、onload
から呼ばれるメインプログラムの中で、その他のイベント用関数と同様に登録しておきます。
該当箇所は以下のようなコードになるわけです。
screenCanvas.addEventListener('mousemove', mouseMove, true);
screenCanvas.addEventListener('mousedown', mouseDown, true);
window.addEventListener('keydown', keyDown, true);
特別難しいことはないと思います。
一応書いておきますが、本連載のサンプルではここまでのみっつのイベントのみを使います。今後はイベント関連は増えません。
##character.js 側##
main.js の中身をさらに詳しく見る前に、まずは character.js 側に追加する自機ショット用のクラスから見ていきましょう。
function CharacterShot(){
this.position = new Point();
this.size = 0;
this.speed = 0;
this.alive = false;
}
CharacterShot.prototype.set = function(p, size, speed){
// 座標をセット
this.position.x = p.x;
this.position.y = p.y;
// サイズ、スピードをセット
this.size = size;
this.speed = speed;
// 生存フラグを立てる
this.alive = true;
};
CharacterShot.prototype.move = function(){
// 座標を真上にspeed分だけ移動させる
this.position.y -= this.speed;
// 一定以上の座標に到達していたら生存フラグを降ろす
if(this.position.y < -this.size){
this.alive = false;
}
};
CharacterShot
というショット管理用の新しいクラスを追加します。
コンストラクタでは各種プロパティの初期化のみを行います。プロパティのそれぞれの意味は以下のようになっています。
- position = Pointクラスのインスタンス
- size = 自機ショットのサイズ
- speed = 自機ショットの進むスピード
- alive = 自機ショットの生存フラグ
ほとんどが読んで字の如く、そのままの意味です。
最後の生存フラグというのはちょっとわかりにくいかもしれませんが、ショットは自機キャラクターと違い、画面上に描かれる場合とそうでない場合があります。alive
はこの描かれるべきなのかどうかという状態を管理するために使われるのですね。
プロパティのほかには、ふたつのメソッドが書かれています。
まず最初、set
メソッドは引数をみっつほど受け取り、それをもとに自機ショットを初期化します。
引数には、ショットの初期位置とサイズ、そしてスピードを与えます。このメソッドが呼び出されると、自動的に生存フラグが立つようになっているのが見てわかると思います。
続いてもうひとつのメソッドmove
ですが、これも読んで字の如く、ショットの動きに関する処理を受け持ちます。
// 座標を真上にspeed分だけ移動させる
this.position.y -= this.speed;
// 一定以上の座標に到達していたら生存フラグを降ろす
if(this.position.y < -this.size){
this.alive = false;
}
canavas 上の座標は、マウスカーソルなどの処理でも扱ったように、左上角が原点(0, 0)です。そして右に行けば行くほど X が大きな数字になり、同様に下に行けば行くほど Y が大きな数字になります。
自機ショットは、サンプルでは上の方向にまっすぐ進むように実装します。つまり、Y の値がどんどん 小さくなっていく ようにするわけです。
また、ショットは当然最後は画面外に消えていきます。
ここでショットの生存フラグを降ろしておくようにしないと、ショットを再度撃つことができなくなります。
よって、ショットの位置とサイズを考慮したうえで、画面外に出てしまったショットの生存フラグは降ろすような処理が書かれています。
##main.js でのショットの初期化##
さて、続いては再び main.js 側に戻って、先ほどの自機ショットのクラスを初期化するところ周辺を見ていきます。
ショットクラスのインスタンスは、冒頭で定数として定義したCHARA_SHOT_MAX_COUNT
の 10 という数値をもとにして、配列として生成します。
var charaShot = new Array(CHARA_SHOT_MAX_COUNT);
for(i = 0; i < CHARA_SHOT_MAX_COUNT; i++){
charaShot[i] = new CharacterShot();
}
これで、定数で指定したとおり 10 個の自機ショットを扱うことができるようになりました。
##ショットの生成##
無名関数のループのなかでは、変数fire
の中身を見ながら、ショットを撃つべきかどうか判断します。
// fireフラグの値により分岐
if(fire){
// すべての自機ショットを調査する
for(i = 0; i < CHARA_SHOT_MAX_COUNT; i++){
// 自機ショットが既に発射されているかチェック
if(!charaShot[i].alive){
// 自機ショットを新規にセット
charaShot[i].set(chara.position, 3, 5);
// ループを抜ける
break;
}
}
// フラグを降ろしておく
fire = false;
}
このように、定数CHARA_SHOT_MAX_COUNT
を利用したループ処理を用意します。
ループ処理のなかでは、まずalive
プロパティを参照することで、ショットが既に発射されているのかどうかを判断します。
もし、全てのショットのalive
プロパティがtrue
だった場合には、既に全てのショットが発射済みということになりますので、新規にショットを撃つことはできません。
もしalive
にfalse
が設定されているショットが見つかったら、新規にset
メソッドを呼び出してショットをセットします。ショットを生成する初期位置は、当り前ですが自機のいる座標です。サイズは 3 で速度は 5 にしてみました。
また、その新規セットの下でbreak
によってループを抜けるようになっていますが、これが結構重要です。
もし、break
を書き忘れてしまうと、fire
がtrue
だったときに、一度に全てのショットが発射されてしまいます。また、最終的にはfire
の値もfalse
に戻しておかないと、次に無名関数が呼ばれたときに無条件で再度ショットがセットされてしまいますので、気を付けましょう。
##ショットを一度に描き出す##
ショットを描くためのプロセスは、基本的には自機キャラクターを描く際と変わりません。
手順としては、パス設定開始を宣言 ⇒ パスを設定 ⇒ 描画命令 という流れです。ただし、ショットは最大で 10 個あります。毎回毎回、この手順を律儀に繰り返すというのは、ちょっと大変ですね。
そこで、次のようにします。
// パスの設定を開始
ctx.beginPath();
// すべての自機ショットを調査する
for(i = 0; i < CHARA_SHOT_MAX_COUNT; i++){
// 自機ショットが既に発射されているかチェック
if(charaShot[i].alive){
// 自機ショットを動かす
charaShot[i].move();
// 自機ショットを描くパスを設定
ctx.arc(
charaShot[i].position.x,
charaShot[i].position.y,
charaShot[i].size,
0, Math.PI * 2, false
);
// パスをいったん閉じる
ctx.closePath();
}
}
// 自機ショットの色を設定する
ctx.fillStyle = CHARA_SHOT_COLOR;
// 自機ショットを描く
ctx.fill();
ここでまず注目すべきは、beginPath
が一度しかコールされていない点です。同様に、図形を描くためのfill
メソッドも一度しかコールされません。自機ショットは 10 個あるはずなのに、パスの開始と描画命令はそれぞれ一度しかコールされません。
同じ形状で、なおかつ同じ色の指定となる図形に関しては、連続して一気にパスを設定してまとめて一度の描画命令で canvas 上に描くことが可能です。その鍵を握っているのが今回初登場のclosePath
メソッドです。
このclosePath
メソッドは、そこまでに指定されたパスの設定をいったん区切ってくれます。次にパスの設定が行われた場合には、先ほどのパスとは別のものとして個別に設定されるようにしてくれるのです。
基本的にパスはひと繋ぎになっているものなので、もしclosePath
メソッドを用いずに連続してパスを設定すると、全てのパスがつながった状態で図形が描き出されます。それはそれで複雑な図形を描けるので便利な場合もあるのですが、ショット同士がつながった状態で描かれてしまうのは困ります。そこで、closePath
でそれぞれを個別に区切り、最後にまとめて描画するという今回のような方法を用います。
#まとめ#
今回の修正で、クリックによって自機がショットを放つようになりました。
最大で画面上に配置できる自機ショットは 10 個を上限にしているので、超クリック連射でも 10 個以上は配置されません。実際にサンプルを動作させて確認してみるのもいいでしょう。
オンラインサンプル.04で今回のサンプルの動作を確認できます。