3

In my code, I have a rocket ship and have added a firing function. I would like to do two things:

  • be able to fire multiple times
  • be able to have the bullets continue in the direction fired (when rotated)

I am guessing I need to somehow separate the rocket animation from the firing animation? Currently, I have the firing animation as a method to the rocket ship object. And the rotation of the rocket ship is connected to the firing animation.

Thank you!

var rocket;
var baseLine;
var rotateValue = 0;

function setup() {
    createCanvas(1000, 600);

    baseLine = height - 50

    rocket = {
        x: width / 2,
        y: baseLine,
        thrust: false,
        fire: false,
        fireX: width / 2,
        fireY: baseLine,
        moveLeft: false,
        moveRight: false,
        rCW: false,
        rCCW: false,
        rotateAngle: 0,
        drawRocket: function() {
            fill(80)
            push();
            if (this.rCW) {
                translate(this.x, this.y);
                rotate(this.rotateAngle);
                rect(10, -90, 10, 100);
            } else if (this.rCCW) {
                translate(this.x, this.y);
                rotate(this.rotateAngle);
                rect(10, -90, 10, 100);
            } else {
                translate(this.x, this.y);
                rotate(this.rotateAngle);
                rect(10, -90, 10, 100);
            }
            pop();

            fill(150, 0, 0)
            push();
            translate(this.x, this.y);
            rotate(this.rotateAngle);
            triangle(10, -90, 15, -120, 20, -90);
            pop();

            fill(150, 0, 0);
            noStroke();
            push();
            translate(this.x, this.y);
            rotate(this.rotateAngle);
            triangle(-10, 10, 10, -20, 10, 10);
            pop();

            push();
            translate(this.x, this.y);
            rotate(this.rotateAngle);
            triangle(20, 10, 20, -20, 40, 10);
            pop();

            if (this.thrust) {
                fill(255, 150, 0);
                noStroke();
                push();
                translate(this.x, this.y);
                rotate(this.rotateAngle);
                triangle(0, 10, 20, 10, 10, 30);
                triangle(5, 10, 25, 10, 15, 35);
                triangle(10, 10, 30, 10, 20, 30);
                pop();

            }

        },
        moveRocket: function() {
            if (this.thrust && this.y > 0 && this.rCW === true) {
                this.y -= 2;
                this.x += 1;
            } else if (this.thrust && this.y > 0 && this.rCCW === true) {
                this.y -= 2;
                this.x -= 1;
            } else if (this.thrust && this.y > 0) {
                this.y -= 2;
            } else if (this.y < baseLine) {
                this.y += 3;
            }

            if (this.moveLeft && this.x > 0 && this.y != baseLine) {
                this.x -= 2;
            }

            if (this.moveRight && this.x < width && this.y != baseLine) {
                this.x += 2;
            }


        },

        fireRocket: function() {
            if (this.fire && this.fireY >= 0) {
                fill(255, 140, 0);
                noStroke();
                ellipse(this.fireX + 5, this.fireY - 10, random(3, 6), random(8, 11));
                ellipse(this.fireX + 25, this.fireY - 10, random(3, 6), random(8, 11));
                if (this.rCW === true) {
                    this.fireY -= 3;
                    this.fireX += 2;
                } else if (this.rCCW === true) {
                    this.fireY -= 3;
                    this.fireX -= 2;
                } else {
                    this.fireY -= 3;
                }

                if (this.fireY <= 1) {
                    this.fireY = baseLine;
                    this.fire = false;
                }

            }
        }

    }
}

function draw() {
    background(10);

    rocket.drawRocket();
    rocket.moveRocket();
    rocket.fireRocket();

}

function keyPressed() {
    if (key == "W") {
        rocket.thrust = true;
    }

    if (key == "A") {
        rocket.moveLeft = true;
    }

    if (key == "D") {
        rocket.moveRight = true;
    }

    if (key == "F") {
        rocket.fireX = rocket.x;
        rocket.fireY = rocket.y;
        rocket.fire = true;
    }

    if (key == "R") {
        rocket.rCW = true;
        rocket.rotateAngle = PI / 4;
    }

    if (key == "E") {
        rocket.rCCW = true;
        rocket.rotateAngle = -PI / 4;
    }
}

