レッスン 24

最終課題:シューティングゲームをつくろう

このコースで学んだことを全て使ってシューティングゲームをつくろう

チャプター1このレッスンのゴール

このレッスンで完成させるものを確認しよう

1. 1 完成形の仕様

</pre>
今回作るゲームの仕様を紹介するよ。
・キーボードの方向キーでプレイヤーを左右に移動させるよ。
・スペースキーを押すとプレイヤーが弾を発射するよ。
・敵が縦二段に並んで徐々に降りてくるよ。
・下まで敵が下りてくるとゲームオーバーになるから弾を敵に当てて敵を倒そう。
</pre>

チャプター2プレイヤーを表示しよう

このチャプターでは、プレイヤーのクラスを作って表示する所まで作るよ。

2. 1 完成コード

//プレイヤーの変数
let player;

//最初に一回だけ呼び出される初期設定
function setup() {
  //キャンバスの設定
  createCanvas(480, 640);
  //カラーモードの設定
  colorMode(HSB, 360, 100, 100, 3);
  //Playerクラスのインスタンスを生成
  player = new Player();
}

//繰り返し呼び出される処理
function draw() {
  //背景色設定
  background(200, 80, 30);
  //プレイヤーを描画
  player.display();
}

//Playerクラス
class Player {
  constructor() {
    //x座標
    this.x = width / 2;
    //y座標
    this.y = height - 20
  }
  //描画の処理
  display() {
    //塗りつぶす色
    fill(200, 80, 100);
    //線を無効
    noStroke();
    //四角の描画元の指定
    rectMode(CENTER);
    //四角を描画
    rect(this.x, this.y, 20, 50, 5);
  }
}

コードが書けたら実行してみよう。

画面の下中央に青い四角が表示されていればOKだよ。

2. 2 コードの解説

変数

//プレイヤーの変数
let player;

player変数を宣言しているよ。

関数

  • setup()関数
//最初に一回だけ呼び出される初期設定
function setup() {
  //キャンバスの設定
  createCanvas(480, 640);
  //カラーモードの設定
  colorMode(HSB, 360, 100, 100, 3);
  //Playerクラスのインスタンスを生成
  player = new Player();
}

createCanvas()解像度を幅480高さ640に設定しているよ。
colorMode()カラーモードをHSBに色相の最大値を360彩度、明度の最大値を100、透明度の最大値を3に設定
player = new Player()Playerクラスのインスタンス生成、新しいプレイヤーを一つ作ったイメージだよ。

  • draw()関数
//繰り返し呼び出される処理
function draw() {
  //背景色設定
  background(200, 80, 30);
  //プレイヤーを描画
  player.display();
}

background()で背景色(色相を200,彩度を80,明度を30)を設定しているよ。
player.display()player変数のdisplay()メソッドを実行してプレイヤーを描画しているよ。

オブジェクト

  • Playerクラス
//Playerクラス
class Player {
  constructor() {
    //x座標
    this.x = width / 2;
    //y座標
    this.y = height - 20
  }
  //描画の処理
  display() {
    //塗りつぶす色
    fill(200, 80, 100);
    //線を無効
    noStroke();
    //四角の描画元の指定
    rectMode(CENTER);
    //四角を描画
    rect(this.x, this.y, 20, 50, 5);
  }

constructor関数はクラスのインスタンスを生成したときに実行される関数だよ。
this.xに画面の幅の半分this.yで画面の高さから20引いた数の座標の値を設定しているよ。
display()メソッドでは描画の設定をして四角をプレイヤーに見立てて表示しているよ。
rectMode(CENTER)に指定するとはrect()関数の第一第二引数の場所を中心に四角を描画するよ。
rect()の第5引数に値を入れると四角の角が丸くなるよ。

ここまでの見本46

 

チャプター3プレイヤーを動かせるようにしよう

このチャプターでは、プレイヤーをキーボード操作で移動する機能を作っていくよ。

3. 1 コードを変更しよう

draw()関数を変更しよう。

//繰り返し呼び出される処理
function draw() {
  //背景色設定
  background(200, 80, 30);
+ //プレイヤーを移動
+ player.move();
  //プレイヤーを描画
  player.display();
}

playerクラスを変更しよう。

//Playerクラス
class Player {
  constructor() {
    //x座標
    this.x = width / 2;
    //y座標
    this.y = height - 20
+   //移動速度
+   this.speed = 5;
  }
  //描画の処理
  display() {
    //塗りつぶす色
    fill(200, 80, 100);
    //線を無効
    noStroke();
    //四角の描画元の指定
    rectMode(CENTER);
    //四角を描画
    rect(this.x, this.y, 20, 50, 5);
  }
+ //移動の処理
+ move() {
+   //右へ移動
+   if (keyIsDown(RIGHT_ARROW)) {
+     this.x += this.speed;
+     //左へ移動
+   } else if (keyIsDown(LEFT_ARROW)) {
+     this.x -= this.speed;
+   }
+   //左移動の制限
+   if (this.x < 0) {
+     this.x = 0;
+   }
+   //右移動の制限
+   if (width < this.x) {
+     this.x = width;
+   }
+ }
}

コードが書けたら実行してみよう。

キーボードの左右のアローキーで’←’‘→’でプレイヤーが移動できればOKだよ。

注意

