for Proce55ing
EsCave - Source code
(C) Kenta Cho(ABA."Saba")
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);
}
}