ゲーム開発ラボ

視覚的に楽しいアプリやゲーム開発をしながら,Javaやjavascriptを楽しく学んでいきます

横スクロールゲームを作る-3

前回は画像ファイルの読み込みと,走るアニメーションの実装を行いました
processing-p5.hateblo.jp

今回は障害物が右から左へと流れるようにし,当たり判定も実装しましょう
以下が今回作るアプリです
f:id:filopodia:20201117163619g:plain

trexがサボテンに衝突するとその時点でゲームオーバーとなります

今回のコードは以下となります

PImage rexJmpImg, rexRunImg1, rexRunImg2, rexHitImg, cactusImg;
float gravity = 1; // 下向きの重力加速度
Player rex;
Hurdle cactus;
Observer observer;
boolean hit;

void setup(){
  size(600,200);
  background(255);
  frameRate(60);
  imageMode(CENTER);
  rectMode(CENTER);
  rexJmpImg = loadImage("trex.png");
  rexRunImg1 = loadImage("trex_run1.png");
  rexRunImg2 = loadImage("trex_run2.png");
  rexHitImg = loadImage("trex_hit.png");
  cactusImg = loadImage("cactus.png");
  rex = new Player();
  cactus = new Hurdle();
  observer = new Observer(rex, cactus);
}

void draw(){
  background(255);
  line(0, 0.8*height, width, 0.8*height);
  cactus.update();
  rex.update();
  observer.update();
}

void keyPressed(){
  if(key==' '){rex.jump();} 
}

class Player{
  PImage rexImg;
  float px,py; 
  float vy; 
  float stdHeight = 0.7*height; 
  boolean isGrounded; 
  
  Player(){
    px = 50;
    py = stdHeight;
    vy = 0;
    isGrounded = true;
  }
  
  void update(){
    if(hit){image(rexHitImg, px, py, 55, 55);}
    else{
      vy += gravity;
      py += vy;
      if(py>=stdHeight){
        isGrounded = true; 
        py = stdHeight;
      }
    
      if(isGrounded){
        if(frameCount%10>0 && frameCount%10<6) rexImg = rexRunImg1;
        else rexImg = rexRunImg2;
      }
      else rexImg = rexJmpImg;
      image(rexImg, px, py, 60, 60);
    }
  }
  
  void jump(){ 
    if(isGrounded){ 
      isGrounded = false;
      vy = -14;
    }
  }
}

class Hurdle{
  float px, py; 
  float vx; 
  
  Hurdle(){
    px = width;
    py = 0.7*height;
    vx = -10;
  }
  
  void update(){
    if(!hit){
      px+= vx;
      if(px<0) px = width;
    }
    image(cactusImg, px, py, 40, 70);
  }
}

class Observer{
  Player rex;
  Hurdle cactus;
  
  Observer(Player _rex, Hurdle _cactus){
    rex = _rex;
    cactus = _cactus;
  }
  
  void update(){
   if(dist(rex.px, rex.py, cactus.px, cactus.py)<40) hit = true;
   else hit = false;
  }  
}


サボテンのpngファイルと,ゲームオーバー時のtrexの画像は以下のものを使用しています
これらのファイルについても,dataフォルダ下に保存します
画像ファイルの取り扱いについては,前回の記事を参考にしてください
f:id:filopodia:20201117164303p:plainf:id:filopodia:20201117164253p:plain


上のコードを詳しくみていきます

障害物はHurdleクラスとして定義しています
Hurdleクラスには,位置を指定する変数pxと, 速度を指定する変数vxがあり,pxの初期値はキャンバスの幅widthとなっています

PImage rexJmpImg, rexRunImg1, rexRunImg2, rexHitImg, cactusImg;
cactusImg = loadImage("cactus.png");

void setup(){
  cactusImg = loadImage("cactus.png");
  cactus = new Hurdle();
}

void draw(){
  cactus.update();
}

class Hurdle{
  float px, py; 
  float vx; 
  
  Hurdle(){
    px = width;
    py = 0.7*height;
    vx = -10;
  }
  
  void update(){
    if(!hit){
      px+= vx;
      if(px<0) px = width; // サボテンが画像から見切れた場合はpxを初期値にもどす
    }
    image(cactusImg, px, py, 40, 70);
  }
}

各フレームごとにpx+=vxを行うので,pxは時間経過とともに10ずつ小さい値をとることになります
そしてimage()関数を用いてサボテンの画像を描画します
pxはだんだん小さな値をとるので,サボテンの画像は右から左へと流れることがわかります


次に,サボテンとtrexが衝突したことを監視するクラスとしてObserverを用意します

boolean hit;

class Observer{
  Player rex;
  Hurdle cactus;
  
  Observer(Player _rex, Hurdle _cactus){
    rex = _rex;
    cactus = _cactus;
  }
  
  void update(){
   if(dist(rex.px, rex.py, cactus.px, cactus.py)<40) hit = true;
   else hit = false;
  }  
}

Observerクラスのコンストラクタ引数にはプレイヤーと障害物のオブジェクトを渡しています
プレイヤーと障害物の中心距離が40pxより近くなったとき,衝突したとみなしグローバル変数hitにtrueを格納します

Playerクラス内では,グローバル変数hitがtrueのときに衝突時の画像が表示されるように設定しています

Player(){
  
  void update(){
    if(hit){
      image(rexHitImg, px, py, 55, 55); // 衝突時に衝突の画像を表示する
    }
    else{ // 衝突するまでは以下の処理を行う
      vy += gravity;
      py += vy;
      if(py>=stdHeight){
        isGrounded = true; 
        py = stdHeight;
      }
   }
}