function keyReleased() {
    if (key == "W") {
        rocket.thrust = false;
    }

    if (key == "A") {
        rocket.moveLeft = false;
    }

    if (key == "D") {
        rocket.moveRight = false;
    }


    if (key == "R") {
        rocket.rCW = false;
        rocket.rotateAngle = 0;
    }


    if (key == "E") {
        rocket.rCCW = false;
        rocket.rotateAngle = 0;
    }

}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>
George Profenza
  • 50,687
  • 19
  • 144
  • 218

1 Answers1

3

Quickly scanning through your code I see there's a single fire coordinates pair. What you want is a separate fire(bullet) class so that instances of this class can update independently. You'll need an array to store the bullets. When the rocket fires, a new bullet is instantiated at the rocket's position, but from then on, each bullet's velocity updates the bullet position :

var rocket;
var missiles = [];
var baseLine;
var rotateValue = 0;

class Missile{
  constructor(x, y, vx, vy){
    this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
  }
  update(){
    this.x += this.vx;
    this.y += this.vy;
    fill(255, 140, 0);
    noStroke();
    ellipse(this.x + 5, this.y - 10, random(3, 6), random(8, 11));
    ellipse(this.x + 25, this.y - 10, random(3, 6), random(8, 11));
  }
}

function setup() {
    createCanvas(1000, 600);

    baseLine = height - 50

    rocket = {
        x: width / 2,
        y: baseLine,
        vx: 0,
        vy: 0,
        thrust: false,
        fire: false,
        fireX: width / 2,
        fireY: baseLine,
        moveLeft: false,
        moveRight: false,
        rCW: false,
        rCCW: false,
        rotateAngle: 0,
        drawRocket: function() {
            fill(80)
            push();
            if (this.rCW) {
                translate(this.x, this.y);
                rotate(this.rotateAngle);
                rect(10, -90, 10, 100);
            } else if (this.rCCW) {
                translate(this.x, this.y);
                rotate(this.rotateAngle);
                rect(10, -90, 10, 100);
            } else {
                translate(this.x, this.y);
                rotate(this.rotateAngle);
                rect(10, -90, 10, 100);
            }
            pop();

            fill(150, 0, 0)
            push();
            translate(this.x, this.y);
            rotate(this.rotateAngle);
            triangle(10, -90, 15, -120, 20, -90);
            pop();

            fill(150, 0, 0);
            noStroke();
            push();
            translate(this.x, this.y);
            rotate(this.rotateAngle);
            triangle(-10, 10, 10, -20, 10, 10);
            pop();

            push();
            translate(this.x, this.y);
            rotate(this.rotateAngle);
            triangle(20, 10, 20, -20, 40, 10);
            pop();

            if (this.thrust) {
                fill(255, 150, 0);
                noStroke();
                push();
                translate(this.x, this.y);
                rotate(this.rotateAngle);
                triangle(0, 10, 20, 10, 10, 30);
                triangle(5, 10, 25, 10, 15, 35);
                triangle(10, 10, 30, 10, 20, 30);
                pop();

            }

        },
        moveRocket: function() {
            if (this.thrust && this.y > 0 && this.rCW === true) {
                this.y -= 2;
                this.x += 1;
                this.vy = -2;
                this.vx  = 1;
            } else if (this.thrust && this.y > 0 && this.rCCW === true) {
                this.y -= 2;
                this.x -= 1;
                this.vy = -2;
                this.vx  = -1;
            } else if (this.thrust && this.y > 0) {
                this.y -= 2;
                this.vy = -2;
            } else if (this.y < baseLine) {
                this.y += 3;
                this.vy = 2;
            }

            if (this.moveLeft && this.x > 0 && this.y != baseLine) {
                this.x -= 2;
                this.vx = -2;
            }

            if (this.moveRight && this.x < width && this.y != baseLine) {
                this.x += 2;
                this.vx = 2;
            }


        },

        fireRocket: function() {
            if (this.fire && this.fireY >= 0) {
                fill(255, 140, 0);
                noStroke();
                ellipse(this.fireX + 5, this.fireY - 10, random(3, 6), random(8, 11));
                ellipse(this.fireX + 25, this.fireY - 10, random(3, 6), random(8, 11));
                if (this.rCW === true) {
                    this.fireY -= 3;
                    this.fireX += 2;
                } else if (this.rCCW === true) {
                    this.fireY -= 3;
                    this.fireX -= 2;
                } else {
                    this.fireY -= 3;
                }

                if (this.fireY <= 1) {
                    this.fireY = baseLine;
                    this.fire = false;
                }

            }
        }

    }
}

