Flocking

back

Source

/* @pjs preload="rocket.png, explosion.png"; */

PImage rocket;
PImage explosion;

int MAX_MISSILES = 10;
Missile[] missiles;

int SMOKE_LIFETIME = 200;
int NUMBER_OF_SMOKE_PARTICLES = 100 * MAX_MISSILES;
int SMOKE_EMISSION_SPEED = 2;
float AVOID_DISTANCE = 30;

Smoke[] smokeParticles;
int currentParticle;

ArrayList explosions = new ArrayList();

// Setup the example
void setup(){
  size(640, 480);
  
  imageMode(CENTER);
  
  smokeParticles = new Smoke[NUMBER_OF_SMOKE_PARTICLES];
  for(int i = 0; i < smokeParticles.length; i++){
    smokeParticles[i] = new Smoke();
  }
  
  // Load images
  rocket = loadImage("rocket.png");
  explosion = loadImage("explosion.png");
  
  missiles = new Missile[MAX_MISSILES];
  for(int i = 0; i < missiles.length;i++){
    missiles[i] = new Missile();
  }
}

// The draw() method is called every frame
void draw(){
  background(#4488cc);
 
  for(int i = 0; i < smokeParticles.length; i++){
    smokeParticles[i].display();
  }
  
  for(int i = 0; i < missiles.length;i++){
    
    // Make each missile steer away from other missiles.
    // This avoidance behavior prevents
    // all of the missiles from bunching up too tightly and following the
    // same track.
    float avoidAngle = 0;
    for(Missile m : missiles){
      // Don't calculate anything if the other missile is me
      if(m.equals(missiles[i]))
        break;

      // Calculate the distance between me and the other missile
      float distance = dist(m.rocket_x, m.rocket_y, missiles[i].rocket_x, missiles[i].rocket_y);
      
      // If the missile is too close...
      if(distance < AVOID_DISTANCE){
        // Chose an avoidance angle of 90 or -90 (in radians)
        avoidAngle = HALF_PI;// zig
        if(random(1) < 0.5) avoidAngle *= -1;// zag
        
        missiles[i].avoidAngle = avoidAngle;
        
        // Already found an avoidAngle so skip the rest
        break;
      }
      
    }
    
    missiles[i].display();
  }
  
  ArrayList deadExplosions = new ArrayList();
  for(Explosion s : explosions){
     s.display();
     if(s.isDead()){
       deadExplosions.add(s);
     }
  }
  for(Explosion s : deadExplosions){
    explosions.remove(s);
  }
}

class Missile{
  float SPEED = 4.5;
  float TURN_RATE = 0.125;
  float WOBBLE_LIMIT = 0.25;
  float WOBBLE_SPEED = 0.75;

  float rocket_x;
  float rocket_y;
  float velocity_x;
  float velocity_y;
  float rotation;
  float avoidAngle;

  // Create a variable called wobble that tweens back and forth between
  // -WOBBLE_LIMIT and +WOBBLE_LIMIT forever
  float wobble = WOBBLE_LIMIT;
  float wobble_direction = 1;

  int emitTimer;
  
  Missile(){
    reset();
  }
  
  void display(){
    // Calculate the angle from the missile to the mouse cursor game.input.x
    // and game.input.y are the mouse position; substitute with whatever
    // target coordinates you need.
    float targetRotation = atan2(mouseY - rocket_y, mouseX - rocket_x);
    
    // Add our "wobble" factor to the targetRotation to make the missile wobble
    targetRotation += wobble;
    targetRotation += avoidAngle;
    avoidAngle = 0;
  
    wobble = lerp(wobble, WOBBLE_LIMIT * wobble_direction, WOBBLE_SPEED);
    
    if(wobble == WOBBLE_LIMIT * wobble_direction)
      wobble_direction *= -1;
    
    // Gradually (this.TURN_RATE) aim the missile towards the target angle
    if(rotation != targetRotation){
      // Calculate difference between the current angle and targetAngle
      float delta = targetRotation - rotation;
      
      // Keep it in range from -180 to 180 to make the most efficient turns.
      if(delta > PI) delta -= TWO_PI;
      if(delta < -PI) delta += TWO_PI;
      
      if(delta > 0){
        // Turn clockwise
        rotation += TURN_RATE;
      }else{
        // Turn counter-clockwise
        rotation -= TURN_RATE;
      }
      
      // Just set angle to target angle if they are close
      if(abs(delta) < radians(TURN_RATE)){
        rotation = targetRotation;
      }
    }
    
    // Calculate velocity vector based on rotation and this.SPEED
    velocity_x = cos(rotation) * SPEED;
    velocity_y = sin(rotation) * SPEED;
    
    rocket_x += velocity_x;
    rocket_y += velocity_y; 
    
    
    emitTimer++;
    if(emitTimer > SMOKE_EMISSION_SPEED){
      emitTimer = 0;
      smokeParticles[currentParticle].reset(rocket_x - velocity_x, rocket_y - velocity_y);
      currentParticle = (currentParticle + 1) % smokeParticles.length;
    }
    
    float distance = dist(rocket_x, rocket_y, mouseX, mouseY);
    if(distance < 50){
      explosions.add(new Explosion(explosion, 4, (int)rocket_x, (int)rocket_y));
      reset();
    }
    
    pushMatrix();
    translate(rocket_x, rocket_y);
    rotate(rotation);
    image(rocket, 0, 0);
    popMatrix();
  }
  
  void reset(){
    rocket_x = random(width);
    rocket_y = height - 16; 
  }
}

class Smoke{
  float x;
  float y;
  float velocity_y;
  int ttl;
  
  void display(){
    if(ttl <= 0)
      return;

    y += velocity_y;
    ttl--;
    pushStyle();
    noStroke();
    fill(255, 255, 255, (int)(255 * ( ttl / (float) SMOKE_LIFETIME)));
    ellipse(x, y, 10, 10);
    popStyle();
  }
  
  void reset(float x, float y){
    this.x = x;
    this.y = y;
    this.velocity_y = random(-0.8, -0.5);
    ttl = SMOKE_LIFETIME;
  }
}

class Explosion{
 PImage[] frames;
 float currentFrame;
 int x;
 int y;
 
 Explosion(PImage img, int noFrames, int x, int y){
  int frameWidth = img.width / noFrames;
  frames = new PImage[noFrames];
  for(int i = 0; i < frames.length; i++){
    frames[i] = img.get(frameWidth * i, 0, frameWidth, img.height);
  }
  this.x = x;
  this.y = y;
 }
 
 void display(){
   currentFrame = (currentFrame + 0.5);
   if(currentFrame < frames.length){
     image(frames[(int)currentFrame], x, y);
   }
 }
 
 boolean isDead(){
   return currentFrame >= frames.length;
 }
}

Warning: Cannot load module "http" because required module "raphf" is not loaded in Unknown on line 0