  • タイトル画面をつくるまでは、キャンバス部分をアクティブにした状態でキー操作ができるようになります。以下の画像を参考にしてください。

3. 2 コードの解説

関数

  • draw()関数の変更箇所
  //プレイヤーを移動
  player.move();

Playerクラスのインスタンスのmove()関数を呼び出しているよ。
move()の内容は後で見ていくよ。

オブジェクト

  • Playerクラスの変更箇所
    //移動速度
    this.speed = 5;

this.speedの値がmove()関数の移動速度になるよ。

  //移動の処理
  move() {
    //右へ移動
    if (keyIsDown(RIGHT_ARROW)) {
      this.x += this.speed;
      //左へ移動
    } else if (keyIsDown(LEFT_ARROW)) {
      this.x -= this.speed;
    }
    //左移動の制限
    if (this.x < 0) {
      this.x = 0;
    }
    //右移動の制限
    if (width < this.x) {
      this.x = width;
    }
  }

keyIsDown()関数で引数に指定したキーが押されているかがわかるよ。
右矢印キーが押されたらplayerのx座標をthis.speedの値だけ右に移動するよ。
左矢印キーが押されたらplayerのx座標をthis.speedの値だけ左に移動するよ。
画面から外れないようにx座標が0より少ない数になったら0に, 
width(画面右端)の値を超えたらwidthの値になるようにしているよ。

ここまでの見本47

 

チャプター4タイトル画面を追加しよう  

このチャプターではタイトル画を追加して画面をクリックしたらゲームが始まる機能を作るよ。

4. 1 コードを変更しよう  

  • 変数を追加しよう。
  //プレイヤーの変数
  let player;
+ //スタートしているかどうかの変数
+ let isStart = false;
  • draw()関数を変更しよう。
  //繰り返し呼び出される処理
  function draw() {
    //背景色設定
    background(200, 80, 30);
+   //スタートしているかどうかで処理を分ける
+   if (isStart) {
      //プレイヤーを移動
      player.move();
      //プレイヤーを描画
      player.display();
+   } else {
+     //塗りつぶす色の設定
+     fill(0,0,100);
+     //線を無効
+     noStroke();
+     //文字の大きさ
+     textSize(34);
+     //アラインの設定
+     textAlign(CENTER, CENTER);
+     //文字を描画
+     text("click to start!", width / 2, height / 2);
+     //マウスを押したときの処理
*     if (mouseIsPressed) {
+       isStart = true;
+     }
+   }
  }

コードが書けたら実行してみよう。

click to start!の文字が表示されて画面をクリックしてプレイヤーが表示されたらOKだよ。

4. 2 コードの解説

変数

//スタートしているかどうかの変数
let isStart = false;

スタートしているかどうかの変数isStartを宣言してfalseに初期化しているよ。

関数

