float BIG_ASTEROID_SIZE = 20.0; int level = 0; Sample pistolshot, explosion, crash, thrust; BFont font; int score = 0; int hits = 0; int shots = 0; class Star { float x,y,phase,phaseStep; color col; Star() { phase = random(TWO_PI); phaseStep = random(0.005,0.01); x = random(width); y = random(height); col = color(random(230,255),random(230,255),random(230,255),64); } void draw() { stroke(col); line(x,y,x,y); color c = color(255.0, 255.0, 255.0, 64.0 + (128.0 * sin(TWO_PI*phase)) ); stroke(c); line(x,y,x,y); phase += phaseStep; phase %= TWO_PI; } } abstract class WorldObject { float x,y,vx,vy,radius; WorldObject(float x, float y, float vx, float vy, float radius) { this.x = x; this.y = y; this.vx = vx; this.vy = vy; this.radius = radius; } void move() { x += vx; y += vy; x = wrap(x,0,width,radius); y = wrap(y,0,height,radius); } boolean collide(WorldObject other) { float distance2 = sq(other.x-this.x) + sq(other.y-this.y); return distance2 < sq(this.radius+other.radius); } abstract void draw(); } abstract class Particle extends WorldObject { int maxage = 10; int age = 0; Vector container; Particle(float x, float y, float vx, float vy, float radius, int maxage) { super(x,y,vx,vy,10); this.maxage = maxage; } void move() { super.move(); age++; if (age == maxage && container != null) { container.removeElement(this); } } abstract void draw(); } class Bullet extends Particle { Bullet(float x, float y, float vx, float vy) { super(x,y,vx,vy,10.0,12); container = bullets; } void draw() { stroke(255,255,0); line(x,y,x-(vx/2),y-(vy/2)); } } class Exhaust extends Particle { Exhaust(float x, float y, float vx, float vy) { super(x,y,vx,vy,10.0,(int)random(5,10)); container = particles; } void draw() { blend(exPart,0,0,exPart.width-1,exPart.height-1,(int)x-exPart.width/2,(int)y-exPart.height/2,(int)x+exPart.width/2,(int)y+exPart.height/2,ADD); } } class Explode extends Particle { Explode(float x, float y, float vx, float vy) { super(x,y,vx,vy,10.0,(int)random(30,50)); container = particles; } void draw() { blend(exPart,0,0,exPart.width-1,exPart.height-1,(int)x-exPart.width/2,(int)y-exPart.height/2,(int)x+exPart.width/2,(int)y+exPart.height/2,ADD); } } class Debris extends Particle { Debris(float x, float y, float vx, float vy) { super(x,y,vx,vy,10.0,(int)random(5,25)); container = particles; } void draw() { blend(exPart,0,0,exPart.width-1,exPart.height-1,(int)x-exPart.width/2,(int)y-exPart.height/2,(int)x+exPart.width/2,(int)y+exPart.height/2,ADD); // rectMode(CENTER_DIAMETER); // fill(200,200,200,200-(age*6)); // noStroke(); // rect(x,y,2,2); } } class Ship extends WorldObject { float angle; float drag = 0.95; boolean shields = false; float power, shieldpower; float maxpower = 20.0; float maxshieldpower = 200.0; boolean collided = false; int lives = 3; Ship() { super(width/2,height/2,0,0,10.0); reset(); } void reset() { x = width/2; y = height/2; vx = 0; vy = 0; shields = false; collided = false; angle = 0; power = maxpower; shieldpower = maxshieldpower; } void turn(float off) { angle += off; } void accel(float accel) { if (!thrust.isPlaying()) { thrust.repeat(); } float cx = cos(angle); float cy = sin(angle); vx += cx * accel; vy += cy * accel; particles.addElement(new Exhaust(x-10*cx,y-10*cy,random(2*vx/3,vx),random(2*vy/3,vy))); } void shields() { if (shieldpower > 0) { shields = true; shieldpower--; } } void move() { shields = false; if (keysPressed[UP]) accel(1.0); else if (keysPressed[DOWN]) shields(); if (keysPressed[LEFT]) turn(-0.2); else if (keysPressed[RIGHT]) turn(0.2); // if (keysPressed[' ']) // fire(); super.move(); vx *= drag; vy *= drag; } void draw() { push(); translate(x,y); rotate(angle); drawShip(255); pop(); for (int i = 0; i < lives; i++) { push(); translate((width*0.5)-(15*lives)+(i*30),height-30); rotate(-HALF_PI); drawShip(100); pop(); } if (shields) { ellipseMode(CENTER_DIAMETER); stroke(255,128,0); if (collided) { fill(255,255,128,70); } else { fill(200,200,100,70); } ellipse(x,y,3*radius,3*radius); } noStroke(); beginShape(QUADS); fill(255,255,0,128); vertex(10,10+((height-20)*((maxpower-power)/maxpower))); vertex(25,10+((height-20)*((maxpower-power)/maxpower))); fill(255,0,0,128); vertex(25,height-10); vertex(10,height-10); endShape(); beginShape(QUADS); fill(0,255,0,128); vertex(width-25,10+((height-20)*((maxshieldpower-shieldpower)/maxshieldpower))); vertex(width-10,10+((height-20)*((maxshieldpower-shieldpower)/maxshieldpower))); fill(255,0,0,128); vertex(width-10,height-10); vertex(width-25,height-10); endShape(); } void drawShip(int alph) { noStroke(); beginShape(POLYGON); fill(120,0,30,alph); vertex(-10,-10); fill(220,30,80,alph); vertex(12.5,0); fill(255,255,255,alph); vertex(-10,10); vertex(-5,0); endShape(); } void fire() { pistolshot.play(); shots++; bullets.addElement(new Bullet(x,y,20*cos(angle),20*sin(angle))); } boolean collide(WorldObject other) { collided = super.collide(other); if (collided && !shields) { crash.play(); float distance = sqrt(sq(x-other.x)+sq(y-other.y)); float speed = sqrt(sq(vx)+sq(vy)) + sqrt(sq(other.vx)+sq(other.vy)); vx = speed * (x-other.x) / distance; vy = speed * (y-other.y) / distance; power--; if (power < 0) { explode(); reset(); } } return collided; } void explode() { explosion.play(); for (int i = 0; i < 512; i++) { float speed = random(1,4); float angle = random(TWO_PI); particles.addElement(new Explode(x+random(-10,10),y+random(-10,10),speed*cos(angle),speed*sin(angle))); } lives--; if (lives == 0) { lives = 3; score = 0; lastscore = 0; hits = 0; shots = 0; // TODO save score table, enter name, etc. } } } class Asteroid extends WorldObject { int n = 15; float xs[]; float ys[]; int c[]; float angle, angleStep; Asteroid(float rad) { super(random(width),random(height),random(-1,1),random(-1,1), rad); xs = new float[n]; ys = new float[n]; c = new int[n]; for (int i = 0; i < n; i++) { float p = (float)i/n; xs[i] = rad*cos(TWO_PI*p) + random(-rad/4,rad/4); ys[i] = rad*sin(TWO_PI*p) + random(-rad/4,rad/4); c[i] = (int)random(60,150); } angle = random(TWO_PI); angleStep = random(-0.03,0.05); } void move() { super.move(); angle+=angleStep; } void draw() { noStroke(); push(); translate(x,y); rotate(angle); beginShape(POLYGON); for (int i = 0; i < n; i++) { fill(c[i]); vertex(xs[i], ys[i]); } endShape(); pop(); } boolean collide(WorldObject other) { boolean collided = super.collide(other); if (collided) { asteroids.removeElement(this); if (radius > BIG_ASTEROID_SIZE/4) { for (int i = 0; i < 4; i++) { Asteroid babe = new Asteroid(radius/2); babe.x = x+random(-radius/2,radius/2); babe.y = y+random(-radius/2,radius/2);; asteroids.addElement(babe); } } for (int i = 0; i < 8*radius; i++) { float speed = random(5); float angle = random(TWO_PI); particles.addElement(new Debris(x+random(-radius,radius),y+random(-radius,radius),speed*cos(angle),speed*sin(angle))); } score += 1000.0/radius; explosion.play(); } return collided; } } Ship ship; Star[] stars; Vector asteroids = new Vector(); Vector particles = new Vector(); Vector bullets = new Vector(); BImage exPart; boolean keysPressed[]; void setup() { size(360,360); Sonia.start(this); ship = new Ship(); stars = new Star[256]; for (int i = 0; i < stars.length; i++) { stars[i] = new Star(); } exPart = createParticleImage(8); keysPressed = new boolean[128]; for (int i = 0; i < keysPressed.length; i++) { keysPressed[i] = false; } framerate(60); pistolshot = new Sample("fire.wav"); explosion = new Sample("explosion.wav"); crash = new Sample("metalhit.wav"); thrust = new Sample("thrust.wav"); font = loadFont("Futura-Light.vlw.gz"); lastscore = 0; } int lastscore; void loop() { background(0); for (int i = 0; i < stars.length; i++) { stars[i].draw(); } for (int i = 0; i < asteroids.size(); i++) { Asteroid asteroid = (Asteroid)asteroids.elementAt(i); asteroid.move(); asteroid.draw(); ship.collide(asteroid); for (int j = 0; j < bullets.size(); j++) { Particle bullet = (Particle)bullets.elementAt(j); if (asteroid.collide(bullet)) { bullets.removeElement(bullet); hits++; break; } } } /* for (int i = 0; i < asteroids.size(); i++) { Asteroid asteroid = (Asteroid)asteroids.elementAt(i); for (int j = i+1; j < asteroids.size(); j++) { Asteroid asteroid2 = (Asteroid)asteroids.elementAt(i); asteroid.collide(asteroid2); } }*/ for (int i = 0; i < particles.size(); i++) { Particle particle = (Particle)particles.elementAt(i); particle.move(); particle.draw(); } for (int i = 0; i < bullets.size(); i++) { Particle particle = (Particle)bullets.elementAt(i); particle.move(); particle.draw(); } ship.move(); ship.draw(); if ( asteroids.size() == 0 ) { for (int i = 0; i < 4 + level; i++) { asteroids.addElement(new Asteroid(BIG_ASTEROID_SIZE)); } level++; } noStroke(); textFont(font); textSize(24); fill(255,0,0,200); text(level,40,24); if (shots > 0) text(nf((100.0*(float)hits/(float)shots),3,2) + "%",80,24); fill(255,255,255,200); if (lastscore < score) lastscore+=(score-lastscore)/5; if ((lastscore-score)/ 5 == 0) lastscore = score; text(lastscore,160,24); } void keyPressed() { keysPressed[key] = true; } void keyReleased() { keysPressed[key] = false; if (key == ' ') { ship.fire(); } if (key == UP) { thrust.stop(); } } float wrap(float val, float min, float max, float radius) { if (val > max+(2*radius)) { val -= (max-min)+4*radius; } else if (val < min-2*radius) { val += (max-min)+4*radius; } return val; } BImage createParticleImage(int dim) { BImage img = new BImage(dim,dim); float maxd = max(img.width/2,img.height/2); for (int i = 0; i < img.width; i++) { for (int j = 0; j < img.height; j++) { float d = sqrt(sq(i-(img.width/2))+sq(j-(img.height/2))); img.set(i,j,color(255,128,10,255-(255*d/maxd))); } } return img; } // safely stop the Sonia engine upon shutdown. public void stop(){ Sonia.stop(); super.stop(); }