for Proce55ing
EsCave - Source code

EsCave.java

/**
 * EsCave.<br>
 *  洞窟抜けゲーム弾付き<br><br>
 *
 * Copyright 2004 Kenta Cho. All rights reserved.
 *
 * @p5 description
 * - マウスを使って弾と壁を避けてください。
 * - Control your mouse and avoid bullets and walls.
 * - 上に行くほどゲームが速くなり、多くの得点が入ります。
 * - The more you go up, the faster the game becomes and you can earn more points.
 */
public class EsCave extends BApplet {
  /**
   * 洞窟の底のZ座標値
   */
  int DEPTH = -100;
  /**
   * フォントデータ
   */
  BFont font;
  /**
   * BGM
   */
  BSound bgm;
  /**
   * SE
   */  
  BSound crashSe;

  /**
   * 壁を縦にいくつ並べるか
   */
  int WALL_NUM = 34;
  /**
   * 画面端の壁インスタンスプール
   */
  Wall[] wall;
  /**
   * 壁の(Y方向の)高さ
   */
  int wallHeight;
  /**
   * 壁のY座標の範囲(出現位置および画面から消える位置)
   */
  int wallMinY, wallMaxY;
  /**
   * 壁出現設定インスタンスの数
   */
  int WALL_MOVE_NUM = 2;
  /**
   * 次の壁の出現位置を設定するためのインスタンスプール
   */
  WallMove[] wallMove;
  /**
   * 次に設定する壁のインスタンスプール内のインデックス
   */
  int wallIdx;
  /**
   * 壁の横揺れ度合い調整定数
   */
  float WALL_SWING_MOVE_RATIO = 0.25f;
  /**
   * 壁と壁の間の距離
   */
  float wallWidth;
  /**
   * 壁の横揺れ範囲
   */
  float wallSwingRange;
  /**
   * 壁と壁の間の距離を最低これだけ取る
   */
  float WALL_MIN_SPACE = 50;

  /**
   * マウスが画面上にいったときの加速度合
   */
  float SPEED_RATIO = 0.07f;
  /**
   * スクロールスピード
   */
  float shipSpeed;
  /**
   * 自機の表示上の大きさ
   */
  int SHIP_RAD = 6;

  /**
   * 弾最大数
   */
  int BULLET_NUM = 64;
  /**
   * 弾インスタンスプール
   */
  Bullet[] bullet;
  /**
   * 次に弾を撃つまでのフレーム数を管理するカウンタ
   */
  int bulletFireCnt;
  /**
   * 弾を撃つ間隔
   */
  float bulletFireInterval;
  /**
   * 次に撃つ弾は広角かどうか
   */
  boolean bulletFireWide;

  /**
   * パーティクルの最大数
   */
  int PARTICLE_NUM = 32;
  /**
   * パーティクルインスタンスプール
   */
  Particle[] particle;

  /**
   * 死んだ後の無敵時間
   */
  int INVINSIBLE_CNT = 120;
  /**
   * 無敵時間カウンタ
   */
  int invisibleCnt;
  /**
   * ランク上昇率調整用(時間で減少)
   */
  float rankUpRatio;
  /**
   * スコア
   */
  float score;
  /**
   * 残機数
   */
  int left;
  /**
   * ゲーム状態用定数
   */
  int TITLE = 0, IN_GAME = 1;
  /**
   * ゲーム状態(タイトル/ゲーム中)
   */
  int state;

  /**
   * 初期化(フォント、サウンド読み込み、インスタンス生成)
   */
  void setup() {
    size(200, 600);
    width = 200;
    height = 600;
    framerate(30);
    smooth();
    ellipseMode(CENTER_DIAMETER);
    font = loadFont("OCR-A.vlw.gz");
    textFont(font);
    bgm = loadSound("ezc.wav");
    crashSe = loadSound("crash.wav");

    wall = new Wall[WALL_NUM];
    wallHeight = height / (WALL_NUM - 4);
    wallMinY = -2 * wallHeight;
    wallMaxY = height + 2 * wallHeight;
    for (int i = 0; i < wall.length; i++)
      wall[i] = new Wall((i - 2) * wallHeight);
    wallIdx = wall.length - 1;

    wallMove = new WallMove[WALL_MOVE_NUM];
    for (int i = 0; i < wallMove.length; i++)
      wallMove[i] = new WallMove();

    bullet = new Bullet[BULLET_NUM];
    for (int i = 0; i < bullet.length; i++)
      bullet[i] = new Bullet();

    particle = new Particle[PARTICLE_NUM];
    for (int i = 0; i < particle.length; i++)
      particle[i] = new Particle();

    score = 0;
    left = 0;
    startTitle();
  }

