import ddf.minim.*; import ddf.minim.analysis.*; FFT fft; /** * Weekly Game 13 / Ludum Dare #17 Entry: "The Floor Is Lava"
* Z to jump, Arrows to move. Move right to earn points.
* M to mute, P to pause.
*
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; ArrayList islands; PVector player_pos; PVector player_vel; ParticleList ripples; ParticleList lava; ParticleList smoke; ParticleList blips; PVector cam; float ISLAND_DRAG = 0.007; float ISLAND_ACCEL = 0.003; float ISLAND_SINK_RATE = 0.02; float GRAVITY = 0.02; float JUMP = 1.2; float HOT_HOP = 2; int RIPPLE_TIME = 30; float MOVE_SPEED = 0.6; int MINIMUM_ISLANDS = 15; int ripple_timer; float life; float LIFE_DRAIN_RATE = 0.00005; float LIFE_BURN_RATE = 0.3; int spinny; Island on_island; PFont font; AudioSample jumpSound; AudioSample burnSound; AudioSample shinySound; AudioSample skipSound; AudioSample poofSound; AudioPlayer bgm; PVector shiny_thing; float ICE_START_DISTANCE = 1000; float ICE_DISTANCE_INCREMENT = 100; int ice_count = 0; int bonus; int ice_skip; int ICE_SKIP_BONUS = 10; int announce_timer; int ANNOUNCE_TIME = 360; boolean dead; void setup() { font = loadFont("PressStartK-32.vlw"); size(720, 480, P2D); frameRate(600); minim = new Minim(this); ellipseMode(RADIUS); strokeWeight(2); burnSound = minim.loadSample("burnt.wav", 1024); jumpSound = minim.loadSample("jump.wav", 1024); shinySound = minim.loadSample("gotice.wav", 1024); skipSound = minim.loadSample("iceskip.wav", 1024); poofSound = minim.loadSample("poof.wav", 1024); bgm = minim.loadFile("lava.mp3", 2048); bgm.loop(); fft = new FFT(bgm.bufferSize(), bgm.sampleRate()); fft.linAverages(5); fft.forward(bgm.mix); reset(); print((-4)/70); } void stop() { burnSound.close(); jumpSound.close(); shinySound.close(); skipSound.close(); poofSound.close(); minim.stop(); super.stop(); } void reset() { islands = new ArrayList(); player_pos = new PVector(width/2, height/2); player_vel = new PVector(0, 0); islands.add(new Island(new PVector(width/2, height/2), 100)); for(int i = 0; i < MINIMUM_ISLANDS; i++) islands.add(new Island(new PVector(random(width * 3), random(height)), random(50)+30)); collideIslands(); collideIslands(); collideIslands(); ripples = new ParticleList(); lava = new ParticleList(); smoke = new ParticleList(); blips = new ParticleList(); cam = new PVector(0, 0); shiny_thing = new PVector(ICE_START_DISTANCE, random(height)); life = 1; ice_count = 0; bonus = 0; ice_skip = 0; dead = false; paused = true; title = true; } class TextBlip extends Particle { String myText; color myColor; TextBlip(PVector p, int life, String label) { super(p, PVector.add(randomVector(0.5), new PVector(0, -2)), new PVector(0, 4.0/life), life); myText = label; myColor = color(255); } TextBlip(PVector p, int life, String label, color c) { super(new PVector(p.x, p.y-p.z), PVector.add(randomVector(0.5), new PVector(0, -2)), new PVector(0, 4.0/life), life); myText = label; myColor = c; } void draw() { textAlign(CENTER, BOTTOM); textFont(font, 8); fill(myColor); text(myText, pos.x, pos.y); } } class Ripple extends Particle { float radius; Ripple(PVector p, float r) { super(p, new PVector(0, 0), 180); radius = r; } void draw() { noFill(); stroke(255, 192, 64, 255*life/(float)maxlife); ellipse(pos.x, pos.y, radius, radius); } void tick() { radius += 0.03; super.tick(); } } class LavaDot extends Particle { LavaDot() { super(new PVector(cam.x + random(width), random(height)), new PVector(0, 0), 500); } void draw() { noStroke(); float bright = fft.getAvg(0) * life / maxlife; fill(160 + bright * 5, 32+ bright*5, 0); float r = (maxlife - life) * 0.1; ellipse(pos.x, pos.y, r, r); } } class SmokePoof extends Particle { SmokePoof(PVector p, int bigness) { super(p, randomVector(random(1.0/6)), new PVector(0, -0.05/36), bigness*6); } void draw() { fill(230, 230, 220); noStroke(); ellipse(pos.x, pos.y, life/6, life/6); } } class Island { PVector pos; PVector vel; float radius; boolean sinking; Island(PVector p, float r) { pos = new PVector(p.x, p.y); vel = new PVector(0, 0); radius = r; } void tick() { if(inRadius(player_pos, pos, radius) && player_pos.z <= 0 && !dead) { sinking = true; PVector to_player = PVector.sub(player_pos, pos); to_player.div(radius); to_player.mult(ISLAND_ACCEL); vel.add(to_player); on_island = this; } //else sinking = false; if(sinking) radius -= ISLAND_SINK_RATE; vel.mult(1 - ISLAND_DRAG); //if(sinking) player_pos.add(vel); pos.add(vel); } void draw() { if(sinking) stroke(255, 192, 64); else stroke(130, 120, 110); fill(160, 150, 140); ellipseMode(RADIUS); ellipse(pos.x, pos.y, radius, radius); } } color KOBOLD_FILL = color(175, 215, 180); color KOBOLD_STROKE = color(196, 176, 97); void collideIslands() { for(int ia = 0; ia < islands.size(); ia++) for(int ib = ia+1; ib < islands.size(); ib++) { Island a = (Island) islands.get(ia); Island b = (Island) islands.get(ib); if(inRadius(a.pos, b.pos, a.radius+b.radius)) { PVector a_to_b = PVector.sub(b.pos, a.pos); float overlap = a.radius + b.radius - a_to_b.mag(); float ab_weight_ratio = a.radius*a.radius / (a.radius*a.radius + b.radius*b.radius); a_to_b.normalize(); a_to_b.mult(overlap); a.pos.sub(PVector.mult(a_to_b, 1 - ab_weight_ratio)); b.pos.add(PVector.mult(a_to_b, ab_weight_ratio)); PVector new_vel = PVector.add(PVector.mult(a.vel, ab_weight_ratio), PVector.mult(b.vel, 1-ab_weight_ratio)); a.vel.x = new_vel.x; b.vel.x = new_vel.x; a.vel.y = new_vel.y; b.vel.y = new_vel.y; } } } void physicsStep() { ripple_timer -= 1; announce_timer --; life -= LIFE_DRAIN_RATE; if(ripple_timer <= 0) { ripple_timer = RIPPLE_TIME; lava.add(new LavaDot()); } if(player_pos.x < cam.x + width/4 && blips.isEmpty() && !dead) { //if(random(2) < 1) blips.add(new TextBlip(player_pos, 20, "--> !", color(198, 255, 128))); //else blips.add(new TextBlip(player_pos, 20, "-->", color(198, 255, 128))); } if(life < 0.2 && blips.size()<3 && !dead) { blips.add(new TextBlip(player_pos, 20, "!", color(192, 220, 255))); } on_island = null; Iterator i = islands.iterator(); while(i.hasNext()) { Island is = (Island) i.next(); is.tick(); if(ripple_timer == RIPPLE_TIME && (is.sinking || is.vel.mag() > 0.01)) ripples.add(new Ripple(is.pos, is.radius)); if(is.radius <= 0 || is.pos.x < cam.x - width/2) i.remove(); } collideIslands(); ripples.tick(); lava.tick(); smoke.tick(); player_vel.z -= GRAVITY; PVector steering = new PVector(0, 0); if(keys[LEFT]) steering.x -= 0.5; if(keys[RIGHT]) steering.x += 0.5; if(keys[UP]) steering.y -= 0.5; if(keys[DOWN]) steering.y += 0.5; steering.normalize(); steering.mult(MOVE_SPEED); if(player_pos.z <= 0 && on_island != null) { player_vel.x = on_island.vel.x; player_vel.y = on_island.vel.y; player_vel.add(steering); } else { PVector new_vel = new PVector(player_vel.x, player_vel.y); float magn = max(new_vel.mag(), MOVE_SPEED); new_vel.add(steering); if(new_vel.mag() > magn) new_vel.mult(magn / new_vel.mag()); player_vel.x = new_vel.x; player_vel.y = new_vel.y; } if(player_pos.z < 0) { if(on_island != null) { player_pos.z = 0; player_vel.z = 0; } else { //BURN if(!muted)burnSound.trigger(); ripples.add(new Ripple(player_pos, 1)); life-= LIFE_BURN_RATE; player_pos.z = 0; player_vel.z = HOT_HOP; blips.add(new TextBlip(player_pos, 60, "o_O;", color(255, 220, 128))); for(int si = 0; si < 5; si++) { smoke.add(new SmokePoof(player_pos, 10)); } } } if(keys['Z'] && player_pos.z <= 0 && on_island != null) { player_vel.z = JUMP; if(!muted)jumpSound.trigger(); } if(!dead) player_pos.add(player_vel); if(player_pos.y < 0) player_pos.y = 0; if(player_pos.y > height) player_pos.y = height; if(player_pos.x < cam.x) player_pos.x = cam.x; if(inRadius(player_pos, shiny_thing, 22) && player_pos.z < 10) { if(!muted) shinySound.trigger(); life = 1; ice_count++; ice_skip = 0; shiny_thing = new PVector(shiny_thing.x+ICE_START_DISTANCE+ICE_DISTANCE_INCREMENT*ice_count, random(height)); blips.add(new TextBlip(player_pos, 60, "<3", color(255, 160, 255))); } if(shiny_thing.x + 12 < cam.x) { ice_count++; shiny_thing = new PVector(shiny_thing.x+ICE_START_DISTANCE+ICE_DISTANCE_INCREMENT*ice_count, random(height)); ice_skip++; bonus += ice_skip * ICE_SKIP_BONUS; announce_timer = ANNOUNCE_TIME; if(!muted) skipSound.trigger(); blips.add(new TextBlip(player_pos, 60, ":D", color(255, 255, 128))); } if(life <0 && player_vel.z <= 0 && !dead) { dead = true; if(!muted) poofSound.trigger(); for(int si = 0; si < 5; si++) { smoke.add(new SmokePoof(new PVector(player_pos.x, player_pos.y - player_pos.z), 15)); } } while(islands.size() < MINIMUM_ISLANDS) { islands.add(new Island(new PVector(cam.x + width*1.5 + random(width/2), cam.y + random(height)), random(50)+30)); } } void drawingStep() { fft.forward(bgm.mix); background(160, 32, 0); float avg = fft.getAvg(0); pushMatrix(); if(player_pos.x > cam.x + width/2) cam.x = player_pos.x - width/2; translate(-cam.x, -cam.y); lava.draw(); ripples.draw(); Iterator i = islands.iterator(); while(i.hasNext()) { Island is = (Island) i.next(); is.draw(); } smoke.draw(); noStroke(); fill(0, 64); if(!dead) ellipse(player_pos.x, player_pos.y + 5, 10, 10); spinny++; regPoly(shiny_thing.x, shiny_thing.y + 5, 12, 4, spinny*0.15); if(!dead) { fill(KOBOLD_FILL); stroke(KOBOLD_STROKE); ellipse(player_pos.x, player_pos.y - player_pos.z, 10, 10); } color spinnycolor = color(255 - (spinny * 10)%255, 192 + 255 - (spinny * 10)%255, 255); fill(spinnycolor); noStroke(); regPoly(shiny_thing.x, shiny_thing.y, 12, 4, spinny*0.15); if(!paused) blips.tick(); blips.draw(); popMatrix(); fill(0, 64); noStroke(); rectMode(CORNER); if(paused) rect(0, 0, width, height); fill(255); textFont(font, 16); textAlign(LEFT, TOP); text("Awesome: "+(int)(cam.x / 50 + bonus), 8, 8); if(announce_timer > 0) text("+"+(ice_skip * ICE_SKIP_BONUS)+ ": Ice Skip Bonus x"+ice_skip, 8, 24); rectMode(CENTER); noStroke(); fill(0); rect(width/2, height - 16, (width - 32), 3); fill(spinnycolor); rect(width/2, height - 16, max((width - 32) * life, 0), 3); textAlign(CENTER, BOTTOM); textFont(font, 8); text("Coolth", width/2, height - 20); fill(255); textFont(font, 16); if(paused) { if(title) { textAlign(CENTER, BOTTOM); text("The Floor Is Lava", width/2, height/2); textAlign(CENTER, TOP); textFont(font, 8); text("prove kobold awesome by going --> that way\n\np = play also pause\nz = jump\narrows = move\nlava = ouch\nkobold = awesome? <3", width/2, height/2); } else { textAlign(CENTER, CENTER); text("Pauseified", width/2, height/2); } } if(dead) { textAlign(CENTER, BOTTOM); text("Game Over", width/2, height/2); textAlign(CENTER, TOP); textFont(font, 8); text("kobold dead due to lack of coolth :(\n\np to again? <3", 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(muted) bgm.mute(); else bgm.unmute(); } if(theKey == 'P') { if(dead) reset(); else { paused = !paused; title = false; } } } void up(int theKey) { println(theKey + " up"); }