import ddf.minim.*;
/**
* Game 19: "Shellbreak"
* Arrows to move, Z to zap and shoot
* P to pause, M to mute
*
Go play more games at NMcCoy.net!
* 
This work by Nathan McCoy is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License.
*/
boolean[] keys = new boolean[256];
boolean title;
boolean paused;
boolean muted;
PFont font;
int drawing_ms_last;
int draw_frame;
int physics_ms_last;
int CORE_FPS = 540;
int DRAWING_FPS = 60;
int PHYSICS_FPS = 180;
int DRAW_MS = 1000 / DRAWING_FPS;
int PHYS_MS = 1000 / PHYSICS_FPS;
int TYPES = 7;
Minim minim;
float REROLL_CHANCE = 0.85;
float PLAYER_MOVE_SPEED = 1.9;
float PLAYER_SHOOTING_MOVE_SPEED = 1.1;
float POWER_SHOT_SPREAD = 5.0;
float POWER_METER_DEPLETE_RATE = 0.0000003;
float PROGRESS_PER_ENEMY = 0.03;
color[] COLORS = {color(0), color(255, 0, 0), color(255, 255, 0), color(0, 255, 0), color(0, 255, 255), color(0, 0, 255), color(255, 0, 255), color(255, 255, 255)};
String[] COLOR_NAMES = {"BLACK", "RED", "YELLOW", "GREEN", "CYAN", "BLUE", "MAGENTA", "WHITE"};
float BULLET_RATE = 0.03;
char SHOOT_KEY = 'Z';
float PLAYER_MOVE_MARGIN = 16;
float PLAYER_RADIUS = 6;
float ENEMY_DRAG = 98.0/100;
float ENEMY_GRAVITY = 0.01;
int ENEMY_SPAWN_INTERVAL = 100;
float ENEMY_RADIUS = 10;
float SHELL_RADIUS = 18;
int ZAP_TIME = 90;
int GAME_TIME = PHYSICS_FPS * 120;
ArrayList playerShots;
ArrayList enemyShots;
ArrayList enemies;
ParticleList particles;
int chain_timer;
int power_depletion_timer;
int powerlevel_timer;
int powerlevel;
float power_progress_meter;
int score;
int top_score;
int last_score;
float bullet_shot_meter;
int enemy_spawn_timer;
int shot_button_counter;
int zap_timer;
int game_timer;
PVector player_pos;
void gameInit()
{
player_pos = new PVector(width/2, height/2);
playerShots = new ArrayList();
enemyShots = new ArrayList();
enemies = new ArrayList();
particles = new ParticleList();
score = 0;
chain_timer = 0;
power_depletion_timer = 0;
powerlevel = 1;
power_progress_meter = 0;
enemy_spawn_timer = ENEMY_SPAWN_INTERVAL;
game_timer = -PHYSICS_FPS*3;
}
AudioSample shotSound;
AudioSample enemySound;
AudioSample zapSound;
AudioSample shellSound;
AudioSample levelUpSound;
void audioInit()
{
minim = new Minim(this);
shotSound = minim.loadSample("shot.wav", 1024);
shotSound.setGain(-5);
enemySound = minim.loadSample("enemy.wav", 1024);
enemySound.setGain(-5);
shellSound = minim.loadSample("shell.wav", 1024);
shellSound.setGain(-5);
zapSound = minim.loadSample("zap.wav", 1024);
zapSound.setGain(-5);
levelUpSound = minim.loadSample("levelup.wav", 1024);
}
void audioClose()
{
shotSound.close();
enemySound.close();
shellSound.close();
zapSound.close();
levelUpSound.close();
minim.stop();
}
void setup()
{
size(720, 480);
frameRate(600);
audioInit();
gameInit();
font = loadFont("PressStartK-32.vlw");
}
void stop()
{
audioClose();
super.stop();
}
void physicsStep()
{
game_timer --;
if(game_timer == 0)
{
last_score = score;
enemies.clear();
}
if(game_timer <= 0)
{
powerlevel = 0;
score = 0;
}
top_score = max(score, top_score);
zap_timer --;
power_depletion_timer ++;
if(powerlevel > 0) power_progress_meter -= POWER_METER_DEPLETE_RATE * power_depletion_timer * powerlevel;
if(power_progress_meter <= 0)
{
power_progress_meter = 1;
powerlevel--;
if(powerlevel < 0 ) powerlevel = 0;
}
if(powerlevel == 0) power_progress_meter = 1.1;
playerControls();
playerBullets();
particles.tick();
enemyStep();
}
void doZap()
{
if(zap_timer <= 0)
{
if(!muted) zapSound.trigger();
int combo = 0;
ListIterator ei = enemies.listIterator();
while(ei.hasNext())
{
Enemy e = (Enemy) ei.next();
if(abs(e.pos.x - player_pos.x) < e.radius && e.pos.y < player_pos.y && e instanceof Shell)
{
if(e.type <= powerlevel || (power_progress_meter >= 1 && e.type == powerlevel + 1))
{
if(game_timer <= 0) game_timer = GAME_TIME;
combo++;
score += combo * 100;
particles.add(new PointMark(e.pos, combo*100));
ei.remove();
//TODO shell zap effects
particles.add(new ShockWave(e.pos));
int num;
if(random(5) < 1) num = (int)(random(15)+random(15));
else num = (int)(random(5)+random(5));
num += 3;
for(int i = 0; i < num; i++)
{
Enemy n = new Enemy(e.pos, e.type);
n.vel = randomVector(random(3));
ei.add(n);
}//while(random(1) < REROLL_CHANCE);
if(e.type > powerlevel)
{
if(!muted) levelUpSound.trigger();
powerlevel++;
power_progress_meter = 0.2;
power_depletion_timer = 0;
}
}
}
}
if(combo > 0) if(!muted) shellSound.trigger();
zap_timer = ZAP_TIME;
}
}
void enemyStep()
{
enemy_spawn_timer--;
if(enemy_spawn_timer <= 0)
{
enemy_spawn_timer = ENEMY_SPAWN_INTERVAL;
enemies.add(new Shell(new PVector(random(width*0.8)+width*0.1, -SHELL_RADIUS), (int)min(random(min(powerlevel+4.5, TYPES+1))+1, TYPES)));
}
Iterator ei = enemies.iterator();
while(ei.hasNext())
{
Enemy e = (Enemy) ei.next();
e.tick();
if(e.pos.y > height+e.radius) ei.remove();
else
{
Iterator bi = playerShots.iterator();
{
while(bi.hasNext())
{
PlayerBullet b = (PlayerBullet) bi.next();
if(inRadius(e.pos, b.pos, e.radius))
{
if(e instanceof Shell)
{
PVector away = PVector.sub(e.pos, b.pos);
away.normalize();
away.mult(0.7);
away.y -= 0.4;
e.vel.add(away);
bi.remove();
//TODO shell hit effects
}
else
{
ei.remove();
bi.remove();
//TODO enemy destruction effects
if(!muted) enemySound.trigger();
particles.add(new Ripple(e.pos));
score += e.type * 10;
particles.add(new PointMark(e.pos, e.type*10));
if(powerlevel != TYPES)power_depletion_timer = 0;
if(e.type == powerlevel)
{
power_progress_meter += PROGRESS_PER_ENEMY;
power_depletion_timer = 0;
}
}
}
}
}
}
}
}
void playerControls()
{
int dx = 0;
int dy = 0;
if(keys[LEFT]) dx--;
if(keys[RIGHT]) dx++;
if(keys[UP]) dy--;
if(keys[DOWN]) dy++;
if(keys[SHOOT_KEY])
{
player_pos.x += dx * PLAYER_SHOOTING_MOVE_SPEED;
player_pos.y += dy * PLAYER_SHOOTING_MOVE_SPEED;
}
else
{
player_pos.x += dx * PLAYER_MOVE_SPEED;
player_pos.y += dy * PLAYER_MOVE_SPEED;
}
if(player_pos.x < PLAYER_MOVE_MARGIN) player_pos.x = PLAYER_MOVE_MARGIN;
if(player_pos.x > width - PLAYER_MOVE_MARGIN) player_pos.x = width - PLAYER_MOVE_MARGIN;
if(player_pos.y < PLAYER_MOVE_MARGIN) player_pos.y = PLAYER_MOVE_MARGIN;
if(player_pos.y > height - PLAYER_MOVE_MARGIN) player_pos.y = height - PLAYER_MOVE_MARGIN;
}
void playerBullets()
{
if(keys[SHOOT_KEY])
{
bullet_shot_meter += BULLET_RATE * (powerlevel + 1);
while(bullet_shot_meter > 1)
{
playerShots.add(new PlayerBullet(new PVector(player_pos.x + random(5*powerlevel)-random(5 * powerlevel), player_pos.y)));
//if(!muted) shotSound.trigger();
bullet_shot_meter --;
}
shot_button_counter++;
}
else
{
bullet_shot_meter = 0;
shot_button_counter = 0;
}
if(shot_button_counter == 1) doZap();
Iterator bi = playerShots.iterator();
{
while(bi.hasNext())
{
PlayerBullet b = (PlayerBullet) bi.next();
b.pos.add(b.vel);
if(b.pos.x < 0 || b.pos.x > width || b.pos.y < 0 || b.pos.y > height)
{
bi.remove();
}
}
}
}
void drawingStep()
{
draw_frame++;
color bg = blendColors(color(0), COLORS[powerlevel], 0.2);
background(bg);
drawEnemies();
drawPlayer();
drawPBullets();
particles.draw();
drawHUD();
}
void drawEnemies()
{
Iterator ei = enemies.iterator();
while(ei.hasNext())
{
Enemy e = (Enemy) ei.next();
e.draw();
}
}
void drawPBullets()
{
Iterator bi = playerShots.iterator();
{
while(bi.hasNext())
{
PlayerBullet b = (PlayerBullet) bi.next();
b.draw();
}
}
}
void drawPlayer()
{
ellipseMode(RADIUS);
noStroke();
fill(160);
beginShape();
vertex(player_pos.x, player_pos.y - PLAYER_MOVE_MARGIN);
vertex(player_pos.x + PLAYER_MOVE_MARGIN, player_pos.y + PLAYER_MOVE_MARGIN);
vertex(player_pos.x, player_pos.y + PLAYER_MOVE_MARGIN/2);
vertex(player_pos.x - PLAYER_MOVE_MARGIN, player_pos.y + PLAYER_MOVE_MARGIN);
endShape(CLOSE);
fill(230);
ellipse(player_pos.x, player_pos.y, PLAYER_RADIUS, PLAYER_RADIUS);
if(zap_timer > 0)
{
stroke(255, 255.0 * zap_timer / ZAP_TIME);
strokeWeight(5);
line(player_pos.x, player_pos.y, player_pos.x, 0);
strokeWeight(1);
}
}
void drawHUD()
{
noStroke();
fill(0);
rect(width - 20, 20, 10, height-40);
fill(COLORS[powerlevel]);
if(power_progress_meter > 1 && draw_frame /2 % 3 == 0)
{
fill(blendColors(COLORS[powerlevel], color(255), 0.6));
}
rect(width - 20, 20, 10, (height-40)*min(power_progress_meter, 1));
textFont(font, 16);
textAlign(RIGHT, BOTTOM);
if(power_progress_meter > 1 && powerlevel < TYPES)
{
fill(COLORS[powerlevel+1]);
text("ZAP "+COLOR_NAMES[powerlevel+1], width-20, height-20);
}
textAlign(LEFT, TOP);
fill(230);
text("Score: "+score+"\nTime: "+(max(game_timer, 0) / PHYSICS_FPS), 20, 20);
textFont(font, 8);
text("\n\n\n\nTop Score: "+top_score, 20, 20);
if(game_timer > GAME_TIME - PHYSICS_FPS)
{
fill(255, 255* (game_timer - GAME_TIME + PHYSICS_FPS)/(float)PHYSICS_FPS);
textAlign(CENTER, CENTER);
textFont(font, 64);
text("BEGIN!", width/2, height/2);
}
if(game_timer < 0 && game_timer > -PHYSICS_FPS*3)
{
fill(255, 255* (game_timer + PHYSICS_FPS*3)/(float)(PHYSICS_FPS*3));
textAlign(CENTER, CENTER);
textFont(font, 32);
text("TIME UP!\nScore: "+last_score, width/2, height/2);
}
}
void draw()
{
while(physics_ms_last + PHYS_MS < millis())
{
if(!paused)
{
physicsStep();
}
physics_ms_last += PHYS_MS;
}
if(drawing_ms_last + DRAW_MS < millis())
{
drawingStep();
drawing_ms_last = millis();
}
}
void keyPressed()
{
if(!keys[keyCode])
{
keys[keyCode] = true;
down(keyCode);
}
}
void keyReleased()
{
if(keys[keyCode])
{
keys[keyCode] = false;
up(keyCode);
}
}
void down(int theKey)
{
println(theKey + " down");
if(theKey == 'M') muted = !muted;
if(theKey == 'P') paused = !paused;
}
void up(int theKey)
{
println(theKey + " up");
}