function draw() {
    background(10);

    rocket.drawRocket();
    rocket.moveRocket();
    //rocket.fireRocket();
    // update missiles
    for(let i = 0 ;  i < missiles.length; i++){
      missiles[i].update();
    }

}

function keyPressed() {
    if (key == "W") {
        rocket.thrust = true;
    }

    if (key == "A") {
        rocket.moveLeft = true;
    }

    if (key == "D") {
        rocket.moveRight = true;
    }

    if (key == "F") {
        //rocket.fireX = rocket.x;
        //rocket.fireY = rocket.y;
        //rocket.fire = true;
        missiles.push(new Missile(rocket.x, rocket.y, rocket.vx, rocket.vy));
        console.log(missiles);
    }

    if (key == "R") {
        rocket.rCW = true;
        rocket.rotateAngle = PI / 4;
    }

    if (key == "E") {
        rocket.rCCW = true;
        rocket.rotateAngle = -PI / 4;
    }
}

function keyReleased() {
    if (key == "W") {
        rocket.thrust = false;
    }

    if (key == "A") {
        rocket.moveLeft = false;
    }

    if (key == "D") {
        rocket.moveRight = false;
    }


    if (key == "R") {
        rocket.rCW = false;
        rocket.rotateAngle = 0;
    }


    if (key == "E") {
        rocket.rCCW = false;
        rocket.rotateAngle = 0;
    }

}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>

This is a bit hacky: ideally you'd want to update the velocity on keyPressed and simply reuse it to add it to the current position.

Unfortunately I can't go into many details at this point in time, however I'd advise simplifying the code and getting the hang of p5.Vector/Daniel Shiffman's Nature of Code Vector math videos and a bit of trigonometry will help.

Here's a super basic example that you could adapt:

class Rocket{
  
  constructor(x, y){
    this.position = createVector(x, y);
    this.velocity = createVector();
    this.thrust = 0;
    this.angle = 0;
    this.friction = 0.1;
  }
  
  update(){
    // use polar to cartesian coordinate conversion formulate apply direction angle and thrust (radius)
    // -HALF_PI angle offset is because 0 degrees points to the right when it comes to rendering and we want our ship pointing up
    this.velocity.set(cos(this.angle - HALF_PI) * this.thrust,
                      sin(this.angle - HALF_PI) * this.thrust);
    // add velocity to position
    this.position.add(this.velocity);
    // render
    push();
    // remember: order of transformations is important
    translate(this.position.x, this.position.y);
    rotate(this.angle);
    triangle(0 , -10,
             -5,  10,
             +5, 10);
    pop();
  }
  
}

class Laser{
  constructor(position, velocity, angle){
    this.position = position;
    this.velocity = velocity;
    // angle is simply copied so it can be reused for rendering without having to re-calculate using this.velocity.heading()
    this.angle = angle;
    // keep direction, but change speed
    this.velocity.normalize();
    this.velocity.mult(3);
  }
  
  update(){
    this.position.add(this.velocity);
    push();
    translate(this.position.x, this.position.y);
    rotate(this.angle);
    line(0, -5,
         0,  5);
    
    pop();
  }
  
}

let rocket;
// store lasers shot
let lasers = [];

function setup() {
  createCanvas(300, 300);
  rocket = new Rocket(width / 2, height / 2);
}

function draw() {
  // smooth controls in draw
  if(keyIsPressed){
    // change angle and thrust
    if (keyCode === UP_ARROW) {
      rocket.thrust = 0.3;
    }
    if (keyCode === LEFT_ARROW) {
      rocket.angle -= radians(1);
    }
    if (keyCode === RIGHT_ARROW) {
      rocket.angle += radians(1);
    }
  }
  
  background(255);
  rocket.update();
  updateLasers();
  text("use arrow keys to drive ship\nuse SPACE to shoot", 10, 15);
}

function updateLasers(){
  for(let i = 0 ; i < lasers.length; i++){
    lasers[i].update();
  }
}

