Search code examples
processingcollision-detectioncollisiondetection

Need assistance with bullet to enemy interaction in Processing 3


I am writing a game for my programming class. It is a plane shooter game that has other planes coming in as "enemies". The plane that shoots shoots little ellipses. I cannot figure out how to have the enemy that was hit removed from the game. I know how to remove the "enemy" but not how to make that happen when they collide. Here is my current code.

    ArrayList <Bullet> bullets = new ArrayList <Bullet> ();
    ArrayList enemies;
    PVector player, playerSpeed;
    float maxSpeed = 3;
    PImage jet;
    PImage enemy;
    PImage laser;
    void setup() {
      size(600, 600);
      player = new PVector(300, 550);
      playerSpeed = new PVector();
      noCursor();
      noStroke();
      smooth();
      String jeturl = "http://s1.postimg.org/dhe38w1rv/fighter_jet_md_20.png";
      String enemyurl =                "http://s29.postimg.org/cdaj0d7z7/fighter_jet_md_20.png";
      String laserurl = "http://s13.postimg.org/fq00vsl37/red_Laser_Ray.png";
      // Load image from a web server
      jet = loadImage(jeturl, "png");
      enemy = loadImage(enemyurl, "png");
      laser = loadImage(laserurl, "png");
      enemies = new ArrayList();
    }

    void draw() {
      background(255);

      player.add(playerSpeed);
      //fill(255, 0, 0);
      image(jet, player.x, player.y);

      PVector mouse = new PVector(mouseX, mouseY);
              fill(10);
      ellipse(mouse.x, mouse.y, 5, 5);

      if (frameCount%7==0 && mousePressed) {
        PVector dir = PVector.sub(mouse, player);
        dir.normalize();
        dir.mult(maxSpeed*3);
        Bullet b = new Bullet(player, dir);
        bullets.add(b);
      }
     for(int i = enemies.size()-1; i>=0; i--) {
        Enemy b = (Enemy) enemies.get(i);
        b.move();
        b.draw();



      }

      if (frameCount%50==0) {
        enemies.add(new Enemy());


      }

      for (Bullet b : bullets) {
        b.update();
        b.display();
      }




    }

    class Bullet extends PVector {
      PVector vel;

      Bullet(PVector loc, PVector vel) {
        super(loc.x, loc.y);
        this.vel = vel.get();
      }

      void update() {
        add(vel);
      }

      void display() {
        fill(0, 0, 255);
        ellipse(x, y, 3, 3);
      }
    }

    class Enemy {
      float x, y;
      Enemy() {
            x = random(20, 580);
            y = random(-20, -580);

      }

      void move() {
        y = y + random(1,3);

      }

      void draw() {
        image(enemy, x, y);

      }
    }

    void keyPressed() {

      if (keyCode == LEFT)  { 

      playerSpeed.x = -maxSpeed; 

       }
      if (keyCode == RIGHT) { 

      playerSpeed.x = maxSpeed;

       }
    }

    void keyReleased() {

      if (keyCode == LEFT || keyCode == RIGHT) { 
      playerSpeed.x = 0; 
    }
    }