  /**
   * タイトル初期化
   */
  void startTitle() {
    state = TITLE;
    cursor();
    stop(bgm);
  }

  /**
   * ゲーム開始(各種インスタンスプール初期化、BGM再生開始)
   */
  void startGame() {
    state = IN_GAME;
    noCursor();
    for (int i = 0; i < wall.length; i++)
      wall[i].reset();
    for (int i = 0; i < wallMove.length; i++)
      wallMove[i].reset();
    for (int i = 0; i < bullet.length; i++)
      bullet[i].reset();
    invisibleCnt = INVINSIBLE_CNT;
    rankUpRatio = 1;
    bulletFireCnt = 200;
    bulletFireInterval = bulletFireCnt;
    bulletFireWide = true;
    wallSwingRange = 16;
    wallWidth = 5;
    setWallMoveParams(wallSwingRange, wallWidth);
    score = 0;
    left = 2;
    shipSpeed = 0;
    volume(bgm, 1.0f);
    jump(bgm, 0);
    repeat(bgm);
  }

  
  /**
   * 1フレームごとの処理
   */
  void loop() {
    if (state == TITLE)
      loopTitle();
    else
      loopGame();
  }

  /**
   * タイトル時の1フレーム処理
   */
  void loopTitle() {
    background(50, 100, 150);

    beginShape(QUADS);
    for (int i = 0; i < wall.length; i++)
      wall[i].draw();
    endShape();
    boolean onStart;
    if (mouseX > width / 2 - 50
      && mouseX < width / 2 + 50
      && mouseY > height - 150
      && mouseY < height - 125)
      onStart = true;
    else
      onStart = false;

    push();
    translate(0, 0, 15);
    textSize(42);
    textMode(ALIGN_CENTER);
    fill(255, 255, 255);
    text("EsCave", width / 2, height / 3);
    stroke(250, 150, 100);
    if (onStart)
      fill(250, 180, 180);
    else
      fill(200, 150, 150);
    strokeWeight(2);
    rect(width / 2 - 50, height - 150, 100, 25);
    strokeWeight(1);
    fill(255, 255, 255);
    textSize(25);
    text("START", width / 2, height - 130);
    pop();
    drawStatus();

    if (onStart && mousePressed)
      startGame();
  }