  • draw()関数の変更箇所
if (isStart) {
    //プレイヤーを移動
    player.move();
    //プレイヤーを描画
    player.display());
  } else {
    //塗りつぶす色の設定
    fill(0,0,100);
    //線を無効
    noStroke();
    //文字の大きさ
    textSize(34);
    //アラインの設定
    textAlign(CENTER, CENTER);
    //文字を描画
    text("click to start!", width / 2, height / 2);

isStartだったら(isStarttrue)今までどうりプレイヤーの表示と移動をするよ。
そうでなければ(isStartfalse)
文字表示の設定をして”click to start!”を表示しているよ。
fill(0,0,100)明度と彩度が0なので白になるよ。
textAlign(CENTER, CENTER)テキストの揃える水平、垂直の位置を中央に指定しているよ。

    //マウスを押したときの処理
    if (mouseIsPressed) {
      isStart = true;
    }

mouseIsPressedはマウスを押している時にtrue押していない時にfalseになるよ。
マウスを押した時にスタートしているかの変数isStarttrueにしているよ。

ここまでの見本48  

 

チャプター5敵を表示しよう

このチャプターでは敵を表示するところまで作るよ。
少し難しくなってくるからじっくり取り組もう。

5. 1 コードを変更しよう  

  • 変数を追加しよう。
  //プレイヤーの変数
  let player;
  //スタートしているかどうかの変数
  let isStart = false;
+ //敵の変数
+ let enemys = Array(20);
  • draw()関数を変更しよう。
  function draw() {
    //背景色設定
    background(200, 80, 30);
    //スタートしているかどうかで処理を分ける
    if (isStart) {
      //プレイヤーを移動
      player.move();
      //プレイヤーを描画
      player.display();
      //四角の描画元の指定
      rectMode(CENTER);
+     //敵を描画
+     for (let enemy of enemys) {
+       enemy.display();
+     }
    } else {
      //塗りつぶす色の設定
      fill(0, 0, 100);
      //線を無効
      noStroke();
      //文字の大きさ
      textSize(34);
      //アラインの設定
      textAlign(CENTER, CENTER);
      //文字を描画
      text("click to start!", width / 2, height / 2);
      //マウスを押したときの処理
      if (mouseIsPressed) {
        isStart = true;
+       enemysSetup();
      }
    }
  }
  • draw()関数の後ろにenemysSetup()関数を追加しよう。
//敵のポジションを設定
function enemysSetup() {
  for (let j = 0; j < 2; j++) {
    for (let i = 0; i < enemys.length / 2; i++) {
      enemys[i + j * enemys.length / 2] = new Enemy(i * 40, 60 - j * 60)
    }
  }
}
  • Playerクラスの後ろにEnemyクラスを追加しよう。
+ //Enemyクラス
+ class Enemy {
+   constructor(_x, _y) {
+     //x座標
+     this.x = _x;
+     //y座標
+     this.y = _y;
+     //辺の長さ
+     this.sidelength = 30;
+     //ライフ
+     this.life = 3;
+   }
+   //描画の処理
+   display() {
+     //3回繰り返す
+     for (let i = 0; i < 3; i++) {
+       //線の色
+       stroke(160, 80, 100, this.life);
+       //線の太さ
+       strokeWeight(3);
+       //塗りつぶしを無効
+       noFill();
+       //四角を描画
+       rect(this.x, this.y, this.sidelength * (i + 1) / 3, this.sidelength * (i + 1) / 3);
+     }
+   }
+ }

コードが書けたら実行してみよう。

タイトル画面でクリックした後に左上に敵が表示されていたらOKだよ。

5. 2 コードの解説

変数

  • 追加した変数
//敵の変数
let enemys = Array(20);

敵の変数enemysを20の長さの配列で初期化しているよ。

lengthプロパティ

配列.lengthで、配列の長さを調べて値として返すことができます。

  • 例1
var array = ["りんご","バナナ","ぶどう"]
console.log(array.length)

コンソールに3と表示されます。

  • 例2
let enemys = Array(20);//enemys変数に長さ20の空の配列を代入
console.log(enemys.length)

コンソールに20と表示されます。

for…of ループ文

for…of 文は配列などの繰り返しが可能なオブジェクトに対してループ処理をするよ。

for ( 変数 of 配列 ) { 変数への処理 }

配列をfor…of文で繰り返すと、配列内の値を変数に取り出して、配列内の値すべてに処理をすることができるよ。
for文と違って繰り返しの回数を指定しなくていいから短くかけるよ。

  • forループの例
var array = ["りんご", "バナナ", "ぶどう"]
//forループで配列をコンソールに表示
for (var i = 0; i < array.length; i++) {
  console.log(array[i]);
}
  • forループの例をfor...ofループで置き換えた例
var array = ["りんご", "バナナ", "ぶどう"]
//for...ofループで配列をコンソールに表示
for (let a of array) {//変数aにarray配列内の値が繰り返し1つずつ代入される
  console.log(a);//aに一回目は"りんご"二回目は"バナナ"三回目は"ぶどう"が入る
}

どちらも同じでコンソールに
りんご
バナナ
ぶどう
と表示されます。

関数

  • draw()関数の変更箇所
     //敵を描画
     for (let enemy of enemys) {
       enemy.display());
     }

enemys配列の要素をenemy変数に取り出していくよ。
配列の要素全てのdisplay()メソッドを呼びだして敵を描画しているよ。

      enemysSetup();

enemysSetup()関数を呼び出しているよ。
内容は次に見ていくよ。

  • EenemysSetup()関数
//敵のポジションを設定
function enemysSetup() {
  for (let j = 0; j < 2; j++) {
    for (let i = 0; i < enemys.length / 2; i++) {
      enemys[i + j * enemys.length / 2] = new Enemy(i * 40, 60 - j * 60)
    }
  }
}

敵を二段に並べるために、二重ループで敵のポジションを設定してenemys配列の要素にEnemyクラスのインスタンスを代入しているよ。
for (let j = 0; j < 2; j++) {}外側のループが2回、 (j == 0の時が下の1段目、j == 1の時が上の2段目)
for (let i = 0; i < enemys.length / 2; i++) {}内側のループがenemys配列の長さ20の半分で10回、(1段毎に10の敵を並べる)
10回のループを2回繰り返しているから20回繰り返すことになるよ。