Solution

  • You know the coordinates of both the bullets and enemies, therefore you can check for collision. There is a built in dist() function which calculates the Euclidean distance between two points.

    Although not extremely efficient, nor accurate, you can do something like this:

    ArrayList <Bullet> bullets = new ArrayList <Bullet> ();
    ArrayList enemies;
    PVector player, playerSpeed;
    float maxSpeed = 3;
    
    PImage jet;
    PImage enemy;
    PImage laser;
    
    int lives = 3;
    int score = 0;
    
    void setup() {
      size(600, 600);
      player = new PVector(300, 550);
      playerSpeed = new PVector();
      noCursor();
      noStroke();
      smooth();
      String jeturl = "http://s1.postimg.org/dhe38w1rv/fighter_jet_md_20.png";
      String enemyurl = "http://s29.postimg.org/cdaj0d7z7/fighter_jet_md_20.png";
      String laserurl = "http://s13.postimg.org/fq00vsl37/red_Laser_Ray.png";
      // Load image from a web server
      jet = loadImage(jeturl, "png");
      enemy = loadImage(enemyurl, "png");
      laser = loadImage(laserurl, "png");
      enemies = new ArrayList();
    
    
    }
    
    void draw() {
      background(255);
    
      player.add(playerSpeed);
      //fill(255, 0, 0);
      image(jet, player.x, player.y);
    
      PVector mouse = new PVector(mouseX, mouseY);
      fill(10);
      ellipse(mouse.x, mouse.y, 5, 5);
    
      if (frameCount%7==0 && mousePressed) {
        PVector dir = PVector.sub(mouse, player);
        dir.normalize();
        dir.mult(maxSpeed*3);
        Bullet b = new Bullet(player, dir);
        bullets.add(b);
      }
      for (int i = enemies.size ()-1; i>=0; i--) {
        Enemy b = (Enemy) enemies.get(i);
        b.move();
        b.draw();
        //check enemy to bullet collisions
        //for each bullet
        for(Bullet blt : bullets) {
          //if the distance between the bullet and enemy is less than the enemy's width
          if(dist(blt.x,blt.y,b.x,b.y) < enemy.width){
            //remove the enemy
            enemies.remove(b);
            //optional, add score
            score += 10;
          }
        }
        //check enemy to player collision
        if(dist(player.x,player.y,b.x,b.y) < jet.width){
          enemies.remove(b);//remove current enely
    
          //optional, update lives, reset score/game if needed
          lives--;
          println("player hit: " + lives + " lives left");
    
          if(lives < 1){
            println("Game Over!\nscore:" + score);
            lives = 3;
            score = 0;
          }
    
        }
      }
    
      if (frameCount%50==0) {
        enemies.add(new Enemy());
      }
    
      for (Bullet b : bullets) {
        b.update();
        b.display();
      }
    }
    
    class Bullet extends PVector {
      PVector vel;
    
      Bullet(PVector loc, PVector vel) {
        super(loc.x, loc.y);
        this.vel = vel.get();
      }
    
      void update() {
        add(vel);
      }
    
      void display() {
        fill(0, 0, 255);
        ellipse(x, y, 3, 3);
      }
    }
    
    class Enemy {
      float x,y;
    
      Enemy() {
        x = random(20, 580);
        y = random(-20, -580);
      }
    
      void move() {
        y = y + random(1, 3);
      }
    
      void draw() {
        image(enemy, x, y);
      }
    }
    
    void keyPressed() {
    
      if (keyCode == LEFT) { 
    
        playerSpeed.x = -maxSpeed;
      }
      if (keyCode == RIGHT) { 
    
        playerSpeed.x = maxSpeed;
      }
    }
    
    void keyReleased() {
    
      if (keyCode == LEFT || keyCode == RIGHT) { 
        playerSpeed.x = 0;
      }
    }
    

    The reason the above code is not accurate is because it's checking collisions based on distance. Imagine a circle around a checked object. If your objects would be circles this would've been perfect, but you have mostly rectangular shapes (PImages).

    The reason the above code is slow has a bit to do with how the dist() function works. Imagine a line between two objects on screen. Drawing two straight lines from one object to another constrained to horizontal and vertical only will give you the two sides of a right angle triangle, where the original line connecting the objects is the hypothenuse. To find the distance, you would solve for hypothenuse which is the square root of the addition of the two sides. You don't have to worry too much about this, as dist() does this math for you, but keep in mind it's using square root which can be costly on the CPU. One way to get around this is to used the squared distance (and check against the radius squared), but this wouldn't be too accurate.

    Another option is to do simple rectangular bound checks. You know where each box (PImage) is on screen and where each bullet is drawn, therefore you can workout if each bullet is within an enemy. Similarly, you can check if two rectangles intersect (player PImage and enemy PImage with updated coordinates).

    Here's a minimal example for rectangle intersection, taking advantage of the java.awt.Rectangle class (which already provides a check for intersections):

    import java.awt.Rectangle;
    
    //based on jet image from previous code
    int jetWidth = 20;
    int jetHeight = 28;
    
    Rectangle playerBox,enemyBox;
    
    void setup(){
      size(600, 600);
      noFill();strokeWeight(3);
      //bounding boxes
      playerBox = new Rectangle(width / 2, height-jetHeight*2,jetWidth,jetHeight);
      enemyBox = new Rectangle((int)random(width),-jetHeight,jetWidth,jetHeight);
    }
    
    void draw(){
      //update enemy
      enemyBox.y += 3;
      //reset enemy position if out of screen
      if(enemyBox.y > height) {
        enemyBox.y = -jetHeight;
      }
      playerBox.x = (int)(mouseX - jetWidth * .5);
    
      //check collision
      if(playerBox.intersects(enemyBox)){
        strokeWeight(10);
      }else{
        strokeWeight(3);
      }
    
      background(255);
      stroke(192,0,0);
      rect(enemyBox.x,enemyBox.y,enemyBox.width,enemyBox.height);
      stroke(0,192,0);
      rect(playerBox.x,playerBox.y,playerBox.width,playerBox.height);
    }
    

    If you want pixel accuracy, you can take advantage of the alpha channel of the images you load and:

    1. initially do the bounding box test
    2. if (and only if) the bound boxes are intersecting, loop through the non transparent pixels of one image to check against the non transparent pixels of the other). (If you know the direction of objects, you can use that to start counting the pixels closer to a possible collision first)

    As next steps, I'd recommend implementing the collision detection based on bounding boxes. Later you might want to look into basic state machines to keep track of the game's state(e.g. intro screen, level1, level2, ..., game over screen, high score screen, etc.)

    Have fun! :)