/** Game 02: "Stargrazing"
Use arrow keys to move
Hold Z to extend capture field, release to fire stored bullets
Press P to pause
Press Q to return to title screen
Creative Commons License
This work by Nathan McCoy is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License. */ // import ddf.minim.*; int debug_bulletcount; //bullet constants int IDLE = -1; int SIMPLE = 0; int FIRE = 1; int ELEC = 2; float SIMPLE_SHOT_SPEED = 10; float FIRE_SHOT_SPEED = 3; float ELEC_SHOT_SPEED = 7; float FIRE_SHOT_ANGLE = PI/4; float PLAYER_SPEED = 5; float PLAYER_FOCUS_SPEED = 3; float PLAYER_FIELD_RADIUS = 50; float SIMPLE_SHOT_SPREAD = 20; float ELEC_SHOT_TURNING = PI/64; float PLAYER_RADIUS = 7; float PLAYER_MARGIN = PLAYER_RADIUS; float MARGIN = 50; float THREAT_RATIO = 0.3; int SPAWN_TIME_LIMIT = 120; int spawntimer; //foe types int ROCK = 0; int TURRET = 2; int MOOK = 1; int HUNTER = 3; int TOUGHGUY = 4; int BOSS = 5; int ROCK_STRENGTH_LIMIT = 4; int TURRET_STRENGTH_LIMIT = 5; int MOOK_STRENGTH_LIMIT = 3; int HUNTER_STRENGTH_LIMIT = 10; int TOUGHGUY_STRENGTH_LIMIT = 25; int ROCK_LIFE = 5; int EXPLOSION_LIFE = 30; float WARN_SIZE = 4; int THREAT_RESET = 6; int EXTRA_LIFE_SCORE = 10000; color[] COLORS = {#FFFFAA, #FF3300, #3300FF}; //game vars ArrayList stars; Ship playerShip; Gun playerGun; boolean focusing; ArrayList downKeys; ArrayList playerShots; ArrayList enemyShots; ArrayList explosions; int snagCount; int snagType; ArrayList foes; ArrayList foesToAdd; int rank; //general difficulty of foes int score; int time; int threat; //limit on how dangerous foes can be int lives; int gameMode; //what state the game is in int TITLE_SCREEN = 0; int FREE_PLAY = 1; int ARCADE = 2; int BOSS_RUSH = 3; boolean gameover = false; boolean paused = false; // sound vars Minim minim; AudioSample shotSound; AudioSample fieldSound; AudioSample fireSound; AudioSample elecSound; AudioSample exploSound; AudioSample bigExploSound; AudioSample hitSound; AudioSample snagSound; AudioSample lifeSound; PFont font; void setup() { //sound setup minim = new Minim(this); shotSound = minim.loadSample("simpleshot.wav", 512); fieldSound = minim.loadSample("shieldout.wav", 512); fireSound = minim.loadSample("fireshot.wav", 512); hitSound = minim.loadSample("hit.wav", 512); exploSound = minim.loadSample("smallexplo.wav", 512); bigExploSound = minim.loadSample("largeexplo.wav", 512); elecSound = minim.loadSample("electricshot.wav", 512); snagSound= minim.loadSample("absorb.wav", 512); lifeSound = minim.loadSample("1up.wav", 512); //screen setup background(0); size(480, 640); frameRate(60); noStroke(); font = loadFont("EnigmaticUnicode-26.vlw"); textFont(font, 20); //stars setup stars = new ArrayList(); for(int i = 0; i < 1000; i++) stars.add(new PVector(random(width), random(height), random(100))); //game setup reset(); //smooth(); } void reset() { downKeys = new ArrayList(); playerShip = new Ship(); playerShip.pos = new PVector(width/2, height/4*3); playerShip.hullsize = PLAYER_RADIUS; playerShip.guns.add(new Gun(playerShip, new PVector(0, -PLAYER_RADIUS), new PVector(0, -1))); playerGun = (Gun) playerShip.guns.get(0); playerGun.friendly = true; playerShots = new ArrayList(); enemyShots = new ArrayList(); foes = new ArrayList(); foesToAdd = new ArrayList(); explosions = new ArrayList(); time = 0; score = 0; rank = 2; snagCount = 0; snagType = 0; threat = THREAT_RESET; lives = 5; gameover = false; focusing = false; } class Explosion { PVector pos; int life; float maxlife; float magnitude; void draw() { noStroke(); fill(color(255, 255 * life/maxlife * 2, 255 * life / maxlife, 255 * life / maxlife *2)); ellipse(pos.x, pos.y, (1-life/maxlife)*magnitude + magnitude/4, (1-life/maxlife)*magnitude+magnitude/4); } Explosion(PVector position, float magn) { magnitude = magn; pos = new PVector(position.x, position.y); maxlife = life = EXPLOSION_LIFE; } } class Ship { PVector pos; PVector vel; ArrayList guns; float hullsize; float life; float lifemax = 1; int strength; int behavior; void draw() { noStroke(); if(behavior == ROCK) fill(color(192, 160*life/lifemax, 100*life/lifemax)); else fill(color(192, 192*life/lifemax, 192*life/lifemax)); ellipse(pos.x, pos.y, hullsize*2, hullsize*2); for(int i=0; i height/4) { vel.y = 0; } } if(behavior == HUNTER) { Gun g = (Gun) guns.get(0); if(g.queue.isEmpty()) { g.queue.addAll(g.routine); } g.tick(); PVector targetPos = new PVector(playerShip.pos.x, playerShip.pos.y); targetPos.y /= 2; PVector seekVel = PVector.sub(targetPos, pos); seekVel.normalize(); int SPEED = 2; seekVel.mult(SPEED); if(PVector.dist(targetPos, pos) < SPEED) { vel = new PVector(0,0); pos = targetPos; } else vel = seekVel; } if(behavior == TOUGHGUY) { Gun g = (Gun) guns.get(0); if(g.queue.isEmpty()) { g.queue.addAll(g.routine); } g.tick(); PVector targetPos = new PVector(playerShip.pos.x, playerShip.pos.y); targetPos.y /= 2; PVector seekVel = PVector.sub(targetPos, pos); seekVel.normalize(); int SPEED = 1; seekVel.mult(SPEED); if(PVector.dist(targetPos, pos) < SPEED) { vel = new PVector(0,0); pos = targetPos; } else vel = seekVel; } if(behavior == BOSS) { for(int i = 0; i< guns.size(); i++) { Gun g = (Gun) guns.get(i); if(g.queue.isEmpty()) { g.queue.addAll(g.routine); } g.tick(); } PVector targetPos = new PVector(playerShip.pos.x, playerShip.pos.y); targetPos.y /= 4; PVector seekVel = PVector.sub(targetPos, pos); seekVel.normalize(); int SPEED = 1; seekVel.mult(SPEED); if(PVector.dist(targetPos, pos) < SPEED) { vel = new PVector(0,0); pos = targetPos; } else vel = seekVel; } } void die() { explosions.add(new Explosion(pos, strength*50)); if(strength < 20) exploSound.trigger(); else bigExploSound.trigger(); addScore(strength * 100); if(behavior == ROCK && strength > 1) { foesToAdd.add(new Ship(ROCK, strength/2, pos, new PVector(random(4)-2, random(4)-2))); foesToAdd.add(new Ship(ROCK, strength/2, pos, new PVector(random(4)-2, random(4)-2))); } if(behavior == BOSS) { rank++; threat = THREAT_RESET; enemyShots.clear(); spawntimer = 90; } threat++; } } class Gun { PVector facing; //vector to shoot bullets along; should be normalized PVector offset; //displacement from parent's origin Ship parent; //ship that this gun is attached to boolean friendly; //if true, add to playerBullets instead of enemyBullets ArrayList queue; //a queue of BulletPacks ArrayList routine; //routine of bullets to shoot from this gun int warnTimer; int warnType; //SIMPLE, FIRE, ELEC BulletPack active; Gun(Ship mount, PVector off, PVector face) { parent = mount; offset = off; facing = face; queue = new ArrayList(); } private void loadBullets() //try to arm a bulletpack from the queue, putting it in the active slot { if(!queue.isEmpty()) { active = (BulletPack) queue.remove(0); warnTimer = active.warning; warnType = active.type; } else active = null; //out of bulletpacks } void tick() { if(active != null) { if(warnTimer <= 0) //you've been sufficiently warned, fire { active.fire(PVector.add(offset, parent.pos), facing, friendly); //bullets fired, load next pack loadBullets(); } else warnTimer--; //keep counting dowm } else loadBullets(); } void draw() { PVector pos = PVector.add(offset, parent.pos); stroke(#FFFFFF); PVector nosePos = PVector.add(pos, PVector.mult(facing, 5)); line(pos.x, pos.y, nosePos.x, nosePos.y); if(warnTimer > 0 && warnType != IDLE) { stroke(COLORS[warnType]); noFill(); ellipse(pos.x, pos.y, warnTimer*WARN_SIZE, warnTimer*WARN_SIZE); } } } class BulletPack { int type; //SIMPLE, FIRE, ELEC int amount; //how many bullets to fire in this pack int power; //how powerful the bullets are int warning; //number of frames to warn before firing BulletPack(int t, int a, int p, int w) { type = t; amount = a; power = p; warning = w; } void fire(PVector position, PVector direction, boolean friendly) { if(type == SIMPLE) { PVector offset = new PVector(direction.y, -direction.x); /*for(int i = 0; i < amount; i++) { PVector offStep = PVector.mult(offset, (-SIMPLE_SHOT_SPREAD * (amount - 1) / 2) + SIMPLE_SHOT_SPREAD * i); PVector pos = PVector.add(position, offStep); Bullet b = new Bullet(SIMPLE, power, pos, PVector.mult(direction, SIMPLE_SHOT_SPEED)); // print("created bullet "+b.pos.toString()+"\n"); if(friendly) playerShots.add(b); else enemyShots.add(b); }*/ float stepAmount = 0.5 / amount; float angle = -(amount-1) * stepAmount/2; for(int i = 0; i < amount; i++) { PVector newdir = pivot(direction, angle); Bullet b = new Bullet(SIMPLE, power, position, PVector.mult(newdir, ELEC_SHOT_SPEED)); if(friendly) playerShots.add(b); else enemyShots.add(b); angle += stepAmount; } shotSound.trigger(); } if(type == FIRE) { for(int i = 0; i < amount; i++) { float angle = random(FIRE_SHOT_ANGLE) - FIRE_SHOT_ANGLE/2; PVector newdir = new PVector(direction.x*cos(angle) - direction.y*sin(angle), direction.x*sin(angle) + direction.y*cos(angle)); Bullet b = new Bullet(FIRE, power, position, PVector.mult(newdir, FIRE_SHOT_SPEED)); //print("created bullet "+b+"\n"); if(friendly) playerShots.add(b); else enemyShots.add(b); } fireSound.trigger(); } if(type == ELEC) { float stepAmount = PI / amount; float angle = -(amount-1) * stepAmount/2; for(int i = 0; i < amount; i++) { PVector newdir = pivot(direction, angle); Bullet b = new Bullet(ELEC, power, position, PVector.mult(newdir, ELEC_SHOT_SPEED)); if(friendly) playerShots.add(b); else enemyShots.add(b); angle += stepAmount; } elecSound.trigger(); } //TODO } } class Bullet { int debug_id; int type; int power; PVector pos; PVector vel; boolean auraFlag; int lifetime; Bullet(int t, int p, PVector position, PVector velocity) { debug_id = debug_bulletcount++; type = t; power = p; pos = new PVector(position.x, position.y); vel = new PVector(velocity.x, velocity.y); lifetime = 0; auraFlag = false; } void draw() { if(type == SIMPLE) { float radius = power * 2 + 1; stroke(color(255, 255, 255-(time%10)*25)); fill(#FFFFFF); float angle = random(PI); line(pos.x + cos(angle) * 2*radius, pos.y + sin(angle) * 2*radius, pos.x - cos(angle) * 2*radius, pos.y - sin(angle) * 2*radius); angle = random(PI); line(pos.x + cos(angle) * 1.5 *radius, pos.y + sin(angle) * 1.5 *radius, pos.x - cos(angle) * 1.5 *radius, pos.y - sin(angle) * 1.5 *radius); ellipse(pos.x, pos.y, 2*radius, 2*radius); } if(type == FIRE) { float radius = 6 * sqrt(power); noStroke(); fill(color(255, 0, 0)); ellipse(pos.x, pos.y, 2*radius, 2*radius); float angle = time/1.5; fill(color(255, 128, 0)); ellipse(pos.x + cos(angle) * 0.2*radius, pos.y + sin(angle) * 0.2*radius, 1.6*radius, 1.6*radius); angle = time/1.5 + PI/2; fill(color(255, 200, 0)); ellipse(pos.x + cos(angle) * 0.4*radius, pos.y + sin(angle) * 0.4*radius, 1.2*radius, 1.2*radius); } if(type == ELEC) { noStroke(); float radius = power * 2 + 1; fill(color(0, 0, 255)); ellipse(pos.x - vel.x, pos.y - vel.y, 1.5*radius, 1.5*radius); fill(color(192, 0, 255)); ellipse(pos.x, pos.y, 2*radius, 2*radius); stroke(color(0, 255, 255)); noFill(); ellipse(pos.x, pos.y, random(3*radius), random(3*radius)); ellipse(pos.x, pos.y, random(3*radius), random(3*radius)); ellipse(pos.x, pos.y, random(3*radius), random(3*radius)); } } String toString() { return "id "+debug_id+" Type "+type+", pos "+pos+", vel "+vel; } } PVector pivot(PVector direction, float angle) { return new PVector(direction.x*cos(angle) - direction.y*sin(angle), direction.x*sin(angle) + direction.y*cos(angle)); } void addScore(int amount) { if ((score+amount)/EXTRA_LIFE_SCORE > score / EXTRA_LIFE_SCORE) { lifeSound.trigger(); lives+=(score+amount)/EXTRA_LIFE_SCORE - score / EXTRA_LIFE_SCORE; } score += amount; } ArrayList newRoutine(int level) { ArrayList myPattern = new ArrayList(); int basePower = level / 10 + 2; int bulletCount = level / 5 + 1; int bulletType = (int) random(3); int bulletPattern = (int) random(3)+1; myPattern.add(new BulletPack(IDLE, 0, 0, max(60-level, 10))); if(bulletPattern == 0) //Big Single Shot myPattern.add(new BulletPack(bulletType, 1, bulletCount + 1, 20+level/2)); if(bulletPattern == 1) //rapid stream { myPattern.add(new BulletPack(bulletType, 1, basePower, 20+level/2)); for(int i=1; i (a.x - b.x)*(a.x - b.x) + (a.y - b.y) * (a.y - b.y); } void drawStars() { PVector star; for(int i = 0; i < stars.size(); i++) { star = (PVector)stars.get(i); //fill(i*255.0/stars.size()); //ellipse(star.x, star.y, 3.0, 3.0); set((int)star.x, (int)star.y, color(i*255.0/stars.size())); star.y += (float)i/stars.size()*3; if(star.y > height) { star.y -= height; star.x = random(width); } } } void doPlayerBullets() { Iterator i = playerShots.iterator(); while(i.hasNext()) { Bullet b = (Bullet) i.next(); b.pos.add(b.vel); b.lifetime++; if(b.pos.x < 0 - MARGIN || b.pos.x > width + MARGIN || b.pos.y < 0 - MARGIN || b.pos.y > height + MARGIN) { i.remove(); } else { if(b.lifetime < 40 && !foes.isEmpty() && (b.type == ELEC)) { Ship nearestFoe = (Ship) foes.get(0); float bestMeasure = 100000; for(int f=0; f width + MARGIN || b.pos.y < 0 - MARGIN || b.pos.y > height + MARGIN) { i.remove(); } else { if(b.type == SIMPLE) if(inRadius(b.pos, playerShip.pos, playerShip.hullsize)) { killPlayer(); break; } if(b.type == FIRE) if(inRadius(b.pos, playerShip.pos, playerShip.hullsize*0 + 6 * sqrt(b.power))) { killPlayer(); break; } if(b.type == ELEC) { if(b.lifetime < 40)//b.power * 20) { PVector toPlayer = PVector.sub(playerShip.pos, b.pos); PVector crossed = toPlayer.cross(b.vel); if(crossed.z < 0) b.vel = pivot(b.vel, min(PVector.angleBetween(toPlayer, b.vel), ELEC_SHOT_TURNING)); else b.vel = pivot(b.vel, -min(PVector.angleBetween(toPlayer, b.vel), ELEC_SHOT_TURNING)); } if(inRadius(b.pos, playerShip.pos, playerShip.hullsize)) { killPlayer(); break; } } if(focusing) { if(inRadius(b.pos, playerShip.pos, PLAYER_FIELD_RADIUS)) b.auraFlag = true; else if(b.auraFlag) { i.remove(); PVector snagDir = PVector.sub(b.pos, playerShip.pos); snagDir.normalize(); snagDir.mult(b.power/2); playerShip.pos.add(snagDir); if(snagType != b.type) snagCount = 0; snagCount+=b.power; snagType = b.type; addScore( b.power); snagSound.trigger(); } } else b.auraFlag = false; //TODO hit detection b.draw(); } } } void killPlayer() { explosions.add(new Explosion(playerShip.pos, 1000)); bigExploSound.trigger(); enemyShots.clear(); foes.clear(); playerShip.pos = new PVector(width/2, height/4*3); snagCount = 0; spawntimer = 120; lives--; if(lives <= 0 && gameMode != FREE_PLAY) gameover = true; } void doPlayerMovement() { int x = 0; int y = 0; if(downKeys.contains(LEFT)) x--; if(downKeys.contains(RIGHT)) x++; if(downKeys.contains(UP)) y--; if(downKeys.contains(DOWN)) y++; PVector move = new PVector(x, y); if(focusing) move.mult(PLAYER_FOCUS_SPEED); else move.mult(PLAYER_SPEED); playerShip.pos.add(move); playerShip.pos.x = max(playerShip.pos.x, 0 + PLAYER_MARGIN); playerShip.pos.x = min(playerShip.pos.x, width - PLAYER_MARGIN); playerShip.pos.y = max(playerShip.pos.y, 0 + PLAYER_MARGIN); playerShip.pos.y = min(playerShip.pos.y, height - PLAYER_MARGIN); } void drawField() { if(focusing) { smooth(); if(snagCount == 0) { stroke(#FFFFFF); fill(255,255,255, 96); } else { stroke(COLORS[snagType]); fill(COLORS[snagType], 96); } ellipse(playerShip.pos.x, playerShip.pos.y, PLAYER_FIELD_RADIUS*2, PLAYER_FIELD_RADIUS*2); noSmooth(); for(int i = snagCount; i > 0; i-=5) { float angle = random(PI); line(playerShip.pos.x + cos(angle) * i * 10, playerShip.pos.y + sin(angle) * i * 10, playerShip.pos.x - cos(angle) * i * 10, playerShip.pos.y - sin(angle) * i * 10); } } } void focus(boolean is) { focusing = is; if(focusing) { fieldSound.trigger(); } if(!focusing) { if(snagCount == 0) playerGun.queue.add(new BulletPack(SIMPLE,1, 1, 3)); else { if(snagType == SIMPLE) { int shotPow = snagCount / 10 + 2; for(int i=0; i wide) { playerGun.queue.add(new BulletPack(ELEC, wide, shotPow, 3)); totalFired += wide; } } } else //FIRE { int shotPow = snagCount / 10 + 2; for(int i=0; i< snagCount/(shotPow-1)/2 + 1; i++) playerGun.queue.add(new BulletPack(FIRE, 2, shotPow, 2)); } snagCount = 0; } } } void populateFoes() { int totalhazard = 0; for(int i = 0; i < foes.size(); i++) { Ship s = (Ship) foes.get(i); totalhazard += s.strength; } int headroom = rank*10 + threat / 4 - totalhazard; if(totalhazard == 0 || headroom >= threat * THREAT_RATIO) { if(spawntimer <= 0) { spawnThreat(threat * (random(1 - THREAT_RATIO) + THREAT_RATIO)); spawntimer = SPAWN_TIME_LIMIT; } } spawntimer--; if(!foesToAdd.isEmpty()) { foes.addAll(foesToAdd); foesToAdd.clear(); } } void spawnThreat(float level) { if(level<1) level = 1; PVector pos = new PVector(random(width), -MARGIN/2); PVector vel = new PVector(0, 2); int enemyType = min((int)random(threat/5), BOSS); if(gameMode == BOSS_RUSH) enemyType = BOSS; foes.add(new Ship(enemyType, (int)level, pos, vel)); } void runFoes() { Iterator i = foes.iterator(); while(i.hasNext()) { Ship s = (Ship) i.next(); s.think(); s.pos.add(s.vel); if(inRadius(s.pos, playerShip.pos, playerShip.hullsize + s.hullsize)) { killPlayer(); break; } if(s.pos.x < 0 - MARGIN || s.pos.x > width + MARGIN || s.pos.y < 0 - MARGIN || s.pos.y > height + MARGIN) { i.remove(); } s.draw(); } } void doExplosions() { Iterator i = explosions.iterator(); while(i.hasNext()) { Explosion e = (Explosion)i.next(); e.draw(); e.life--; if(e.life <= 0) i.remove(); } } void drawHud() { smooth(); textAlign(LEFT, TOP); fill(#FFFFFF); text("Score: "+ score + "\nLevel: "+ (rank-1) + "\nLives: "+ lives, 8, 8); noSmooth(); } void drawTitle() { smooth(); textAlign(CENTER, CENTER); fill(#FFFFFF); text("STARGRAZING\n\nselect game mode:\n'a' - Arcade\n'b' - Boss Rush\n'f' - Free Play", width/2, height/2); noSmooth(); } void draw() { //TODO if(paused) { //smooth(); textAlign(CENTER, CENTER); fill(#FFFFFF); text("paused", width/2, height/2); //noSmooth(); } else { background(0); drawStars(); if(gameMode == TITLE_SCREEN) { drawTitle(); } if(gameMode != TITLE_SCREEN) { if(!gameover) { playerGun.tick(); doPlayerMovement(); drawField(); } doExplosions(); doPlayerBullets(); if(!gameover) { populateFoes(); runFoes(); doEnemyBullets(); playerShip.draw(); } } if(gameMode != FREE_PLAY && gameMode != TITLE_SCREEN) drawHud(); } time++; if(gameover) { smooth(); textAlign(CENTER, CENTER); fill(#FFFFFF); text("Game Over", width/2, height/2); noSmooth(); } } //input processing goes here: void keyPressed() {/* if(key == 'p') { if(!gameover) paused = !paused; else{ reset(); firstplay = false; gameover = false; } }*/ if(!downKeys.contains(keyCode)) { downKeys.add(keyCode); down(keyCode); } } void keyReleased() { if(downKeys.indexOf(keyCode)!= -1) { downKeys.remove(downKeys.indexOf(keyCode)); up(keyCode); } } void down(int theKey) { println(theKey + " down"); // if(theKey == 92) threat+=20; if(gameMode == TITLE_SCREEN) { if(theKey == 65) {gameMode = ARCADE; reset();} if(theKey == 70) {gameMode = FREE_PLAY; reset();} if(theKey == 66) {gameMode = BOSS_RUSH; reset();} } else { if(gameover) { if(theKey == 90) { reset(); gameMode = TITLE_SCREEN; } } else if(theKey == 80) { paused = !paused; } else if(theKey == 81) { gameover = true; paused = false; } else if(theKey == 90 && !paused) focus(true); } } void up(int theKey) { println(theKey + " up"); if(theKey == 90 && !paused && !gameover && (gameMode != TITLE_SCREEN)) focus(false); /* playerGun.queue.add(new BulletPack(FIRE, 1, 2, 60)); playerGun.queue.add(new BulletPack(FIRE, 2, 2, 3)); playerGun.queue.add(new BulletPack(FIRE, 3, 2, 3)); playerGun.queue.add(new BulletPack(FIRE, 4, 2, 3)); playerGun.queue.add(new BulletPack(FIRE, 5, 2, 3)); playerGun.queue.add(new BulletPack(SIMPLE, 1, 2, 60)); playerGun.queue.add(new BulletPack(SIMPLE, 2, 2, 3)); playerGun.queue.add(new BulletPack(SIMPLE, 3, 2, 3)); playerGun.queue.add(new BulletPack(SIMPLE, 4, 2, 3)); playerGun.queue.add(new BulletPack(SIMPLE, 5, 2, 3)); */ } void stop() { // always close Minim audio classes when you are done with them shotSound.close(); fireSound.close(); fieldSound.close(); hitSound.close(); exploSound.close(); bigExploSound.close(); elecSound.close(); snagSound.close(); lifeSound.close(); minim.stop(); super.stop(); }