enemys[i + j * enemys.length / 2] = new Enemy(i * 40, 60 - j * 60) の値の変化を見てみよう。
j i enemys[i + j * enemys.length / 2] = new Enemy(i * 40, 60 - j * 60)
0 0 enemys[0] = new Enemy(0, 60)
0 1 enemys[1] = new Enemy(40, 60)
0 2 enemys[2] = new Enemy(80, 60)
0 3 enemys[3] = new Enemy(120, 60)
0 4 enemys[4] = new Enemy(160, 60)
0 5 enemys[5] = new Enemy(200, 60)
0 6 enemys[6] = new Enemy(240, 60)
0 7 enemys[7] = new Enemy(280, 60)
0 8 enemys[8] = new Enemy(320, 60)
0 9 enemys[9] = new Enemy(360, 60)
1 0 enemys[10] = new Enemy(0, 0)
1 1 enemys[11] = new Enemy(40, 0)
1 2 enemys[12] = new Enemy(80, 0)
1 3 enemys[13] = new Enemy(120, 0)
1 4 enemys[14] = new Enemy(160, 0)
1 5 enemys[15] = new Enemy(200, 0)
1 6 enemys[16] = new Enemy(240, 0)
1 7 enemys[17] = new Enemy(280, 0)
1 8 enemys[18] = new Enemy(320, 0)
1 9 enemys[19] = new Enemy(360, 0)

オブジェクト

  • Enemyクラス
//Enemyクラス
class Enemy {
  constructor(_x, _y) {
    //x座標
    this.x = _x;
    //y座標
    this.y = _y;
    //辺の長さ
    this.sidelength = 30;
    //ライフ
    this.life = 3;
  }
  //描画の処理
  display() {
    //3回繰り返す
    for (let i = 0; i < 3; i++) {
      //線の色
      stroke(160, 80, 100, this.life);
      //線の太さ
      strokeWeight(3);
      //塗りつぶしを無効
      noFill();
      //四角の描画元の指定
      rectMode(CENTER);
      //四角を描画
      rect(this.x, this.y, this.sidelength * (i + 1) / 3, this.sidelength * (i + 1) / 3);
    }
  }
}

constructor()関数でx座標、y座標、辺の長さ、ライフの値を設定しているよ。
display()関数で描画の設定をして、四角を1辺の長さの1/3,2/3,3/3の幅と高さで3回描画しているよ。
storoke()の第4引数(透明度の値)をライフの値にして、ライフが0になったら完全に透明になるようにしているよ。

ここまでの見本49 

 

チャプター6敵を移動させよう

このチャプターでは敵が移動してくる機能を作るよ。
敵は右に動いて行って、画面の端まで行った敵がいたら下に下りてきて左に移動向きを反転するよ。
左端についたらまた下りてきて、右に動いていくという動作を繰り返すよ。

6. 1 コードを変更しよう

  • draw()関数を変更しよう。
  //繰り返し呼び出される処理
  function draw() {
    //背景色設定
    background(200, 80, 30);
    //スタートしているかどうかで処理を分ける
    if (isStart) {
      //プレイヤーを移動
      player.move();
      //プレイヤーを描画
      player.display();
+     //敵が端に来たかどうか
+     let enemyIsCameEdge = false;
      //敵の数だけ繰り返す
      for (let enemy of enemys) {
+       //敵を移動
+       enemy.move();
        //敵を描画
        enemy.display();
+       //x座標とライフの状況を確認
+       if ((enemy.x > width || enemy.x < 0) && enemy.life > 0) {
+         enemyIsCameEdge = true;
+       }
      }
+     //端にきていたら下に移動
+     if (enemyIsCameEdge) {
+       for (let enemy of enemys) {
+         enemy.shiftDown();
+       }
+     }
    } else {
      //塗りつぶす色の設定
      fill(0, 0, 100);
      //線を無効
      noStroke();
      //文字の大きさ
      textSize(34);
      //アラインの設定
      textAlign(CENTER, CENTER);
      //文字を描画
      text("click to start!", width / 2, height / 2);
      //マウスを押したときの処理
      if (mouseIsPressed) {
        isStart = true;
        enemysSetup();
      }
    }
  }
  • Enemyクラスを変更しよう。
  //Enemyクラス
  class Enemy {
    constructor(_x, _y) {
      //x座標
      this.x = _x;
      //y座標
      this.y = _y;
      //辺の長さ
      this.sidelength = 30;
      //ライフ
      this.life = 3;
+     //移動速度
+     this.xSpeed = 1.5;
    }
    //描画の処理
    display() {
      //3回繰り返す
      for (let i = 0; i < 3; i++) {
        //線の色
        stroke(160, 80, 100, this.life);
        //線の太さ
        strokeWeight(3);
        //塗りつぶしを無効
        noFill();
        //四角の描画元の指定
        rectMode(CENTER);
        //四角を描画
        rect(this.x, this.y, this.sidelength * (i + 1) / 3, this.sidelength * (i + 1) / 3);
      }
    }
+   //下に降りる
+   shiftDown() {
+     this.xSpeed *= -1;
+     this.y += this.sidelength;
+   }
+   //移動の処理
+   move() {
+     this.x += this.xSpeed;
+   }
  }

コードが書けたら実行してみよう。

ゲームスタートして敵の移動の動きができていればOKだよ。

6. 2 コードの解説