  /**
   * ゲーム中の1フレーム処理
   */
  void loopGame() {
    background(50, 100, 150);
    if (left >= 0)
      shipSpeed += ((height - mouseY) * SPEED_RATIO - shipSpeed) * 0.1f;
    else {
      shipSpeed *= 0.95f;
      float vm =
        1.0f - (float) (INVINSIBLE_CNT - invisibleCnt) / INVINSIBLE_CNT * 2;
      if (vm < 0)
        vm = 0;
      volume(bgm, vm);
      if (invisibleCnt <= INVINSIBLE_CNT - 30) {
        push();
        translate(0, 0, 15);
        textSize(32);
        textMode(ALIGN_CENTER);
        fill(255, 255, 255);
        text("Game Over", width / 2, height / 3);
        pop();
        if (invisibleCnt < 5 || mousePressed) {
          startTitle();
          return;
        }
      }
    }
    // Change the bgm speed.
    float ps = (shipSpeed - 10) / 100.0f + 1;
    speed(bgm, ps);

    beginShape(QUADS);
    int wi = wallIdx;
    for (int i = 0; i < wall.length; i++) {
      wall[wi].move(shipSpeed);
      wall[wi].draw();
      wi--;
      if (wi < 0)
        wi += wall.length;
    }
    endShape();

    noStroke();
    for (int i = 0; i < particle.length; i++) {
      if (particle[i].cnt >= 0)
        particle[i].loop();
    }

    // Handle a mouse cursor.
    if (invisibleCnt > 0) {
      invisibleCnt--;
      shipSpeed *= 1 - (float) invisibleCnt / INVINSIBLE_CNT * 0.1f;
    } else {
      score += shipSpeed * shipSpeed * 0.01f;
      if (checkWallHit(mouseX, mouseY)
        || checkBulletHit(mouseX, mouseY)
        || checkBulletHit(pmouseX, pmouseY)) {
        for (int i = 0; i < 24; i++)
          setParticle(mouseX, mouseY, random(PI * 2), random(32));
        invisibleCnt = INVINSIBLE_CNT;
        stop(crashSe);
        play(crashSe);
        left--;
      }
    }
    if (invisibleCnt % 20 < 10 && left >= 0) {
      stroke(250, 150, 100);
      fill(200, 150, 150);
      ellipse(mouseX, mouseY, SHIP_RAD * 2, SHIP_RAD * 2);
      push();
      translate(0, 0, DEPTH);
      noStroke();
      fill(120, 100, 120);
      ellipse(mouseX, mouseY, SHIP_RAD * 2, SHIP_RAD * 2);
      pop();
    }

    for (int i = 0; i < bullet.length; i++) {
      if (bullet[i].isExist)
        bullet[i].loop();
    }
    bulletFireCnt--;
    if (bulletFireCnt < 0) {
      bulletFireCnt = (int) bulletFireInterval;
      float d = atan2(mouseX - width / 2, mouseY);
      float md;
      if (bulletFireWide) {
        md = 0.2f;
        bulletFireWide = false;
      } else {
        md = 0.01f + random(0.05f);
        bulletFireWide = true;
      }
      d -= md * 2;
      for (int i = 0; i < 5; i++) {
        setBullet(d, shipSpeed * 0.5f + 3);
        d += md;
      }
    }

    // Control the game difficulty.
    bulletFireInterval *= (1 - 0.0002f * rankUpRatio);
    wallSwingRange *= (1 + 0.0005f * rankUpRatio);
    wallWidth *= (1 + 0.0004f * rankUpRatio);
    setWallMoveParams(wallSwingRange, wallWidth);
    rankUpRatio *= 0.99995f;

    drawStatus();
  }

  /**
   * スコア、残機表示
   */
  void drawStatus() {
    push();
    translate(0, 0, 15);
    textSize(30);
    textMode(ALIGN_RIGHT);
    fill(255, 255, 255);
    text((int) score, width - 5, height - 10);
    for (int i = 0; i < left; i++) {
      stroke(250, 150, 100);
      fill(200, 150, 150);
      ellipse(
        SHIP_RAD * 2 + i * SHIP_RAD * 3,
        height - SHIP_RAD * 3,
        SHIP_RAD * 2,
        SHIP_RAD * 2);
    }
    pop();
  }

  /**
   * 画面端の壁
   */
  class Wall {
    /**
     * Y座標値
     */
    float y;
    /**
     * 左右の壁のX方向の厚さ
     */
    int w1, w2;
    /**
     * 左右の壁の中央方向X座標位置
     */
    int x1, x2;

    /**
     * 初期Y座標を設定してリセット
     * @param y 初期Y座標
     */
    Wall(int y) {
      this.y = y;
      reset();
    }

    /**
     * 厚さを0に
     */
    void reset() {
      w1 = w2 = 0;
    }

    /**
     * スクロールにあわせて移動、下端に消えたら上端に戻り新たな壁として設定
     * @param my スクロール量
     */
    void move(float my) {
      y += my;
      if (y >= wallMaxY) {
        y -= wallMaxY - wallMinY;
        w1 = w2 = -999;
        for (int i = 0; i < wallMove.length; i++) {
          wallMove[i].move();
          int tw1 = (int) (wallMove[i].x + wallMove[i].width);
          if (tw1 > w1)
            w1 = tw1;
          int tw2 = (int) (-wallMove[i].x + wallMove[i].width);
          if (tw2 > w2)
            w2 = tw2;
        }
        if (w1 + w2 > width - WALL_MIN_SPACE) {
          float ov = w1 + w2 - (width - WALL_MIN_SPACE);
          w1 -= ov / 2;
          w2 -= ov / 2;
        }
        wallIdx--;
        if (wallIdx < 0)
          wallIdx += wall.length;
      }
    }