function keyPressed(){
  // if space is pressed and the ship is moving
  if(key == ' ' && rocket.velocity.mag() > 0){
    // copy the ships position and velocity as well as the pre-computed rotation angle to the newly shot laser
    // ...and append it to the lasers array
    lasers.push(new Laser(rocket.position.copy(), 
                          rocket.velocity.copy(),
                          rocket.angle));
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>

This isn't perfect: there are many things to improve (such has handling the ship going offscreen, marking the lasers as on screen or off screen to re-use and avoid slowdowns the more lasers are shot, etc), however the intention is to illustrate decoupling the fire/laser functionality as a separate class. Firing will produce new instances which are managed in an array. Hopefully the point gets across.

(You might also find a few handy tips in this answer)

Update For fun, here's a version with a few of the suggested tweaks (e.g. wrapping around border, slowing down, etc.):

class Rocket{
  
  constructor(x, y){
    this.position = createVector(x, y);
    this.velocity = createVector();
    this.thrust = 0;
    this.angle = 0;
    this.friction = 0.1;
    this.halfHeight = 10;
    this.width = 5;
  }
  
  update(){
    // use polar to cartesian coordinate conversion formulate apply direction angle and thrust (radius)
    // -HALF_PI angle offset is because 0 degrees points to the right when it comes to rendering and we want our ship pointing up
    this.velocity.set(cos(this.angle - HALF_PI) * this.thrust,
                      sin(this.angle - HALF_PI) * this.thrust);
    // add velocity to position
    this.position.add(this.velocity);
    // wrap around borders
    if(this.position.x > width) this.position.x = 0;
    if(this.position.y > height) this.position.y = 0;
    if(this.position.x < 0) this.position.x = width;
    if(this.position.y < 0) this.position.y = height;
    // render
    push();
    // remember: order of transformations is important
    translate(this.position.x, this.position.y);
    rotate(this.angle);
    triangle(0 , -this.halfHeight,
             -this.width,  this.halfHeight,
             +this.width,  this.halfHeight);
    let mag = this.velocity.mag();
    let tail = map(sin(frameCount), -1.0, 1.0,0, 9 * mag);
    triangle(-this.width * .3, this.halfHeight,
              this.width * .3, this.halfHeight,
              0              , this.halfHeight + tail);
    pop();
  }
  
}

class Laser{
  constructor(position, velocity, angle){
    this.position = position;
    this.velocity = velocity.copy();
    // angle is simply copied so it can be reused for rendering without having to re-calculate using this.velocity.heading()
    this.angle = angle;
    // keep direction, but change speed
    this.velocity.normalize();
    this.velocity.mult(3);
  }
  
  update(){
    this.position.add(this.velocity);
    push();
    translate(this.position.x, this.position.y);
    rotate(this.angle);
    line(0, -5,
         0,  5);
    
    pop();
  }
  
}

let rocket;
// store lasers shot
let lasers = [];

function setup() {
  createCanvas(300, 300);
  rocket = new Rocket(width / 2, height / 2);
}

function draw() {
  // smooth controls in draw
  if(keyIsPressed){
    const acceleration = 0.01;
    // change angle and thrust
    switch(keyCode) {
      case UP_ARROW:
        rocket.thrust += acceleration;
        if(rocket.thrust > 1.5) rocket.thrust = 1.5;
        break;
      case DOWN_ARROW:
        rocket.thrust -= acceleration;
        if(rocket.thrust < 0) rocket.thrust = 0;
        break;
      case LEFT_ARROW:
        rocket.angle -= radians(1);
        break;
      case RIGHT_ARROW:
        rocket.angle += radians(1);
        break;
    }
  }
  
  background(255);
  rocket.update();
  updateLasers();
  text("use arrow keys to drive ship\nuse SPACE to shoot", 10, 15);
}

function updateLasers(){
  for(let i = 0 ; i < lasers.length; i++){
     lasers[i].update();
  }
}

function keyPressed(){
  // if space is pressed and the ship is moving
  if(key == ' ' && rocket.velocity.mag() > 0){
    // copy the ships position and velocity as well as the pre-computed rotation angle to the newly shot laser
    // ...and append it to the lasers array
    lasers.push(new Laser(rocket.position.copy(), 
                          rocket.velocity.copy(),
                          rocket.angle));
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js" integrity="sha512-N4kV7GkNv7QR7RX9YF/olywyIgIwNvfEe2nZtfyj73HdjCUkAfOBDbcuJ/cTaN04JKRnw1YG1wnUyNKMsNgg3g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
George Profenza
  • 50,687
  • 19
  • 144
  • 218