Your browser does not support the canvas tag.

previous        Show / Hide Source        Download        next
/* Introduction to Media Computation - Assignment 2: Interactive Toy
 
 By Roberto Rosales
 10/2018
 
 "Banshee Ride"
 You pilot a Type 26C 'Banshee' aircraft. UNSC forces relentlessly try to take you down with long range missiles. 
 You control your Banshee with the mouse, and each missile you evade adds to your score.
 */

// Stablish variables

// Banshee variables  
float bansheeX;
float bansheeY;
float bansheeTrailMin = 40;
float bansheeTrailMax = 50;
float bansheeTrailWeight = 2.5;
// Mountain variables
float mountainX = 0;
// Cloud variables
float cloud1PositionX = 800;
float cloud1PositionY = 60;
float cloud2PositionX = 600;
float cloud2PositionY = 120;
float refresh = 1000;
// Missile and interactions variables
float missileX = width;
float missileY = random(5, 395);
float missileWidth = 25;
float missileHeight = 5;
float missileSpeed = 12 ;
float missileSpawnXMin = 413;
float missileSpawnXMax = 600;
float missileSpawnYMin = 2.4;
float missileSpawnYMax = 397.5;
float explosionSize = 0;

int score = 0;

void setup() {
  size(400, 400);
  noCursor();
}

void draw() {
  background(10, 40, 60);
  frameRate(60);

  displayMovingMountains();
  respawnMountains();
  drawMoon();
  drawClouds();
  drawTheGloriousBanshee();
  drawMissiles();
  respawnMissile();
  missileCollition();
  text("SCORE: " + score, 320, 25);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////   THIS SEPARATES DRAW LOOP FROM KEY AND USER FUNCTIONS  ////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void keyPressed() {
  /* NOTE: Originally the banshee was controlled with 'wasd' keys, but I found the movement too clunky and
   I wanted to make the missiles relatively fast. So a mouse controlled Banshee made more sense, but I didn't
   want to completely scrap these cool (yet pretty irrelevant) visual effects. */
  if (key == 'd') {
    // Increase intensity of trail, make trail longer and thicker
    bansheeTrailMin = 80;
    bansheeTrailMax = 100;
    bansheeTrailWeight = 5;
  }
  if (key == 'a') {
    // Decrease intensity of trail, make trail shorter and thinner
    bansheeTrailMin = 15;
    bansheeTrailMax = 40;
    bansheeTrailWeight = 2;
  }
}
void keyReleased() {
  if (key == 'd') {
    // Revert Banshee's trail parameters to normal
    bansheeTrailMin = 40;
    bansheeTrailMax = 60;
    bansheeTrailWeight = 2.5;
  }
  if (key == 'a') {
    // Revert Banshee's trail parameters to normal
    bansheeTrailMin = 40;
    bansheeTrailMax = 60;
    bansheeTrailWeight = 2.5;
  }
}


void displayMovingMountains() {
  /* MOUNTAINS & STUFF
   This is a fairly simple 'move and respawn' mechanic. The mountain moves at certain rate from right to left, 
   once it reaches a negative point beyond the visible screen its X variable resets to a positive X value beyond the visible screen.
   */
  fill(80, 70, 50);
  stroke(90, 95, 100, 150);
  strokeWeight(4);
  triangle(mountainX - 10, height, mountainX + 56, 350, mountainX + 100, height);
  triangle(mountainX + 70, height, mountainX + 100, 380, mountainX + 130, height);
  triangle(mountainX + 130, height, mountainX + 160, 370, mountainX + 230, height);
  quad(mountainX + 200, height, mountainX + 220, 380, mountainX + 250, 370, mountainX + 280, height);
  quad(mountainX + 280, height, mountainX + 260, 380, mountainX + 290, 380, mountainX + 310, height);

  /* Moutain speed
   negative number to indicate the direction desired */
  mountainX = mountainX -0.7;
}

void respawnMountains() {
  /* Mountain respawn
   The condition to reset the X position of the mountains once it reaches the negative point beyond the visible screen. */
  if (mountainX <= -320) {
    mountainX = 420;
  }
}


void drawMoon() {
  /* MOON & STUFF
   Pretty straight foward, just thought it look neat. I found no actual need to use variables here honestly. */
  noStroke();
  // Glow
  fill(255, 228, 80, 50);
  ellipse(70, 50, 100, 100);
  // Moon
  fill(220, 220, 190);
  ellipse(70, 50, 70, 70);
  // Crater 1
  fill(200, 200, 170, 150);
  ellipse(78, 55, 50, 50);
  // Crater 2
  fill(190, 190, 160);
  ellipse(50, 30, 12, 15);
  // Crater 3
  ellipse(60, 40, 30, 30);
  // Crater 4
  ellipse(85, 65, 10, 10);
}


