見出し画像

障害物との接触はたった1行で解決!- クラッシュディテクションのライブラリを利用する - JavaScriptでやる1分間プログラミング

さて、これまでのコーディングで次のことができるようになりました。

✅ ユニコーン(円形)がスペースバーでジャンプする
✅ 地面でもジャンプ中でもユニコーンを左右の矢印キーで移動させる
✅ トレイン(四角形)が右からランダムに登場し、左に流れていく

画像1

ユニコーンのジャンプは当然、障害物を避けるためですが、今のところトレインの四角と接触しても何も起きません。そこで今回はこの「接触」を処理するコードを書いていきます

Collision(=衝突)を検知する

キャンバス上にある物体が接触したかどうかは意外に面倒な計算が必要となります。しかしゲームを始めとして2D描画ではよく使う機能なので、「ライブラリ」という、特定の機能を提供する仕組みを利用すると、なんと1行を追加するだけで物体同士が接触したかどうかチェックできます。

ステップ❶:ライブラリを参照する

まず準備として、これまで一切触らなかったindex.htmlのファイルでそのライブラリが使えるようにする1行を加えます。ここではindex.htmlをすべて掲載します。

<!DOCTYPE html>
<html lang="en">
 <head>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.js"></script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/addons/p5.sound.min.js"></script>

  <!-- 次の1行を加える -->
  <script defer src="https://unpkg.com/p5.collide2d"></script>

   <link rel="stylesheet" type="text/css" href="style.css">
   <meta charset="utf-8" />

 </head>
 <body>
   <script src="sketch.js"></script>
   <script src="cat.js"></script>
   <script src="train.js"></script>
 </body>
</html>

加えるのはこの1行です。

  <!-- 次の1行を加える -->
  <script defer src="https://unpkg.com/p5.collide2d"></script>

これでColllide2DというライブラリがJavaScriptのコードで使えるようになります。

ステップ2:draw関数の中で衝突をチェックする

Collide2Dを使えば、二つの物体が接触したかどうかのチェックはたったの1行ですみます。

let hit = collideRectCircle (train.x, train.y, train.r, train.r, 
    unicorn.x, unicorn.y, unicorn.r);

collideRectCircleはrectangle(四角)とcircle(円)がcollide(衝突した)ということをチェックするもので、もし衝突していたらtrue(真)、つまりYESという値をhitに入れ、そうでないとfalse(偽)、つまりNOとなります。collideRectCircle関数に渡す値として括弧の中に入れるのはトレインのX,YそしてR(注:ここは四角のタテとヨコの長さの両方を入れないといけないので、train.r, train.rと繰り返しています)の値を入れ、続いてユニコーンの位置と大きさに関する値を入れます。これだけでトレインとユニコーンがぶつかったかどうかを調べることができます。

ちなみにcollide2Dは円や四角などの図形だけではなく線との接触などいろんなものを対象に接触をチェックできます。p5.collide2Dのサンプルがたくさん紹介されていますので、もっと詳しい使い方を知りたい場合はこのサイトからp5エディタでのサンプルプロジェクトを見てください。

どこで衝突をチェックするかがポイント

ではトレインとユニコーンが衝突したかどうかはどこでチェックしたらよいのでしょうか。ここでポイントとなるのは、トレインがいくつもあるということです。

トレインの表示箇所をもう一度見てみます。

 //トレインを表示
for (let t of trains) {
  t.x -= 10;
  t.show();  
}

トレインはいくつものインスタンスが「配列」の入れ物に入っていましたよね。このうちどれと衝突したかわからないので、とりあえず全部チェックしないといけません。そこで、今はこのトレインの表示の際に、配列のインスタンスをループですべて取り出している(これを t に入れている)ので、ここで衝突をチェックすることにします。

 //トレインを表示
for (let t of trains) {
  t.x -= 10;
  t.show();
     
 //衝突をチェックする
 let hit = collideRectCircle (t.x, t.y, t.r, t.r, unicorn.x, unicorn.y, unicorn.r);
  if(hit) 
    noLoop();
}

トレインのどれかがユニコーンと衝突していたらhitには"true"という値が入り、その場合(つまりif (hit)は”hitがtrueだったら”という意味)、noLoop()、つまりdrawのループが止まって、画面全体がフリーズします。

画像2

これでゲームオーバーとなるわけです。

draw関数のところはこうなります。

function draw() {
  background(220);
  fill(200, 0, 200);

  //レインをランダムに発生させる
  if (random(0, 1) < 0.01)
    trains.push(new Train(width, height-50, 50));

  //トレインを表示
  for (let t of trains) {
    t.x -= 10;
    t.show();
     
   //衝突をチェックする
   let hit = collideRectCircle (t.x, t.y, t.r, t.r, unicorn.x, unicorn.y, unicorn.r);
    if(hit) 
      noLoop();
  }

  //左右のキーに動きを反応させる
  if (keyIsPressed) {
    if (key == "ArrowRight")
      unicorn.x += 10;
    else if (key == "ArrowLeft")
      unicorn.x -= 10;
  }
  
  unicorn.move(); //ユニコーンを動かす.
  unicorn.show(); //ユニコーンを表示させる
}

実は衝突チェックのタイミングでちょっと問題があります。draw関数の中での描画の順番を考えてみてください。まずトレインの配列をすべて動かして、その段階でユニコーンと接触していたらゲームオーバーになります。ところが、ユニコーンの描画はこの後に実行されます。左右の動きやジャンプを反映させてからユニコーンが描画されますが、この段階で実際にはユニコーンのジャンプや左右の動きで接触が回避されていたかもしれないのです。

