ドロップアイテム編
『弾幕インベーダー』の解説シリーズの8回目はアイテムドロップについて見ていきたい思います。インベーダーを倒すとランダムな確率でアイテムが落ちてきます。アイテムは全部で5種類用意しており、どのアイテムをドロップするかは抽選で決めています。ドロップアイテムをどのように制御しているかを見ていきましょう。
『弾幕インベーダーゲーム』で遊びたい方は以下よりアクセスしてください。
ソースコードはGitHub上にアップしています。
ドロップアイテム一覧
アイテムの種類は全部で5種類用意しています。
アイテム画像 | アイテム名 | アイテム詳細 | レア度 |
---|---|---|---|
1機アップアイテム | 自機の残機数が1つ増えます。 | ★★★★★ | |
シールドアイテム | 取ると自機にシールドが付きます。シールドは敵の攻撃を一度だけ無効化する効果があります。またシールド中にシールドアイテムを取った場合はスコアに500ポイントを加算するようにしています。 | ★★★★☆ | |
ボムアイテム | ボムを使える回数が1つ増えます。ボムアイテムは最大3つまで取ることができます。満タン時にボムアイテムと取った場合はスコアに500ポイントを加算するようにしています。ボムを使用すると画面上にある敵のミサイルをすべて無効化する効果があります。 | ★★★☆☆ | |
パワーアップアイテム | パワーアップアイテムは最大5つまで取ることができます。満タン時にパワーアップアイテムと取った場合はスコアに500ポイントを加算するようにしています。パワーアップアイテムは持っている数だけ連続してミサイル攻撃ができる効果があります。 | ★★☆☆☆ | |
スコアアップアイテム | スコアに500ポイントが加算されます。 | ★☆☆☆☆ |
ドロップアイテム制御の定義一覧
ドロップアイテム制御に使用するグローバル変数一覧
変数名 | 内容 |
---|---|
item | ドロップアイテムを管理する配列 |
ドロップアイテムのクラス定義(Itemクラス)
アイテムクラスはアイテムの種類、有効無効、座標で制御しています。
フィールド名 | 型 | 場所 | 内容 |
---|---|---|---|
type | Number | インスタンス | アイテムの種類 |
alive | Boolean | インスタンス | アイテムが有効かどうか |
x | Number | インスタンス | ドロップ時のX座標 |
y | Number | インスタンス | ドロップ時のY座標 |
aliveフィールド
他のクラスのインスタンスと同様にアイテムクラスのインスタンスも初期化時に生成したインスタンスを再利用しているためaliveフィールドを持つようにしています。
ドロップアイテムオブジェクトの初期化
弾幕のオブジェクトの初期化はjsファイルが読み込まれた際に行っています。
for (let i = 0; i <= 4; i++) {
item[i]=new Item(false, 0, 0, 0);
}
ドロップアイテム制御の関数一覧
弾幕を制御を主としている関数一覧は以下となります。
クラス名 | 内容 |
---|---|
draw() | ボムアイテムとパワーアイテムのステータス表示 |
nextstage() | 次のステージへ遷移する際に行うアイテムの一括無効化 |
respone() | 自機のリスポーン時に行うアイテムの一括無効化 |
dropchance() | ドロップアイテムの抽選処理 |
itemmove() | ドロップアイテムの落下処理 |
game() | itemmove()関数の定期呼び出し |
ドロップアイテムの抽選処理(dropchance関数)
アイテムの種類は全部で5種類用意しています。
dropchance()
function dropchance(_i, _j){
let ratio = Math.random()*100; //アイテムの抽選
if (ratio < 40) { //抽選結果が40より小さいとき(40%の確率でアイテムを落とす)
for (let i = 0; i < item.length; i++) {
if (!item[i].alive) { //アイテムが有効でないとき
item[i].alive = true; //アイテムを有効とする
item[i].x = teki[_i][_j].x + tx - 4; //アイテムを落とすインベーダーのx座標をセット
item[i].y = teki[_i][_j].y + 16; //アイテムを落とすインベーダーのy座標をセット
if (ratio < 1) { //抽選結果が1より小さいとき
item[i].type = 5; //1機アップアイテムをセット
} else if (ratio < 5) { //抽選結果が5より小さいとき
item[i].type = 4; //シールドアイテムをセット
} else if (ratio < 10) { //抽選結果が10より小さいとき
item[i].type = 3; //ボムアイテムをセット
} else if (ratio < 20) { //抽選結果が20より小さいとき
item[i].type = 2; //パワーアップアイテムをセット
} else { //抽選結果が上記以外のとき
item[i].type = 1; //スコアアップアイテムをセット
}
break;
}
}
}
}
アイテムの座標の設定
ドロップアイテムの座標は以下のように設定しています。
item[i].x = teki[_i][_j].x + tx - 4;
item[i].y = teki[_i][_j].y + 16;
「- 4」や「+ 16」としているのはインベーダーの画像とアイテムの画像の大きさが異なるため、インベーダーの中心から表示されるように微調整しています。
Math.random()
ゲームプログラミングでは多用されるランダムな数値を得るための関数です。戻り値は0以上、1未満の範囲の浮動小数点の疑似乱数値です。
アイテムの種別は1から5のリテラルな数値を使っていますが、リテラルな値はあまり推奨されません。なぜならリテラルな値は、値を見ても何の値かぱっと見では分かりにくいこと、値を変更するときに何か所も修正しないといけないことなどデメリットが多いためです。今回のように連続した値の場合は列挙型を使うのが一般的です。
ドロップアイテムの落下制御
ドロップアイテムの落下制御は以下のような順に各処理を行っています。
- ドロップアイテムの描画(4行目~34行目)
- 自機のアイテム取得制御(36行目~63行目)
- アイテムが画面外に出た時の処理(65行目~67行目)
itemmove()
function itemmove(){
for (k = 0; k < item.length; k++) {
if (item[k].alive) { //アイテム有効のとき
if (!miss && !clear) { //自機が被弾状態でない、かつ、クリア状態でない
item[k].y++; //アイテムのy座標を更新
}
//アイテム画像の描画
itemimga = new Image();
itemimgb = new Image();
itemimgc = new Image();
itemimgd = new Image();
itemimge = new Image();
itemimga.src = "res/item_s.png";
itemimgb.src = "res/item_p.png";
itemimgc.src = "res/item_b.png";
itemimgd.src = "res/item_sh.png";
itemimge.src = "res/item_ex.png";
switch (item[k].type){
case 1:
ctx.drawImage(itemimga, item[k].x, item[k].y, 40, 40);
break;
case 2:
ctx.drawImage(itemimgb, item[k].x, item[k].y, 40, 40);
break;
case 3:
ctx.drawImage(itemimgc, item[k].x, item[k].y, 40, 40);
break;
case 4:
ctx.drawImage(itemimgd, item[k].x, item[k].y, 40, 40);
break;
case 5:
ctx.drawImage(itemimge, item[k].x, item[k].y, 40, 40);
break;
}
//アイテムがゲットされたときの処理
if (item[k].y >= 400 && item[k].y <= 499) { //アイテムのy座標が400px~499pxのとき
if (item[k].x >= x-14 && item[k].x <= x+40) { //アイテムのx座標が自機のx座標と重なったとき
if (item[k].type == 1) { //ゲットしたアイテムがスコアアップアイテムのとき
score = score + 500; //スコアに500点追加
} else if (item[k].type == 2) { //ゲットしたアイテムがパワーアップアイテムのとき
if (power < 5){ //アイテム数が5個より小さいとき
power++; //アイテム数を増やす
}else{ //アイテム数が5つ以上のとき
score = score + 500; //スコアに500点追加
}
} else if (item[k].type == 3) { //ゲットしたアイテムがボムアイテムのとき
if (bomb < 3) { //アイテム数が3個より小さいとき
bomb++; //アイテム数を増やす
}else{ //アイテム数が3つ以上のとき
score = score + 500; //スコアに500点追加
}
} else if (item[k].type == 4) { //ゲットしたアイテムがシールドアイテムのとき
if (!shield) { //シールド中でないとき
shield = true; //シールド状態にセット
} else { //シールド中のとき
score = score + 500; //スコアに500点追加
}
}else if (item[k].type == 5) { //ゲットしたアイテムが1機アップアイテムのとき
zanki++; //残機数を増やす
}
item[k].alive = false; //アイテムを無効にする
}
}
//アイテムが流れた時の処理
if (item[k].y > 500) { //アイテムのy座標が500pxを超えたとき
item[k].alive = false; //アイテムを無効にする
}
}
}
}
ライン数は多いですが、やっていることは単純なことばかりです。工夫の余地はあるとおもいますが、分かりやすいコードを書く上では、やりたいことをストレートに表現するのも悪くないですね。
さいごに
今回はドロップアイテムの制御についてのコードを見てきました。ドロップアイテムがあるとゲームとしての見栄えもいいですし、クリアしやすくなるのでゲーム性も高まりますね。自機も弾幕を打てるようなアイテムを作ったり、ミサイルのスピードをアップするアイテムなど、アイデア次第でもっと楽しめるゲームになるかもしれませんね。ぜひいろいろなアイデアを考え、実際に実装してみてください。コードの解説はこれでおしまいです。次回はリファクタリングにチャレンジしたいと思います。次回もお楽しみに!
不要になったインスタンスはその都度削除し、必要になったらnewする、という設計方針でも問題ないと思います。