インベーダーの移動編
『弾幕インベーダー』の解説シリーズの3回目はインベーダーの移動についてみていきたいと思います。インベーダーは全員が行列に綺麗に並んでいるため、移動制御は割と単純ですが、インベーダーの数によって移動速度を調整していたり細かい工夫があります。では実際のコードを見てみましょう。
『弾幕インベーダーゲーム』で遊びたい方は以下よりアクセスしてください。
ソースコードはGitHub上にアップしています。
インベーダーの移動制御の定義一覧
インベーダーの移動制御に使用するグローバル変数一覧
変数名 | 内容 |
---|---|
tx | インベーダー共通のx座標 |
tmove | インベーダーの移動量 |
tcount | インベーダーの生存数(移動速度に関係) |
tact | 移動速度を調整するカウンタ |
teki | インベーダーのインスタンスを管理する二次元配列 |
インベーダーのクラス定義(Enemyクラス)
インベーダーは縦5、横10の50体が並んでいますので、複数のインベーダーを制御する必要があります。このように複数のキャラクタを制御するときはオブジェクト指向が強力です。今回のインベーダーもクラスとして定義しており、実際のインベーダーはインスタンス(オブジェクト)として配列で管理していますd。
フィールド名 | 型 | 場所 | 内容 |
---|---|---|---|
image | string | static | インベーダーの画像データのファイルパス |
x | number | インスタンス | インベーダーのX座標 |
y | number | インスタンス | インベーダーのY座標 |
alive | boolean | インスタンス | インベーダーが生きているかどうかの判定用フラグ |
class Enemy {
constructor(_alive, _x, _y, _image) {
this.x = _x;
this.y = _y;
this.image = _image;
this.alive = _alive;
}
}
インベーダーのクラス宣言を見てみると、前回のブログで紹介した自機のミサイルクラス(Jtamaクラス)とほとんど内容が同じです。こういう場合は「継承」を使って設計した方がいいですね。このシリーズの最後にリファクタリングしますので、その際に継承を使ったコード例をご紹介したいと思います。
インベーダーの移動制御の関数一覧
クラス名 | 内容 |
---|---|
tdrow() | インベーダーの描画 |
game() | tdrow()関数の定期呼び出し |
function tdrow() {
for (let i = 0; i < teki.length; i++) {
for (let j = 0; j < teki[i].length; j++) {
if (teki[i][j].alive) { //インベーダーが生存しているとき
//インベーダーの描画
let tekiimg = new Image();
tekiimg.src = teki[i][j].image;
ctx.drawImage(tekiimg, teki[i][j].x+tx, teki[i][j].y, 32, 24);
if (!miss) { //自機が被弾状態でないとき
if (teki[i][j].x+tx > 800-32){ //インベーダーが右端に到達したとき
tmove=-2; //左方向への移動にセット
}else if (teki[i][j].x+tx < 0){ //インベーダーが左端に到達したとき
tmove=2; //右方向への移動にセット
}
shotchance(i, j); //インベーダーの攻撃の抽選
}
}
}
}
if (!miss) { //自機が被弾状態でないとき
//インベーダーの数に応じて移動速度を変える
tact = tact + 1; //カウントアップ
if (tact >= tcount) { //カウンタが生存しているインベーダーの数を超えたとき
tx = tx + tmove; // //インベーダーのx座標の更新
tact = 0; //カウンタをクリア
}
}
}
function game() {
~中略~
tdrow();
~中略~
}
多次元配列のfor文による走査の定石
for (let i = 0; i < teki.length; i++) {
for (let j = 0; j < teki[i].length; j++) {
オブジェクト指向言語の多次元配列は一次元配列をオブジェクトとして扱うため、配列の長さ(要素数)は一定でない、いわゆるジャグ配列を採用している言語がほとんどです。このように配列の長さが一定でないときの多次元配列をfor文で走査するときはlengthフィールドを使うのが定石です。
四次元配列のfor文による走査のコード例
//arrが四次元配列だった場合の全データの走査例
for (let i = 0; i < arr.length; i++){
for (let j = 0; j < arr[i].length; j++){
for (let k = 0; k < arr[i][j].length; k++){
for (let l = 0; l < arr[i][j][k].length; l++){
console.log(arr[i][j][k][l]);
}
}
}
}
一次元目の配列の長さ:arr.length
二次元目の配列の長さ:arr[i].length
三次元目の配列の長さ:arr[i][j].length
四次元目の配列の長さ:arr[i][j][k].length
データの参照・代入:arr[i][j][k][l]
上記のように各次元ごとの配列の長さを指定すれば、配列の長さが一定でなくてもfor文を使ってすべてのデータを走査することができます。
インベーダーの移動速度の調整
tact = tact + 1; //カウントアップ
if (tact >= tcount) { //カウンタが生存しているインベーダーの数を超えたとき
tx = tx + tmove; // //インベーダーのx座標の更新
tact = 0; //カウンタをクリア
}
インベーダーは数が少なくなると移動速度も速くなるように調整しています。それを制御しているのが上記のコードです。変数tcountにはインベーダーの生存数が格納されています。インベーダーは最大50体ですので、最初は「50体 × 10msec」でおよそ0.5秒ごとにtmoveずつ移動します。インベーダーの残りが1体になると「1体 × 10msec」つまり約0.01秒ごとにtmoveずつ移動することになりますので、残り1体になると最初のスピードの1/50のスピードでインベーダーが移動します。このようにインベーダーの移動速度を変えることでゲーム性を高めています。
コラム~スペースインベーダーの速度の秘話~
1978年に発売され日本のアーケードゲーム史上最大のヒット作である『スペースインベーダー』もインベーダーが減っていくと移動速度が速くなっていました。今の感覚で想像すると意図して速度を上げていたように思ってしまいますが、実は昔のCPUはスペックが低かったため、たくさんのインベーダーを動かすと処理の負荷が大きくなって、自然と移動速度が遅くなっていたようです。つまりCPU性能の問題で、プログラム的にわざと速度を上げていたのではなかったんですね。今のコンピューターのCPU性能は各段に上がっているため、プログラムで意図的に制御してあげる必要があります。CPUの進化は凄いですね。
さいごに
今回はインベーダーの移動制御についてのコードを見てきました。単に左右に移動するだけですと処理は単純になりますが、ゲーム性が損なわれてしまいます。インベーダーの数によって移動速度を変えるだけでゲーム性を上げることができます。ゲーム性を上げることはUXの大切な要素だと思います。今回はステージによる速度の調整は行っていませんが、ステージ数による速度の調整もあるとよりゲーム性が上がりますね。次回はインベーダーの攻撃についてのコードを見ていきたいと思います。以降もお楽しみに!
ちょっと息抜き
プログラミングを学習していると、脳をフル稼働させますし、じっと座ってコーディングしているときは目も身体も疲れてしまいます。そんな時は一息入れて、Yahoo!トラベルを見て現実逃避しましょう♪
Yahoo!トラベルはお得が一杯
- 取り扱い施設数が約17000施設!
- リーズナブルな価格での宿泊施設から贅沢な宿泊施設まで掲載!
- 日程や希望の条件からプランの検索・比較が簡単!
- 期間限定のクーポンがいっぱい!
- PayPayポイントが使える・貯まる!
- エリア×テーマで最も売れている宿をデイリーで更新中!
具体的に旅行を考えている方はもちろん、旅行の計画がなくてもYahoo!トラベルを見ているだけでほんのちょっと旅行に行った気分を味わえます。良い気分転嫁が出来ると思いますので興味がある方はアクセスしてみてください。