関数

  • draw()関数の変更点
    let enemyIsCameEdge = false;
    //敵の数だけ繰り返す
    for (let enemy of enemys) {
      //敵を移動
      enemy.move();
      //敵を描画
      enemy.display();
      //x座標とライフの状況を確認
      if ((enemy.x > width || enemy.x < 0) && enemy.life > 0) {
        enemyIsCameEdge = true;
      }
    }
    //端にきていたら下に移動
    if (enemyIsCameEdge) {
      for (let enemy of enemys) {
        enemy.shiftDown();
      }

敵が端に来たかどうかを記録する変数enemyIsCameEdgefalseで宣言、初期化しているよ。
for...ofループでenemys配列の値を1つずつenemy変数に代入するよ。
enemy.move() enemy.display()で敵の移動と描画をしているよ。
敵のx座標が画面の幅を超えているか、もしくは0以下かつ、ライフが0以上だったら
敵が端に来たかどうかを記録する変数enemyIsCameEdgetrueにしているよ。
enemyIsCameEdgetrueだったら敵を全部shiftDown()関数を実行しているよ。
move() shiftDown()の内容は次に見ていくよ。

オブジェクト

  • Enemyクラスの変更点
    //移動速度
    this.xSpeed = 1.5;

敵の横移動の速度を設定しているよ。  

  • shiftDown()関数
  //下に降りる
  shiftDown() {
    this.xSpeed *= -1;
    this.y += this.sidelength;
  }

移動速度this.xSpeed に-1を掛けて移動の向きを反転しているよ。
(1.5に-1をかけると-1.5に、-1.5に-1をかけると1.5になるよ。)
y座標に辺の長さを足して画面下に移動するようにしているよ。

  • move()関数
  //移動の処理
  move() {
    this.x += this.xSpeed;
  }

x座標の値にスピードの値を足して横に移動しているよ。

ここまでの見本50  

 

チャプター7弾を撃てるようにしよう

このチャプターではプレイヤーが弾を撃つ機能を作っていくよ。

7. 1 コードを変更しよう

変数を追加しよう。

  //プレイヤーの変数
  let player;
  //スタートしているかどうかの変数
  let isStart = false;
  //敵の変数
  let enemys = Array(20);
+ //弾の変数
+ let bullets = [];

draw()関数を変更しよう。

//繰り返し呼び出される処理
  function draw() {
    //背景色設定
    background(200, 80, 30);
    //スタートしているかどうかで処理を分ける
    if (isStart) {
      //プレイヤーを移動
      player.move();
      //プレイヤーを描画
      player.display();
      //敵が端に来たかどうか
      let enemyIsCameEdge = false;
      //敵の数だけ繰り返す
      for (let enemy of enemys) {
        //敵を移動
        enemy.move();
        //敵を描画
        enemy.display();
        //x座標とライフの状況を確認
        if ((enemy.x > width || enemy.x < 0) && enemy.life > 0) {
          enemyIsCameEdge = true;
        }
      }
      //端にきていたら下に移動
      if (enemyIsCameEdge) {
        for (let enemy of enemys) {
          enemy.shiftDown();
        }
      }
+     //弾の数だけ繰り返す
+     for (let bullet of bullets) {
+       //弾を移動
+       bullet.move();
+       //弾を表示
+       bullet.display();
+     }
+     //弾の配列を更新
+     bullets = bullets.filter(b => b.isVisible);
    } else {
      //塗りつぶす色の設定
      fill(0, 0, 100);
      //線を無効
      noStroke();
      //文字の大きさ
      textSize(34);
      //アラインの設定
      textAlign(CENTER, CENTER);
      //文字を描画
      text("click to start!", width / 2, height / 2);
      //マウスを押したときの処理
      if (mouseIsPressed) {
        isStart = true;
        enemysSetup();
      }
    }
  }

draw()関数の後ろにkeyPressed()関数を追加しよう。

//キー操作
function keyPressed() {
  //スペースキーを押したら
  if (key == ' ') {
    //弾を配列に追加
    bullets.push(new Bullet(player.x, height - 20));
  }
}

Enemyクラスの後ろにBulletクラスを追加しよう。

//Bulletクラス
+ class Bullet {
+   constructor(_x, _y) {
+     //x座標
+     this.x = _x;
+     //y座標
+     this.y = _y;
+     //半径
+     this.r = 8;
+     //表示するかどうか
+     this.isVisible = true;
+   }
+   //移動の処理
+   move() {
+     this.y -= 5;
+     if (this.y < 0) {
+       this.isVisible = false;
+     }
+   }
+   //描画の処理
+   display() {
+     //線を無効
+     noStroke();
+     //塗りつぶす色
+     fill(20, 80, 100);
+     //円を描画
+     ellipse(this.x, this.y, this.r * 2, this.r * 2);
+   }
+ }

コードが書けたら実行してみよう。

スペースキーを押して弾が発射されたらOKだよ。

7. 2 コードの解説

変数

  • 追加した変数
//弾の変数
let bullets = [];

弾の変数bulletsを長さ0の配列で宣言、初期化しているよ。

アロー関数

Function (関数)はfunction () {};をアロー関数を使った() => {};に書き換えることができます。

  • function () {};を使った例
var showName = function(obj) {
  console.log('名前は' + obj + 'です。');
}
showName("太郎")

コンソールに名前は太郎です。と表示します。

  • function () {};() => {};に書き換えた例(引数がひとつの場合は => {};に省略可能)
var showName = obj => {
  console.log('名前は' + obj + 'です。');
}
showName("太郎");

こちらも同じでコンソールに名前は太郎です。と表示します。
アロー関数を使うと短く書くことができます。

filter()メソッド

filter()メソッドは、引数に条件式の関数を入れると条件がtrueになるものを新しい配列にして返してくれるよ。

  • 例 文字数3の物だけ新しい配列にしてコンソールに表示する
var array = ["りんご","バナナ","ぶどう","パイナップル","オレンジ"]
console.log(array.filter(a => a.length == 3));//`a`に配列の要素が取り出される、'length== 3'のものだけ新しい配列に追加する。

コンソールに["りんご", "バナナ", "ぶどう"]と表示されます。

関数

  • draw()関数の変更点
    //弾の数だけ繰り返す
    for (let bullet of bullets) {
      //弾を移動
      bullet.move();
      //弾を表示
      bullet.display();
    }
    //弾の配列を更新
    bullets = bullets.filter(b => b.isVisible);

for...ofループでbullets配列をループ処理しているよ。(弾の数だけ繰り返す)
bullet.move() bullet.display()ですべての弾の移動と描画をしているよ。

bullets.filter(b => b.isVisible) では、
filter()メソッドでisVisibleプロパティがtrueの弾だけbulletsの配列から取りだして新しい配列をつくっているよ。
取り出した配列をbullets変数に代入して配列の中身を更新しているよ。
(isVisiblefalse(見えていない)の弾は削除される)

  • keyPressed()関数
//キー操作
function keyPressed() {
  //スペースキーを押したら
  if (key == ' ') {
    //弾を配列に追加
    bullets.push(new Bullet(player.x, height - 20));
  }
}

keyPressed()関数はキーボードを押した時に呼び出されるよ。
key == ' '' 'はスペースが入っているよ。  
スペースキーが押されたらbullets配列に新しい弾を追加しているよ。

オブジェクト

  • Bulletクラス
  constructor(_x, _y) {
    //x座標
    this.x = _x;
    //y座標
    this.y = _y;
    //半径
    this.r = 8;
    //表示するかどうか
    this.isVisible = true;
  }

this.xthis.yが座標、this.rが半径、this.isVisible表示するかの設定をしているよ。

  • move()関数
  //移動の処理
  move() {
    this.y -= 5;
    if (this.y < 0) {
      this.isVisible = false;
    }
  }

this.y5引いて上に移動するようにしているよ。
this.y0より小さくなったら(画面の上端に来たら)表示しないようにしているよ。

  • display()関数
  //描画の処理
  display() {
    //線を無効
    noStroke();
    //塗りつぶす色
    fill(20, 80, 100);
    //円を描画
    ellipse(this.x, this.y, this.r * 2, this.r * 2);
  }

弾の表示の設定、線と色の設定をして、円を描画しているよ。
this.r * 2は半径かける2で直径になるよ

ここまでの見本51 

 

チャプター8弾が敵と当たるようにしよう

敵と弾が当たるようにして弾が当たったら敵の表示を変えられるようにしよう。

8. 1 コードを変更しよう

draw()関数を変更しよう。

  //繰り返し呼び出される処理
  function draw() {
    //背景色設定
    background(200, 80, 30);
    //スタートしているかどうかで処理を分ける
    if (isStart) {
      //プレイヤーを移動
      player.move();
      //プレイヤーを描画
      player.display();
      //敵が端に来たかどうか
      let enemyIsCameEdge = false;
      //敵の数だけ繰り返す
      for (let enemy of enemys) {
        //敵を移動
        enemy.move();
        //敵を描画
        enemy.display();
        //x座標とライフの状況を確認
        if ((enemy.x > width || enemy.x < 0) && enemy.life > 0) {
          enemyIsCameEdge = true;
        }
      }
      //端にきていたら下に移動
      if (enemyIsCameEdge) {
        for (let enemy of enemys) {
          enemy.shiftDown();
        }
      }
      //弾の数だけ繰り返す
      for (let bullet of bullets) {
        //弾を移動
        bullet.move();
        //弾を表示
        bullet.display();
+       //敵の数だけ繰り返す
+       for (let enemy of enemys) {
+         //ヒットしていたら
+         if (bullet.isHit(enemy)) {
+           //球を非表示
+           bullet.isVisible = false;
+           //ライフを減らす
+           enemy.life -= 1;
+         }
+       }
      }
      //弾の配列を更新
      bullets = bullets.filter(b => b.isVisible);
    } else {
      //塗りつぶす色の設定
      fill(0, 0, 100);
      //線を無効
      noStroke();
      //文字の大きさ
      textSize(34);
      //アラインの設定
      textAlign(CENTER, CENTER);
      //文字を描画
      text("click to start!", width / 2, height / 2);
      //マウスを押したときの処理
      if (mouseIsPressed) {
        isStart = true;
        enemysSetup();
      }
    }
  }

Bulletクラスを変更しよう。

  //Bulletクラス
  class Bullet {
    constructor(_x, _y) {
      //x座標
      this.x = _x;
      //y座標
      this.y = _y;
      //半径
      this.r = 8;
      //表示するかどうか
      this.isVisible = true;
    }
    //移動の処理
    move() {
      this.y -= 5;
      if (this.y < 0) {
        this.isVisible = false;
      }
    }
    //描画の処理
    display() {
      //線を無効
      noStroke();
      //塗りつぶす色
      fill(20, 80, 100);
      //円を描画
      ellipse(this.x, this.y, this.r * 2, this.r * 2);
    }
+   //当たり判定
+   isHit(enemy) {
+     //ライフが0なら当たらない
+     if (enemy.life == 0) {
+       return false;
+     }
+     //弾と敵の距離が弾の半径+敵の1辺の長さの半分より短くなったら当たり
+     if (dist(this.x, this.y, enemy.x, enemy.y) < this.r + enemy.sidelength / 2) {
+       return true;
+     }
+     //それ以外は当たらない
+     return false;
+   }
  }

コードが書けたら実行してみよう。

敵に弾が当たるようになっていたらOKだよ。

8. 2 コードの解説

returnについて

return文は関数の実行を終了して、値を指定すれば関数の呼び出し元に値を返します。

  • 足し算をする関数
function add(x, y) {
  return x + y;//引数 x + y の結果を返す
}
console.log(add(1, 2));

コンソールに3が表示されます。

  • マイナスの引数を与えたら何もしないようにした例
function add(x, y) {
  if (x < 0 || y < 0) { //引数xかyが0より小さければ
    return//何も返さず関数を終了
  }
  return x + y; //引数 x + y の結果を返す
}
console.log(add(-1, -2));

コンソールにundefinedが表示されます。

関数

  • draw()関数の変更点
      //敵の数だけ繰り返す
      for (let enemy of enemys) {
        //ヒットしていたら
        if (bullet.isHit(enemy)) {
          //球を非表示
          bullet.isVisible = false;
          //ライフを減らす
          enemy.life -= 1;
        }
      }

for...ofループでenemys配列の値を1つずつenemy変数に代入するよ。
ヒットしていたら弾の表示を非表示にして、敵のライフを減らしているよ。

オブジェクト

  • BulletクラスisHit()関数
  //当たり判定
  isHit(enemy) {
    //ライフが0なら当たらない
    if (enemy.life == 0) {
      return false;
    }
    //弾と敵の距離が弾の半径+敵の1辺の長さの半分より短くなったら当たり
    if (dist(this.x, this.y, enemy.x, enemy.y) < this.r + enemy.sidelength / 2) {
      return true;
    }
    //それ以外は当たらない
    return false;
  }

敵のライフをチェックして0だったら当たらない(falseを返す)
dist(this.x, this.y, enemy.x, enemy.y)で弾と敵の距離がわかるよ。
弾と敵の距離が弾の半径に敵の一辺の長さの半分を足したものより短かかったら当たりにしているよ。
それ以外は当たっていないよ。

ここまでの見本52 

 

チャプター9ゲームを仕上げよう

スコアの表示やゲームオーバー、敵の復活の機能を作って一通り遊べるようにして完成させよう。

9. 1 コードを変更しよう

変数を追加しよう。

  //プレイヤーの変数
  let player;
  //スタートしているかどうかの変数
  let isStart = false;
  //敵の変数
  let enemys = Array(20);
  //弾の変数
  let bullets = [];
+ //最初のゲームかどうかの変数
+ let isFirstGame = true;
+ //スコアの変数
+ let score = 0;

draw()関数を変更しよう。

  //繰り返し呼び出される処理
  function draw() {
    //背景色設定
    background(200, 80, 30);
    //スタートしているかどうかで処理を分ける
    if (isStart) {
      //プレイヤーを移動
      player.move();
      //プレイヤーを描画
      player.display();
      //敵が端に来たかどうか
      let enemyIsCameEdge = false;
      //敵の数だけ繰り返す
      for (let enemy of enemys) {
        //敵を移動
        enemy.move();
        //敵を描画
        enemy.display();
        //x座標とライフの状況を確認
        if ((enemy.x > width || enemy.x < 0) && enemy.life > 0) {
          enemyIsCameEdge = true;
        }
+       //プレイヤーと敵のy座標を確認
+       if (player.y <= enemy.y) {
+         isStart = false;
+         isFirstGame = false;
+       }
      }
      //端にきていたら下に移動
      if (enemyIsCameEdge) {
        for (let enemy of enemys) {
          enemy.shiftDown();
        }
      }
      //弾の数だけ繰り返す
      for (let bullet of bullets) {
        //弾を移動
        bullet.move();
        //弾を表示
        bullet.display();
        //敵の数だけ繰り返す
        for (let enemy of enemys) {
          //ヒットしていたら
          if (bullet.isHit(enemy)) {
            //ライフを減らす
            enemy.life -= 1;
            //球を非表示
            bullet.isVisible = false;
+           //スコアを加算
+           score += 10;
          }
        }
      }
      //弾の配列を更新
      bullets = bullets.filter(b => b.isVisible);
+     //敵がすべてライフが0だったら
+     if (enemys.every(enemy => enemy.life == 0)) {
+       //敵をセットアップ
+       enemysSetup();
+       //弾を初期化
+       bullets = [];
+     }
    } else {
      //塗りつぶす色の設定
      fill(0, 0, 100);
      //線を無効
      noStroke();
      //文字の大きさ
      textSize(34);
      //アラインの設定
      textAlign(CENTER, CENTER);
      //文字を描画
+     if (isFirstGame) {
        text("click to start!", width / 2, height / 2);
+     } else {
+       text("Game Over!\nclick to start!", width / 2, height / 2);
+     }
      //マウスを押したときの処理
      if (mouseIsPressed) {
        isStart = true;
        enemysSetup();
+       score = 0;
      }
    }
+   //線を無効
+   noStroke();
+   //塗りつぶす色の設定
+   fill(0, 0, 100);
+   //アラインの設定
+   textAlign(LEFT, CENTER);
+   //文字の大きさ
+   textSize(16);
+   //文字を描画
+   text("score: " + score, 20, height - 20);
  }

コードが書けたら実行してみよう。

敵を全部倒したらまた敵が出てきて、敵が画面したまできたらゲームオーバーになるか確認してみよう。
スコアが左下に表示されて敵に当たるとスコアが上がっていればOKだよ。

9. 2 コードの解説

every()メソッド

every()メソッドは、引数に条件式の関数を入れると、
配列のすべての要素がtureならtrueを1つでもfalseならfalseを返します。

  • 配列の要素がすべて3文字かチェックする(結果がtrueの例)
var array = ["りんご","バナナ","ぶどう"]//配列の要素がすべて三文字
console.log(array.every(a => a.length == 3));//配列の要素がすべて三文字かチェックする。

コンソールにtrueと表示されます。

  • 配列の要素がすべて3文字かチェックする(結果がfalseの例)
var array = ["りんご","バナナ","ぶどう","オレンジ"]//オレンジが三文字ではない
console.log(array.every(a => a.length == 3));//配列の要素がすべて三文字かチェックする。

コンソールにfalseと表示されます。

関数

  • draw()関数の変更点
//最初のゲームかどうかの変数
let isFirstGame = true;
//スコアの変数
let score = 0;

最初のゲームかどうかで表示を変えるための変数isFirstGameとスコアを記録する変数scoreを用意したよ。

      //プレイヤーと敵のy座標を確認
      if (player.y <= enemy.y) {
        isStart = false;
        isFirstGame = false;
      }

敵のy座標がプレイヤーのy座標より大きくなったらisStartfalseにしてゲームを終了するよ。
isFirstGamefalseにしてスタートしていない時の表示を変えるよ。

        //ヒットしていたら
        if (bullet.isHit(enemy)) {
          //球を非表示
          bullet.isVisible = false;
          //ライフを減らす
          enemy.life -= 1;
          //スコアを加算
          score += 10;
        }

弾が敵に当たった時にスコアを加算するようにしているよ。

    //敵がすべてライフが0だったら
    if (enemys.every(enemy => enemy.life == 0)) {
      //敵をセットアップ
      enemysSetup();
      //弾を初期化
      bullets = [];
    }

every()メソッドで敵のライフが全て0(enemy.life == 0)かどうか調べているよ。
敵を全部倒したらenemysSetup()で敵を初期化して並べ直して、bullets = []弾も初期化しているよ。

  //文字を描画
    if (isFirstGame) {
      text("click to start!", width / 2, height / 2);
    } else {
      text("Game Over!\nclick to start!", width / 2, height / 2);
    }
    //マウスを押したときの処理
    if (mouseIsPressed) {
      isStart = true;
      enemysSetup();
      score = 0;
    }

タイトル画面で最初のゲームかどうかisFirstGameで表示を分けているよ。
mouseIsPressedマウスを押してゲームをはじめる時にscore = 0にリセットしているよ。

  //線を無効
  noStroke();
  //塗りつぶす色の設定
  fill(0, 0, 100);
  //アラインの設定
  textAlign(LEFT, CENTER);
  //文字の大きさ
  textSize(16);
  //文字を描画
  text("score: " + score, 20, height - 20);

線と色、テキストアラインと文字サイズの設定をして、
スコアを表示するように設定しているよ。

ここまでの見本53  

 

チャプターを全部クリアしよう!
前のレッスン
次のレッスン