import ddf.minim.*;
/**
* Game 20: "Icarace"
* Press space to flap.
*
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;
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();
}