void drawClouds() {
  // CLOUDS & STUFF
  // Again, not a lot of interaction with these, just thought they looked nice.
  // Cloud 1
  fill(80, 90, 100, 200);
  noStroke();
  ellipseMode(CENTER);
  /* I'm using a technique that I also used in my interactive drawing. Visually it has the same effect as a 'respawn' condition statement,
   but you can't really play around that much with the values in this technique, but it's easy to make on the go and fits fine 
   dddddfor the purposes of the clouds. */
  ellipse(cloud1PositionX - (frameCount % refresh), cloud1PositionY, 120, 40); 
  ellipse(cloud1PositionX + 40 - (frameCount % refresh), cloud1PositionY + 20, 50, 15);
  ellipse(cloud1PositionX - 40 - (frameCount % refresh), cloud1PositionY + 20, 90, 15);
  ellipse(cloud1PositionX - 60 - (frameCount % refresh), cloud1PositionY + 10, 30, 20);

  // Cloud 2
  ellipse(cloud2PositionX - (frameCount % refresh), cloud2PositionY, 100, 20);
  ellipse(cloud2PositionX - 10 - (frameCount % refresh), cloud2PositionY - 8, 60, 20);
  ellipse(cloud2PositionX - 20 - (frameCount % refresh), cloud2PositionY - 15, 60, 20);
}


void drawTheGloriousBanshee() {
  // BANSHEE & STUFF
  /* I chose to use only 2 variables for the 'building' of the Banshee mainly because I thought it'd be easier to manage just
   two variables at the moment of adding movement, speed, and controls to the Banshee.
   There are more variables other than just bansheeX and bansheeY, but they're mostly invovled in other systems. */
  noStroke();
  fill(100, 70, 100, 240);
  ellipseMode(CENTER);

  // Declare the position of the Banshee. Banshee is controlled by the position of the mouse, so it's pretty nimble and stuff.
  bansheeX = mouseX;
  bansheeY = mouseY;

  // Main body
  ellipse(bansheeX, bansheeY, 20, 25);
  ellipse(bansheeX - 25, bansheeY + 6, 42, 18);

  // Upper body
  ellipse(bansheeX - 12, bansheeY - 7, 40, 15);
  ellipse(bansheeX - 35, bansheeY - 8, 30, 10);

  // Wing
  strokeWeight(3);
  stroke(100, 80, 100);
  noFill();
  //  (        top left           /          top right         /          right bot          /          left bot         )
  quad(bansheeX - 30, bansheeY + 8, bansheeX - 5, bansheeY + 8, bansheeX - 15, bansheeY + 23, bansheeX - 25, bansheeY + 23); 
  line(bansheeX - 8, bansheeY + 8, bansheeX - 18, bansheeY +23);
  // Engine on wing
  noStroke();
  fill(120, 80, 120);
  ellipse(bansheeX - 20, bansheeY + 23, 10, 8);

  // Energy trail on wing
  /* This is just aesthetic effect, to give the Banshee a feel that it's actually flying and propelling*/
  strokeWeight(bansheeTrailWeight);
  /* We use the 'tan(frameCount)' to dictate that transparency will "accelerate/decelerate" giving the flicker effect,
   which in my opinion gives it a more energy feel rather than a combustion effect like we see with the missiles */
  stroke(90, 150, 255, tan(frameCount)*500);
  /* We use random variables for the length of the trail to give it a 'propulsion' effect
   I chose using a simple line as it looks aesthetically cleaner/simpler, which fits the whole theme of the Covenant
   We're also defining the random lenght of the trail as variables as we'll manipulate them later
   Update: I ended up scrapping the ideas to manipulate the trail, but kept the vars for the sake of simplicity. */
  line(bansheeX - random(bansheeTrailMin, bansheeTrailMax), bansheeY + 23, bansheeX - 22, bansheeY + 23);

  // Main body outline
  stroke(60);
  strokeWeight(4);
  curve(bansheeX + 5, bansheeY + 26, bansheeX, bansheeY + 11, bansheeX - 10, bansheeY - 1, bansheeX - 30, bansheeY - 4);
  curve(bansheeX, bansheeY + 10, bansheeX - 10, bansheeY - 1, bansheeX - 30, bansheeY - 4, bansheeX - 48, bansheeY - 7);
  curve(bansheeX - 10, bansheeY - 1, bansheeX - 30, bansheeY - 4, bansheeX - 48, bansheeY - 7, bansheeX - 60, bansheeY - 17);

  // Back flipper
  fill(40);
  ellipse(bansheeX - 42, bansheeY + 8, 10, 6);

  // Energy Trail on flipper
  stroke(90, 150, 255, tan(frameCount)*500);
  strokeWeight(bansheeTrailWeight);
  line(bansheeX - 15 - random(bansheeTrailMin, bansheeTrailMax), bansheeY + 8, bansheeX - 45, bansheeY + 8);

  // Green spot
  noStroke();
  fill(90, 200, 100, 150);
  ellipse(bansheeX + 8, bansheeY, 5, 15);
}