    /**
     * 壁の描画
     */
    void draw() {
      x1 = w1;
      x2 = width - w2;
      stroke(200, 220, 250);
      fill(100, 150, 200);
      vertex(0, y);
      vertex(x1, y);
      vertex(x1, y + wallHeight);
      vertex(0, y + wallHeight);
      vertex(width, y);
      vertex(x2, y);
      vertex(x2, y + wallHeight);
      vertex(width, y + wallHeight);
      stroke(100, 120, 150);
      vertex(x1, y);
      vertex(x1, y + wallHeight);
      fill(50, 100, 200);
      vertex(x1, y + wallHeight, DEPTH);
      vertex(x1, y, DEPTH);
      fill(100, 150, 200);
      vertex(x2, y);
      vertex(x2, y + wallHeight);
      fill(50, 100, 200);
      vertex(x2, y + wallHeight, DEPTH);
      vertex(x2, y, DEPTH);
    }
  }

  /**
   * 壁との当たり判定
   * @param x チェックするX座標
   * @param y チェックするY座標
   * @return 当たったか否か
   */
  boolean checkWallHit(float x, float y) {
    for (int i = 0; i < wall.length; i++) {
      Wall w = wall[i];
      if (w.y <= y && w.y + wallHeight >= y) {
        if (x <= w.x1 || x >= w.x2) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * 次の壁の出現位置設定用
   */
  class WallMove {
    /**
     * 壁の間の空洞の中心点
     */
    float x;
    /**
     * 左右へのゆれのゆれ速度
     */
    float swingMv;
    /**
     * 左右へのゆれのふれ幅
     */
    float swingRange;
    /**
     * 左右へのゆれの方向
     */
    float swingDir;
    /**
     * 左右の壁の幅
     */
    float width, widthAim;

    /**
     * 生成してリセット
     */
    public WallMove() {
      reset();
    }

    /**
     * 中心点、ゆれ方向、幅を初期化
     */
    void reset() {
      x = 0;
      swingDir = 1;
      width = 0;
    }

    /**
     * 次の壁の位置を設定
     */
    void move() {
      x += swingMv * (1 + random(-0.5f, 0.5f)) * swingDir;
      if (swingDir > 0) {
        if (x > swingRange)
          swingDir = -1;
      } else {
        if (x < -swingRange)
          swingDir = 1;
      }
      width += (widthAim - width) * 0.1f;
      bulletFireCnt -= 4;
    }
  }

  /**
   * 壁の位置設定のためのパラメタ設定
   * @param r ふれ幅
   * @param w 壁の厚さ
   */
  void setWallMoveParams(float r, float w) {
    for (int i = 0; i < wallMove.length; i++) {
      wallMove[i].swingMv = r * WALL_SWING_MOVE_RATIO;
      wallMove[i].swingRange = r;
      wallMove[i].widthAim = w;
    }
  }

  /**
   * 弾
   */
  class Bullet {
    /**
     * 表示上の大きさ
     */
    float RAD = 7;
    /**
     * 位置
     */
    float x, y;
    /**
     * 直前フレームでの位置
     */
    float px, py;
    /**
     * 飛ぶ方向
     */
    float deg;
    /**
     * スピード
     */
    float speed;
    /**
     * 表示回転用カウンタ
     */
    int cnt;
    /**
     * 画面上に存在しているか否か
     */
    boolean isExist;

    /**
     * 生成してリセット
     */
    Bullet() {
      reset();
    }

    /**
     * 画面上に存在していない状態に
     */
    void reset() {
      isExist = false;
    }

    /**
     * 画面上に出現させる(出現位置は画面上端中心に固定)
     * @param d 射出方向
     * @param s スピード
     */
    void set(float d, float s) {
      deg = d;
      speed = s;
      px = x = width / 2;
      py = y = 0;
      cnt = 0;
      isExist = true;
    }

    /**
     * 1フレーム毎の処理
     */
    void loop() {
      px = x;
      py = y;
      x += sin(deg) * speed;
      y += cos(deg) * speed;
      y += shipSpeed * 0.25;
      cnt++;
      if (x < 0 || x > width || y < 0 || y > height) {
        isExist = false;
        return;
      }
      if (checkWallHit(x, y)) {
        setParticle(x, y, deg, speed);
        isExist = false;
        return;
      }
      fill(180, 200, 250);
      ellipse(x, y, RAD * 2, RAD * 2);
      fill(240, 240, 250);
      float cd = cnt * 0.5f;
      ellipse(
        x + sin(cd) * RAD * 0.5f,
        y + cos(cd) * RAD * 0.5f,
        RAD * 1.6f,
        RAD * 1.6f);
      push();
      translate(0, 0, DEPTH);
      fill(80, 100, 120);
      ellipse(x, y, RAD * 2, RAD * 2);
      pop();
    }
  }

  /**
   * 弾インスタンスプール内のインスタンス設定用インデックス
   */
  int bulletIdx = 0;

  /**
   * 弾をインスタンスプール内に設定
   * @param d 射出方向
   * @param s スピード
   */
  void setBullet(float d, float s) {
    for (int i = 0; i < bullet.length; i++) {
      bulletIdx--;
      if (bulletIdx < 0)
        bulletIdx += bullet.length;
      if (!bullet[bulletIdx].isExist) {
        bullet[bulletIdx].set(d, s);
        return;
      }
    }
  }

  /**
   * 弾の当たり判定の大きさ
   */
  float BULLET_HIT_DIST = 6;

  /**
   * 弾の当たり判定
   * @param x 自機のX
   * @param y 自機のY座標
   * @return 当たったか
   */
  boolean checkBulletHit(float x, float y) {
    for (int i = 0; i < bullet.length; i++) {
      Bullet b = bullet[i];
      if (b.isExist
        && (dist(x, y, b.x, b.y) < BULLET_HIT_DIST
          || dist(
            x,
            y,
            b.x * 0.7f + b.px * 0.3f,
            b.y * 0.7f + b.py * 0.3f)
            < BULLET_HIT_DIST
          || dist(
            x,
            y,
            b.x * 0.4f + b.px * 0.6f,
            b.y * 0.4f + b.py * 0.6f)
            < BULLET_HIT_DIST))
        return true;
    }
    return false;
  }

  /**
   * スモーク状のパーティクル
   */
  class Particle {
    /**
     * 表示上の大きさ
     */
    float RAD = 7;
    /**
     * 位置
     */
    float x, y;
    /**
     * 飛ぶ方向
     */
    float deg;
    /**
     * スピード
     */
    float speed;
    /**
     * アルファ値
     */
    int alpha;
    /**
     * 消えるまでのカウンタ
     */
    int cnt;

    /**
     * カウンタを-1に設定して非表示に
     */
    Particle() {
      cnt = -1;
    }

    /**
     * 画面上に出現させる
     * @param x 初期x座標
     * @param y 初期y座標
     * @param d 方向
     * @param s スピード
     */
    void set(float x, float y, float d, float s) {
      this.x = x;
      this.y = y;
      deg = d;
      speed = s;
      alpha = 200;
      cnt = 15 + (int) random(15);
    }

    /**
     * 1フレーム毎の処理
     */
    void loop() {
      x += sin(deg) * speed;
      y += cos(deg) * speed;
      y += shipSpeed * 0.25;
      alpha *= 0.9;
      cnt--;
      fill(250, 250, 250, alpha);
      ellipse(x, y, RAD * 2, RAD * 2);
    }
  }

  /**
   * パーティクルプールから次のインスタンスを指し示すためのインデックス
   */
  int particleIdx = 0;

  /**
   * パーティクルをインスタンスプール内に設定
   * @param x 初期x座標
   * @param y 初期y座標
   * @param d 方向
   * @param s スピード
   */
  void setParticle(float x, float y, float d, float s) {
    particleIdx--;
    if (particleIdx < 0)
      particleIdx += particle.length;
    particle[particleIdx].set(x, y, d, s);
  }

}