/** * Game 04: "Wavespark"
* Hold any key (besides P or Q) to increase gravity.
* Use slopes to build speed.
* 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.*; //import ddf.minim.effects.*; //import ddf.minim.signals.*; PImage bg; PImage bg2; PImage buffer; ArrayList levelMap; //AudioSample ouchSound; //AudioSample sweetSound; //AudioSample bonusSound; //AudioSample checkpointSound; //AudioSample timerSound; PFont font; //AudioOutput out; //AudioPlayer bgm; //AudioPlayer bgm2; //Minim minim; //BandPass bpf; //WhiteNoise wn; final int MODE_FREE_PLAY = 0; final int MODE_BONUS = 1; final int MODE_DISTANCE = 2; final int MODE_FIVEMINUTE = 3; int gameMode = MODE_BONUS; int clock; int CLOCK_CYCLE = 60; float GAME_SCALE = 0.6; float GRAVITY = 0.1 * sqrt(GAME_SCALE); float BOOST_GRAVITY = 0.7 * sqrt(GAME_SCALE); float BOOST_UP_GRAVITY = 0.2 * sqrt(GAME_SCALE); float CAM_MIN_Y = -200; float CAM_MAX_Y = 100; float MIN_X_SPEED = 4.0; PImage snapshot; float FIRST_CHECKPOINT = 20000; float CHECKPOINT_INCREMENT = 2500; float nextCheckpoint; float lastCheckpoint; int checkpoints; PVector camPos; PVector playerPos; PVector playerVel; boolean grounded; boolean locked; boolean slogging; int timerAir; int timerSlog; int maxAir; int maxSlog; int ouchCount; int sweetCount; int sweetChain; int sweetMax; int bonusCount; String exclaimString; color exclaimColor; int exclaimTimer; int EXCLAIM_TIME = 60; int bonusTimer; int bonusStack; int BONUS_TIME = 120; int BONUS_STAGGER = 10; ArrayList bonusQueue; String bonusString; int AIR_BONUS_TIME = 120; int AIR_BONUS_MULT = 2; float topSpeed; int bonuses; float distance; boolean ready; int CHECKPOINT_TIME = 60 * 46; int gameTimer; int lastSegment; float level_xpos = 100 * GAME_SCALE; float level_ypos = 0; float level_lastSlope = 0; int MOUNTAINS = 0; int WAVES = 1; int HILLS = 2; int RAMPS = 3; int terrain = WAVES; ArrayList grindParticles; ArrayList checkpointParticles; boolean paused; int gameState; int GAME_OVER = 0; int GAMEPLAY = 1; int TITLE_SCREEN = 2; int gameOverTimer; int keyTimer; class Particle { PVector pos; PVector vel; int life; int maxlife; Particle(PVector p, PVector v, int lifetime) { pos = new PVector(p.x, p.y); vel = new PVector(v.x, v.y); life = maxlife = lifetime; } } PVector randomVector(float magnitude) { float dir = random(TWO_PI); return new PVector(cos(dir)*magnitude, sin(dir)*magnitude); } void addSegment() { if(terrain == WAVES) { level_xpos += 200*GAME_SCALE; if(level_ypos < 80) level_lastSlope = 30 + random(80); else if(level_lastSlope < -10 && random(5)<1 && level_ypos < 300) level_lastSlope = random(50)+30; else if(level_ypos > 350) level_lastSlope = -30; else if(level_lastSlope < -70 /*was -100*/) level_lastSlope = 40; else level_lastSlope -= random(50)+10; level_ypos += level_lastSlope; levelMap.add(new PVector(level_xpos, level_ypos)); } if(terrain == MOUNTAINS) { level_xpos += 100*GAME_SCALE+(int)random(300*GAME_SCALE); levelMap.add(new PVector(level_xpos, random(200*GAME_SCALE)+random(200*GAME_SCALE)+80*GAME_SCALE)); } if(terrain == HILLS) { level_xpos += 300*GAME_SCALE; level_lastSlope = random(200)-100 - (level_ypos-250); level_ypos += level_lastSlope; levelMap.add(new PVector(level_xpos, level_ypos)); } if(terrain == RAMPS) { level_xpos += 100*GAME_SCALE; level_ypos += 100*GAME_SCALE; levelMap.add(new PVector(level_xpos, level_ypos)); level_xpos += -0*GAME_SCALE; level_ypos += -100*GAME_SCALE; levelMap.add(new PVector(level_xpos, level_ypos)); } } void reset() { ready = false; level_xpos = 100; level_ypos = 0; level_lastSlope = 0; lastSegment = 0; clock = 0; playerPos = new PVector(0, -200); playerVel = new PVector(0,0); levelMap = new ArrayList(); grindParticles = new ArrayList(); checkpointParticles = new ArrayList(); grounded = false; locked = false; levelMap.add(new PVector(-200, -200)); for(int i = 0; i < 20; i++) { addSegment(); } camPos = new PVector(50, 0); grounded = false; locked = false; timerAir = 0; timerSlog = 0; maxAir = 0; maxSlog = 0; topSpeed = 0; ouchCount = 0; sweetCount = 0; sweetChain = 0; sweetMax = 0; distance = 0; bonuses = 0; bonusCount = 0; bonusString = ""; bonusTimer = 0; checkpoints = 0; nextCheckpoint = FIRST_CHECKPOINT; if(gameMode != MODE_FIVEMINUTE) gameTimer = CHECKPOINT_TIME; else gameTimer = 121*60; bonusQueue = new ArrayList(); gameState = GAMEPLAY; pause(false); // bgm.loop(); } void setup() { size(640, 480, P2D); frameRate(60); font = loadFont("TartineScriptBlack-48.vlw"); bg = loadImage("abstractsunset.png"); bg.loadPixels(); /* minim = new Minim(this); bgm = minim.loadFile("wavespark_bgmusic.mp3", 2048); bgm2 = minim.loadFile("wavespark_scores.mp3", 2048); ouchSound = minim.loadSample("ouch.wav", 512); sweetSound = minim.loadSample("sweet.wav", 512); bonusSound = minim.loadSample("points.wav", 512); checkpointSound = minim.loadSample("checkpoint.wav", 512); timerSound = minim.loadSample("timer2.wav", 512); out = minim.getLineOut(); wn = new WhiteNoise(1.0); out.mute(); out.addSignal(wn); bpf = new BandPass(440, 200, out.sampleRate()); out.addEffect(bpf); */ titleScreen(); } void ouch() { //ouchSound.trigger(); ouchCount++; exclaimString = "Ouch!"; exclaimColor = color(255, 0, 0); exclaimTimer = EXCLAIM_TIME; } void sweet() { // sweetSound.trigger(); sweetCount++; sweetChain++; sweetMax = max(sweetMax, sweetChain); exclaimString = "Sweet!"; if(sweetChain > 1) exclaimString+="\nx"+sweetChain; exclaimColor = color(0, 192, 255); exclaimTimer = EXCLAIM_TIME; if(sweetChain == 1) awardBonus("Sweet", 200*sweetChain); else awardBonus("Sweet Chain", 200*sweetChain); if(sweetChain == 5) awardBonus("Dessert", 5000); } void drawParticles() { Iterator i = grindParticles.iterator(); while(i.hasNext()) { Particle p = (Particle) i.next(); p.pos.add(p.vel); fill(color(255,255, random(255))); noStroke(); ellipse(p.pos.x, p.pos.y, 4, 4); p.life--; if(p.life <= 0) i.remove(); } i = checkpointParticles.iterator(); while(i.hasNext()) { Particle p = (Particle) i.next(); p.pos.add(p.vel); colorMode(HSB); fill((clock*30)%255, 128, 255); colorMode(RGB); //fill(color(255,255, random(255))); noStroke(); float rad = map(p.life, p.maxlife, 0, 7, 0); ellipse(p.pos.x, p.pos.y, rad, rad); p.life--; if(p.life <= 0) i.remove(); } } void playerPhysics() { if(keyPressed) { playerVel.y += BOOST_GRAVITY; if(playerVel.y <0)playerVel.y += BOOST_UP_GRAVITY; } else playerVel.y += GRAVITY; if(grounded && (playerVel.mag() <= MIN_X_SPEED || playerVel.x <= 0)) { playerVel.normalize(); playerVel.mult(MIN_X_SPEED); if(playerVel.x <= 0) playerVel.mult(-1); slogging = true; } else slogging = false; //find collision segment playerPos.add(playerVel); int levelSegmentIndex = lastSegment; while(((PVector)levelMap.get(levelSegmentIndex)).x < playerPos.x) { levelSegmentIndex++; } levelSegmentIndex--; PVector origin = (PVector)levelMap.get(levelSegmentIndex); PVector segment = PVector.sub((PVector)levelMap.get(levelSegmentIndex+1), origin); PVector toPlayer = PVector.sub(playerPos, origin); PVector c = toPlayer.cross(segment); PVector d = playerVel.cross(segment); if((c.z < 0 && d.z < 0) || locked) { float proj = toPlayer.dot(segment) / segment.dot(segment); playerPos = PVector.add(origin, PVector.mult(segment, proj)); segment.normalize(); if(grounded && levelSegmentIndex != lastSegment) playerVel = PVector.mult(segment, playerVel.mag()); else { if(PVector.angleBetween(segment, playerVel) > PI * 0.4 && timerAir > 3) { ouch(); sweetChain = 0; if(playerVel.mag() > 20) awardBonus("Pancake", 500); } else { if(!grounded) if(PVector.angleBetween(segment, playerVel) < PI * 0.2 && playerVel.y > 12) sweet(); else sweetChain = 0; } playerVel = PVector.mult(segment, segment.dot(playerVel)); } playerVel.mult(0.999); grounded = true; } else grounded = false; if(grounded && keyPressed) locked = true; else locked = false; lastSegment = levelSegmentIndex; topSpeed = max(topSpeed, playerVel.x); } void drawPlayer() { if(keyPressed) fill(0, 255, clock%5*50); else fill(0, clock%5*50, 255); noStroke(); ellipse(playerPos.x, playerPos.y, 15, 15); if(!slogging) fill(clock%5*25+128, 255, 255); else fill(clock%5*25+128-70, 255-70, 255-70); ellipse(playerPos.x, playerPos.y, 13, 13); if(playerPos.y < camPos.y + 80) { PVector indicator = new PVector(camPos.x + width/2 - 40, camPos.y + 70); noFill(); //fill(clock%5*25+128, 255, 255, map(playerPos.y, camPos.y + 80, camPos.y, 0, 255)); //ellipse(indicator.x, indicator.y, 9, 9); stroke(clock%5*25+128, 255, 255, map(playerPos.y, camPos.y + 80, camPos.y, 0, 255)); strokeWeight(3); PVector to = PVector.sub(playerPos, indicator); to.normalize(); to.mult(25); to.add(indicator); line(indicator.x, indicator.y, to.x, to.y); int alt_radius = ((int)map(playerPos.y, camPos.y, camPos.y-height, 0, 50))%50; stroke(clock%5*25+128, 255, 255, map(alt_radius, 0, 50, 255, 0)); if(playerPos.y < camPos.y) ellipse(indicator.x, indicator.y, alt_radius, alt_radius); } } void drawTerrain() { int levelSegmentIndex = 0; stroke(color(255, 230, 200)); strokeWeight(3); fill(color(50, 20, 60, 192)); //if(terrain == WAVES) // fill(color(20, 50, 50, 192)); while(((PVector)levelMap.get(levelSegmentIndex)).x < camPos.x) { levelSegmentIndex++; } beginShape(); levelSegmentIndex--; PVector v = (PVector)levelMap.get(levelSegmentIndex); vertex(v.x, v.y); while(((PVector)levelMap.get(levelSegmentIndex)).x < camPos.x+width && levelMap.size() > levelSegmentIndex+1) { levelSegmentIndex++; v = (PVector)levelMap.get(levelSegmentIndex); vertex(v.x, v.y); } vertex(camPos.x+width+50, 600); vertex(camPos.x-50, 600); endShape(CLOSE); } void doCheckpoints() { if(playerPos.x > nextCheckpoint) { // checkpointSound.trigger(); for(int i=0; i 60) awardBonus("Punctuality", (int)(gameTimer * (FIRST_CHECKPOINT + checkpoints * CHECKPOINT_INCREMENT)/CHECKPOINT_TIME /10)); else awardBonus("Panic", 2000); checkpoints++; lastCheckpoint = nextCheckpoint; nextCheckpoint += FIRST_CHECKPOINT + checkpoints * CHECKPOINT_INCREMENT; gameTimer = CHECKPOINT_TIME; if(playerPos.y < CAM_MIN_Y) awardBonus("Flagpole", 2000); // exclaimString = "Time Extend!"; // exclaimColor = color(0, 140, 140); //color(0, 230, 120); // exclaimTimer = EXCLAIM_TIME; } colorMode(HSB); strokeWeight(5); stroke(color((clock*30)%255, 128, 255)); if(camPos.x+width > nextCheckpoint) line(nextCheckpoint, camPos.y, nextCheckpoint, camPos.y+height); float check_distance_limit = 3000; if(camPos.x+width > nextCheckpoint - check_distance_limit && camPos.x+width < nextCheckpoint) { float distto = (nextCheckpoint - (camPos.x+width))/check_distance_limit; stroke((clock*30)%255, 128, 255, map(distto, 1, 0, 0, 128)); noFill(); ellipse(camPos.x+width, camPos.y+height/2, distto*300, distto*300); noStroke(); fill((clock*30)%255, 128, 255, map(distto, 1, 0, 0, 128)); triangle(camPos.x+width - distto*170, camPos.y+height/2, camPos.x+width - 30 - distto*170, camPos.y+height/2 - 30, camPos.x+width - 30 - distto*170, camPos.y+height/2 + 30); } colorMode(RGB); } void pruneSegments() { while(lastSegment > 10) { levelMap.remove(0); lastSegment--; while(levelMap.size() < 40) addSegment(); } } void doTimers() { if(!grounded) timerAir++; else { maxAir = max(timerAir, maxAir); if(timerAir > AIR_BONUS_TIME) awardBonus("Air", (timerAir-AIR_BONUS_TIME)*AIR_BONUS_MULT); timerAir = 0; } if(slogging) timerSlog++; else { maxSlog = max(timerSlog, maxSlog); timerSlog = 0; } textFont(font, 32); textAlign(LEFT, TOP); // if(timerSlog > 60) if(timerAir > 2) { fill(color(0, 192, 255, timerAir/60.0*255 - 128)); stroke(0); text("Air Time: "+timerAir/60+"."+(timerAir/6)%10, 5, 50); } else { fill(color(255, 32, 32, timerSlog/60.0*255 - 128)); stroke(0); text("Slog Time: "+timerSlog/60+"."+(timerSlog/6)%10, 5, 50); } gameTimer--; exclaimTimer--; textFont(font, 48); textAlign(CENTER, TOP); if(gameTimer/60 > CHECKPOINT_TIME/60 - 2 && (gameTimer/3)%3==0 && gameMode != MODE_FIVEMINUTE) fill(color(255, 255, 0)); else if(gameTimer/60 < 6 && (gameTimer/3)%3==0) fill(color(255, 0, 0)); else fill(255); text(""+(gameTimer/60), width/2, 8); // if(gameTimer/60 <= 6 && (gameTimer%60)==0) timerSound.trigger(); if(bonusTimer <= 0 && bonusQueue.isEmpty()) { bonusString = ""; bonusStack = 0; } //else { if(!bonusQueue.isEmpty() && bonusTimer < BONUS_TIME - BONUS_STAGGER) { bonusTimer = BONUS_TIME; // bonusSound.trigger(); bonusString += bonusQueue.remove(0)+"\n"; } textAlign(RIGHT, TOP); fill(color(255, 255, 255.0/BONUS_TIME*bonusTimer)); textFont(font, 24); text(bonusString, width - 5, 5); } bonusTimer--; if(gameTimer <= 0) gameOver(); } void awardBonus(String reason, int amount) { if(gameMode == MODE_BONUS || gameMode == MODE_FIVEMINUTE) { bonusCount ++; bonuses += amount; bonusQueue.add(reason + " Bonus +"+amount); bonusStack++; if(bonusStack == 5) awardBonus("Bonus", +5000); // print(bonusStack); } } void drawProgress() { stroke(255, 128); strokeWeight(2); noFill(); ellipse(width/2-200, height-20, 10, 10); fill(255, 128); ellipse(width/2+200, height-20, 10, 10); noStroke(); fill(color(255, 0, 0, 128)); float timeGhost = map(gameTimer, CHECKPOINT_TIME, 0, -200, 200); ellipse(width/2+timeGhost, height-20, 7, 7); fill(color(128, 255, 255, 128)); float progress = map(playerPos.x, lastCheckpoint, nextCheckpoint, -200, 200); ellipse(width/2+progress, height-20, 7, 7); } void drawExclamation() { if(exclaimTimer >= EXCLAIM_TIME - 6 && exclaimTimer%2==0) fill(255); else fill(exclaimColor, 255.0/EXCLAIM_TIME*exclaimTimer); if(exclaimTimer > 0) { textFont(font, 48); textAlign(CENTER, CENTER); text(exclaimString, width/2, height/2); } } void pause(boolean toPause) { paused = toPause; if(paused) { capScreen(); // snapshot.filter(GRAY); // bgm.pause(); // out.mute(); } else { // bgm.play(); // out.unmute(); } } void capScreen() { snapshot = createImage(width, height, RGB); loadPixels(); snapshot.loadPixels(); for(int i=0; i 30) { gameOverTimer = 2000; keyTimer = 0;} if(gameState == GAME_OVER && gameOverTimer > 1200 && keyTimer > 30) titleScreen(); if(gameState == TITLE_SCREEN && keyTimer > 30) { if(key == 'f') {gameMode = MODE_FREE_PLAY; reset();} if(key == 't') {gameMode = MODE_FIVEMINUTE; reset();} if(key == 'b') {gameMode = MODE_BONUS; reset();} if(key == 'd') {gameMode = MODE_DISTANCE; reset();} } } void stop() { /* bgm.close(); bgm2.close(); out.close(); ouchSound.close(); sweetSound.close(); bonusSound.close(); checkpointSound.close(); timerSound.close(); minim.stop(); */ super.stop(); }