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!
* Creative Commons License
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"); }