void drawMissiles() {
  // MISSILE DRAWING & STUFF
  fill(125);
  noStroke();
  rectMode(CENTER);
  rect(missileX, missileY, missileWidth, missileHeight);
  // Flipper upper back
  triangle(missileX + 13, missileY - 2, missileX + 15, missileY - 8, missileX + 5, missileY - 2);
  // Flipper upper front
  triangle(missileX - 9, missileY - 2, missileX, missileY - 6, missileX, missileY - 2);
  // Flipper lower back
  triangle(missileX + 13, missileY + 2, missileX + 15, missileY + 8, missileX + 5, missileY + 2);
  // Flipper lower front
  triangle(missileX - 9, missileY + 2, missileX, missileY + 6, missileX, missileY + 2);

  // Missile fire
  fill(230, 230, 130, 100);
  // We use again the trick of assigning random values on X to give the fire the effect desired
  // Outer (weaker)
  triangle(missileX + 13, missileY - 4, missileX + 13, missileY + 4, missileX + random(33, 45), missileY);
  // Inner (strongest) flame
  fill(240, 180, 0, 200);
  triangle(missileX + 13, missileY - 2, missileX + 13, missileY + 2, missileX + random(23, 33), missileY);

  // Set missile movement
  missileX = missileX - missileSpeed;
}

void spawmMissile() {
  /* These are just the general 'global' parameters for when the missile spawns, be it by hitting the Banshee
   or just reaching the end of its trajectory.
   The variables just set the parameters in which I want the missile to spawn. It's obviously randommized.
   The random numbers in missileX are arguably not that relevant, but it does make a small difference that I appreciate. */
  missileX = random(missileSpawnXMin, missileSpawnXMax);
  missileY = random(missileSpawnYMin, missileSpawnYMax);
  // Score increases if missile does not collide with Banshee.
  score++;
}

void respawnMissile() {
  // This is just the condition that makes the missile respawn if it goes across withouth hitting the Banshee.
  if (missileX <= -13) {
    spawmMissile();
  }
}


void explosionDraw() {
  /* Draw explosion
   The ellipse are built around bansheeX and bansheeY so its easier to manage their location. When this condition enters in effect
   the explosionSize variable is also changed (increased) to effectively make the circles visible.
   Certain X and Y locations are randommized to make it slightly more ammusing to watch. */
  explosionSize = explosionSize + 1;
  fill(220, 150, 0, 200);
  ellipse(bansheeX, bansheeY, explosionSize + 30, explosionSize + 30);
  ellipse(bansheeX + random(25, 45), bansheeY - random(10, 15), explosionSize, explosionSize);
  ellipse(bansheeX - random(45, 65), bansheeY + random(10, 20), explosionSize, explosionSize);
}


void missileCollition() {
  /* Missile collition condition (MCC) & STUFF
   So this is just checking if the 'missile head' enters the area of the front of the Banshee. 
   If it does, then invisible circles light up to simulate an explosion. */
  if (missileX - 12.5 <= bansheeX + 10 && missileX >= bansheeX - 100 && missileY >= bansheeY - 13 && missileY <= bansheeY + 14) {
    explosionSize = 20;
    while (explosionSize <= 40) {
      /* We lower the framerate on collision to increase the time we can see the explosion, 
       to restrict some movement after collision, and to improve the hit detection. */
      frameRate(15);
      explosionSize = explosionSize + 1;
      explosionDraw();
      // Also stating that when a missile hits the Banshee it will disappear and respawn immediatly, also resets score.
      spawmMissile();
      score = 0;
    }
  }
}