そこで、もう少しリアルで正確に衝突をチェックするには、ユニコーンを表示させてからトレインを表示させ、その段階で衝突をチェックしたほうがよいでしょう。そこでトレインの表示ループを一番最後に持ってきます。

変更後のコードを全部表示します。もしうまくいかなければこのコードをコピペしてみてください。

let unicorn; //ユニコーン変数を作る
let trains = []; //レインの配列を作る

function setup() {
  createCanvas(400, 400);
  //ユニコーンを一つ作る
  unicorn = new Unicorn(100, height-25, 50);
}

//キー操作の検知
function keyPressed() {
  if (key == ' ')
   unicorn.jump(); //jump関数を呼び出す
}

function draw() {
  background(220);
  fill(200, 0, 200);

  //レインをランダムに発生させる
  if (random(0, 1) < 0.01)
    trains.push(new Train(width, height-50, 50));

  //左右のキーに動きを反応させる
  if (keyIsPressed) {
    if (key == "ArrowRight")
      unicorn.x += 10;
    else if (key == "ArrowLeft")
      unicorn.x -= 10;
  }
  
  unicorn.move(); //ユニコーンを動かす.
  unicorn.show(); //ユニコーンを表示させる

  //トレインを表示
  for (let t of trains) {
    t.x -= 10;
    t.show();
     
   //衝突をチェックする
   let hit = collideRectCircle (t.x, t.y, t.r, t.r, unicorn.x, unicorn.y, unicorn.r);
    if(hit) 
      noLoop();
  }
}

//ユニコーンのクラス
class Unicorn {
  //コンストラクタ
  constructor (x, y, r) {
    this.x = x;
    this.y = y;
    this.r = r;
    this.velocity = 0; //最初のジャンプ量はゼロにする
  }

  //jump関数を追加する
  jump() {
   this.velocity = 50; 
  }

  //動く
  move() {
    this.y -= this.velocity; //ジャンプ距離分をYから引く(上昇)
    this.velocity -= 5; //ジャンプ移動距離から重力分の5を引く
  }

  //クラスの表示 
  show() {
    this.y = constrain(this.y, 0, height-this.r/2);
    circle (this.x, this.y, this.r);
  }
}

//トレインのクラス
class Train {
constructor (x, y, r) {
  this.x = x;
  this.y = y;
  this.r = r;
}

show() {
  rect(this.x, this.y, this.r);
}
}

こうするだけで衝突のタイミングがさらに正確になるはずです。

画像3

ゲームオーバーの表示

どうせなら衝突した段階で「Game Over」のテキストを表示してみましょう。

  //衝突をチェックする
 let hit = collideRectCircle (t.x, t.y, t.r, t.r, unicorn.x, unicorn.y, unicorn.r);
  if(hit) {
    fill("Blue");
    textAlign(CENTER);
    textSize (50);
    textStyle(BOLD);
    text("GAME OVER", width/2, height/2);
    noLoop();     
  }

追加された5行はそれぞれ、テキストの色、太さ、サイズ、場所などを指定したものです。これで衝突が検知されると青の文字でGame Overが出てきます。

画像4

最終的なコードをもう一度掲載します。

let unicorn; //ユニコーン変数を作る
let trains = []; //レインの配列を作る

function setup() {
  createCanvas(400, 400);
  //ユニコーンを一つ作る
  unicorn = new Unicorn(100, height-25, 50);
}

//キー操作の検知
function keyPressed() {
  if (key == ' ')
   unicorn.jump(); //jump関数を呼び出す
}

function draw() {
  background(220);
  fill(200, 0, 200);

  //レインをランダムに発生させる
  if (random(0, 1) < 0.01)
    trains.push(new Train(width, height-50, 50));

  //左右のキーに動きを反応させる
  if (keyIsPressed) {
    if (key == "ArrowRight")
      unicorn.x += 10;
    else if (key == "ArrowLeft")
      unicorn.x -= 10;
  }
  
  unicorn.move(); //ユニコーンを動かす.
  unicorn.show(); //ユニコーンを表示させる

  //トレインを表示
  for (let t of trains) {
    t.x -= 10;
    t.show();
     
  //衝突をチェックする
  let hit = collideRectCircle (t.x, t.y, t.r, t.r, unicorn.x, unicorn.y, unicorn.r);
  if(hit) {
    fill("Blue");
    textAlign(CENTER);
    textSize (50);
    textStyle(BOLD);
    text("GAME OVER", width/2, height/2);
    noLoop();     
  }
}

//ユニコーンのクラス
class Unicorn {
  //コンストラクタ
  constructor (x, y, r) {
    this.x = x;
    this.y = y;
    this.r = r;
    this.velocity = 0; //最初のジャンプ量はゼロにする
  }

  //jump関数を追加する
  jump() {
   this.velocity = 50; 
  }

  //動く
  move() {
    this.y -= this.velocity; //ジャンプ距離分をYから引く(上昇)
    this.velocity -= 5; //ジャンプ移動距離から重力分の5を引く
  }

  //クラスの表示 
  show() {
    this.y = constrain(this.y, 0, height-this.r/2);
    circle (this.x, this.y, this.r);
  }
}

//トレインのクラス
class Train {
constructor (x, y, r) {
  this.x = x;
  this.y = y;
  this.r = r;
}

show() {
  rect(this.x, this.y, this.r);
}
}

これでゲームの基本機能はすべて入りました。次回は円や四角を実際のユニコーンとトレインの画像に置き換えてゲームを完成させます!

この記事が気に入ったらサポートをしてみませんか?