import ddf.minim.*; /** * Game 20: "Icarace"
* Press space to flap.
*
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; int drawing_ms_last; 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; Minim minim; PFont font; AudioPlayer music; AudioSample flapSound; AudioSample flailSound; AudioSample splashSound; AudioSample landSound; void audioInit() { minim = new Minim(this); music = minim.loadFile("icarian_wind.mp3", 2048); flapSound = minim.loadSample("flap.wav", 1024); flapSound.setGain(-5); flailSound = minim.loadSample("failflap.wav", 1024); splashSound = minim.loadSample("splash.wav", 1024); landSound = minim.loadSample("landing.wav", 1024); music.loop(); } void audioClose() { music.close(); flapSound.close(); flailSound.close(); splashSound.close(); landSound.close(); minim.stop(); } float NOISE_SCALE = 0.05; float NOISE_SPEED = 0.05; float CAMERA_OFFSET = 96; float WATER_LEVEL = 30; float WAVE_HEIGHT = 20; float LAND_LEVEL = 60; float CLOUD_MIN_R = 15; float CLOUD_MAX_R = 35; float CLOUD_SPACING = 80; float CLOUD_MARGINS = 200; float SHORE_DISTANCE = 800 * 25 + CAMERA_OFFSET; float HEAT_SCALING = 0.0003; float HEAT_RATE = 0.002; float SHADE_RATE = 0.01; float SKY_TEMP = 1; float SHADE_TEMP = 0; float CEIL_TEMP = 3; float GRAVITY = 0.01; float X_FLAP_IMPULSE = 0.3; float Y_FLAP_IMPULSE = 0.4; float IMPULSE_HEIGHT_BONUS = 0.8; float X_DRAG = 0.998; float CLOUD_DRAG = 0.99; float FINISH_RESET_FRAMES = 180; float SHADE_ANGLE = HALF_PI*0.8; color LAND_COLOR = color(192, 128, 64); int LARGE_NUMBER = 180 * 999; int phys_frame; int draw_frame; int finish_frame; int game_timer; float camera_x; PVector player_pos; PVector player_vel; boolean started; boolean ended; boolean victory; float wax; float heat; ArrayList clouds; ParticleList particles; float bestmarker = -10; float lastmarker = -10; float besttime = LARGE_NUMBER; void newGame() { started = false; ended = false; victory = false; player_pos = new PVector(CAMERA_OFFSET, height/2); player_vel = new PVector(0, 0); clouds = new ArrayList(); particles = new ParticleList(); for(float x = -CLOUD_MARGINS + width; x < width+width+CLOUD_MARGINS; x+= CLOUD_SPACING) { clouds.add(new Cloud(new PVector(x, (random(0.6)+0.2)*height))); } camera_x = 0; wax = 1.0; heat = 0; phys_frame = 0; draw_frame = 0; game_timer = 0; } void physicsStep() { particles.tick(); phys_frame++; if(started && !ended) { player_vel.y += GRAVITY; if(player_pos.x < SHORE_DISTANCE) game_timer++; } if(ended) player_vel.x = 0; player_vel.x *= X_DRAG; boolean shade = false; for(int i = 0; i < clouds.size(); i++) { Cloud c = (Cloud)clouds.get(i); if(inRadius(c.pos, player_pos, c.radius)) { player_vel.mult(CLOUD_DRAG); if(phys_frame%3 == 0) particles.add(new CloudFluff(player_pos)); } if(c.shades(player_pos)) shade = true; } player_pos.add(player_vel); camera_x = player_pos.x - CAMERA_OFFSET; Cloud c1 = (Cloud)clouds.get(0); if(c1.pos.x < camera_x-CLOUD_MARGINS) { clouds.remove(0); clouds.add(new Cloud(new PVector(camera_x+width+CLOUD_MARGINS, (random(0.6)+0.2)*height))); } if(player_pos.y > height - WATER_LEVEL && !ended) { //TODO splash effect ended = true; finish_frame = phys_frame; lastmarker = player_pos.x; if(player_pos.x > bestmarker) bestmarker = player_pos.x; if(!muted) splashSound.trigger(); } if(player_pos.y > height - LAND_LEVEL - 10 && player_pos.x > SHORE_DISTANCE &&!ended) { //TODO splash effect victory = true; ended = true; finish_frame = phys_frame; player_pos.y = height - LAND_LEVEL - 10; player_vel.x = player_vel.y = 0; lastmarker = player_pos.x; if(player_pos.x > bestmarker) bestmarker = player_pos.x; if(game_timer < besttime) besttime = game_timer; if(!muted) landSound.trigger(); } if(started && !ended) { if(shade) { heat = SHADE_RATE*SHADE_TEMP + (1-SHADE_RATE)*heat; } else if(player_pos.y < 0) { heat = HEAT_RATE*CEIL_TEMP + (1-HEAT_RATE)*heat; } else { heat = HEAT_RATE*SKY_TEMP + (1-HEAT_RATE)*heat; } wax -= heat * HEAT_SCALING; } } void drawingStep() { if(!paused) draw_frame++; background(16, 192, 255); pushMatrix(); translate(-camera_x, 0); //draw shadows for(int i = 0; i < clouds.size(); i++) { Cloud c = (Cloud)clouds.get(i); noFill(); stroke(16*0.95, 192*0.95, 255*0.95); strokeWeight(3); PVector cornerA = PVector.add(c.pos, PVector.mult(c.shade_perp, c.radius)); PVector cornerB = PVector.sub(c.pos, PVector.mult(c.shade_perp, c.radius)); PVector cornerC = PVector.add(cornerB, PVector.mult(c.shade_dir, height*1.2-c.pos.y)); PVector cornerD = PVector.add(cornerA, PVector.mult(c.shade_dir, height*1.2-c.pos.y)); beginShape(); vertex(cornerA.x, cornerA.y); vertex(cornerB.x, cornerB.y); vertex(cornerC.x, cornerC.y); vertex(cornerD.x, cornerD.y); endShape(CLOSE); } for(int i = 0; i < clouds.size(); i++) { Cloud c = (Cloud)clouds.get(i); fill(16*0.9, 192*0.9, 255*0.9); noStroke(); strokeWeight(1); PVector cornerA = PVector.add(c.pos, PVector.mult(c.shade_perp, c.radius)); PVector cornerB = PVector.sub(c.pos, PVector.mult(c.shade_perp, c.radius)); PVector cornerC = PVector.add(cornerB, PVector.mult(c.shade_dir, height*1.2-c.pos.y)); PVector cornerD = PVector.add(cornerA, PVector.mult(c.shade_dir, height*1.2-c.pos.y)); beginShape(); vertex(cornerA.x, cornerA.y); vertex(cornerB.x, cornerB.y); vertex(cornerC.x, cornerC.y); vertex(cornerD.x, cornerD.y); endShape(CLOSE); } //draw title textFont(font); textAlign(CENTER, CENTER); fill(255); if(!started) { text("Icarace", width/2, height/2); textFont(font, 24); if(bestmarker > 0) text("Best distance: "+(int)((bestmarker-CAMERA_OFFSET)/25), width/2, height/2 + 35); if(besttime < LARGE_NUMBER) text("Best time: "+(int)((besttime)/PHYSICS_FPS), width/2, height/2 + 65); } //draw cliff if(camera_x < 110) { fill(LAND_COLOR); beginShape(); noStroke(); strokeWeight(1); vertex(-1, height/2+10); vertex(110, height/2+10); vertex(90, height*3/4); vertex(80, height); vertex(-1, height); endShape(CLOSE); } if(camera_x > SHORE_DISTANCE - width) { fill(LAND_COLOR); noStroke(); strokeWeight(1); rect(max(camera_x, SHORE_DISTANCE), height-LAND_LEVEL, width, LAND_LEVEL); } //draw player strokeWeight(2); fill(255, 192, 128); stroke(240, 230, 220); if(wax <= 0) noStroke(); ellipse(player_pos.x, player_pos.y, 10, 10); //draw clouds for(int i = 0; i < clouds.size(); i++) { Cloud c = (Cloud)clouds.get(i); fill(255); stroke(230); strokeWeight(3); ellipse(c.pos.x, c.pos.y, c.radius, c.radius); } particles.draw(); fill(0, 0, 255); noStroke(); strokeWeight(1); textAlign(CENTER, BOTTOM); textFont(font, 18); triangle(lastmarker, height - 60, lastmarker - 5, height - 75, lastmarker +5, height - 75); text((int)((lastmarker-CAMERA_OFFSET)/25), lastmarker, height-75); fill(255, 255, 0); triangle(bestmarker, height - 60, bestmarker - 5, height - 75, bestmarker +5, height - 75); text((int)((bestmarker-CAMERA_OFFSET)/25), bestmarker, height-75); popMatrix(); //draw water strokeWeight(1); noFill(); stroke(0, 70, 116); for(int i = 0; i < width; i++) { line(i, height, i, height-(WATER_LEVEL + WAVE_HEIGHT) + WAVE_HEIGHT*sqrt(noise((i+camera_x)*NOISE_SCALE, (draw_frame)*NOISE_SPEED))); } //draw hud if(started && wax > 0) { noStroke(); fill(240, 230, 220); rect(16, height-16, 5, -(height-32)*wax); fill(255, 255-draw_frame%8*16, 0); if(!ended) ellipse(18, height-16 -(height-32)*wax, heat*8+2, heat*8+2); } if(started) { textFont(font, 24); textAlign(LEFT, TOP); fill(255); text("Distance: "+(int)((player_pos.x-CAMERA_OFFSET)/25)+"\nTime: "+game_timer/PHYSICS_FPS, 32, 16); } if(paused) { fill(0, 128); noStroke(); strokeWeight(1); rect(0, 0, width, height); fill(255); textFont(font, 24); textAlign(CENTER, CENTER); text("Paused", 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) { if(theKey == ' ' && !paused && !ended && wax > 0) { started = true; player_vel.x += X_FLAP_IMPULSE; player_vel.y -= Y_FLAP_IMPULSE + player_pos.y / height * IMPULSE_HEIGHT_BONUS; if(!muted) flapSound.trigger(); } if(theKey == ' ' && !paused && !ended && wax <= 0) { if(!muted) flailSound.trigger(); } if(theKey == ' ' && ended && phys_frame > finish_frame + FINISH_RESET_FRAMES) newGame(); println(theKey + " down"); if(theKey == 'M') { muted = !muted; if(muted) music.mute(); else music.unmute(); } if(theKey == 'P' && started && !ended) paused = !paused; } void up(int theKey) { println(theKey + " up"); } ///////////////////////////////////////////////// void setup() { size(720, 480); ellipseMode(RADIUS); frameRate(600); font = loadFont("BookAntiqua-Italic-48.vlw"); audioInit(); newGame(); } void stop() { audioClose(); super.stop(); }