Search code examples
javascriptcollision-detectiongame-enginegame-physicsphaser-framework

How do I make my arrow stop once they collide with the target?


I am creating a game with JavaScript and the Phaser 3 framework. I have edited the hitbox of my target, but when an arrow hits, it just falls off screen. How do I make the arrow stick as it would in real life?

var physicsConfig = {
  default: 'arcade',
  arcade: {
    debug: false //CHANGE THIS TO TRUE TO SEE LINES
  }
}

var config = {
  type: Phaser.AUTO,
  width: 800,
  height: 600,
  physics: physicsConfig,
  scene: {
    preload: preload,
    create: create,
    update: update,
    render: render
  }
};

//Start the game
var game = new Phaser.Game(config);

function preload() {
  this.load.image('sky', 'assets/sky.png');
  this.load.spritesheet('archer', 'assets/archer_sprites.png', {
    frameWidth: 128,
    frameHeight: 128
  });
  this.load.image('target', 'assets/target.png');
  this.load.image('ground', 'assets/ground.png');
  this.load.image('rings', 'assets/rings.png');
  this.load.image('arrow', 'assets/arrow.png');
  this.load.audio('arrow_shot', 'assets/arrow_shooting.mp3');

}

function create() {
  //Load all the images
  this.add.image(400, 300, 'sky');
  this.add.image(200, 200, 'ground');
  this.add.image(300, 100, 'rings');
  //Create the archer/player
  this.player = this.physics.add.sprite(100, 410, 'archer');
  this.player.setBounce(0.2);
  this.player.setCollideWorldBounds(true);
  //Shooting animation
  this.anims.create({
    key: 'shoot',
    frames: this.anims.generateFrameNumbers('archer', {
      start: 0,
      end: 4
    }),
    frameRate: 20,
    repeat: 0
  });

  //Create the target
  this.target = this.physics.add.sprite(530, 365, 'target');
  this.target.setSize(115, 95).setOffset(70, 130); //TARGET HITBOX
  this.target.enableBody = true;
  this.target.setImmovable();
  //Create an array for arrows later
  this.arrows = [];
  //Get keypresses
  this.cursors = this.input.keyboard.createCursorKeys();
  //Assign input for spacebar
  this.spacebar = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
  //Play sound when the arrow is shot
  this.arrowSound = this.sound.add('arrow_shot');

}

function update() {
  //Declare constants for movement
  const playerMoveAmt = 200;
  const arrowMoveAmt = 1750;
  this.player.setDrag(2000);

  //Rotation of the player
  if (this.cursors.up.isDown && this.player.angle > -45) {
    this.player.angle -= 1;
  }
  if (this.cursors.down.isDown && this.player.angle < 0) {
    this.player.angle += 1;
  }

  //Shooting with the spacebar
  if (Phaser.Input.Keyboard.JustDown(this.spacebar)) {
    //Animate the shooting
    this.player.anims.play('shoot', true);
    //Arrow shooting
    var arrow = this.physics.add.sprite(this.player.x, (this.player.y + 20), 'arrow');
    arrow.enableBody = true;
    arrow.body.immovable = false;
    arrow.setGravityY(4000); //Gravity will affect the arrows
    arrow.angle = this.player.angle //Angle the arrows with the player
    arrow.setVelocityX(arrowMoveAmt);
    arrow.setVelocityY((this.player.angle * 50));
    this.arrows.push(arrow);
    this.arrowSound.play();
  }

}

function render() {}
body {
  margin: 0;
}
<script src="//cdn.jsdelivr.net/npm/[email protected]/dist/phaser.js"></script>

I have tried doing this without the hitbox, and making the arrow's velocity 0 once it passed the target's x-value, but I need to do it properly, with collisions.


Solution

  • I have found a solution to accomplish the behavior that you want:

    • First, we'll check if the target sprite was touched from the left.

    • If the condition evaluates to true, we'll set the arrow sprite gravity & velocity's values to 0;

    You can use this code snippet (it starts after this.arrowSound.play()):

    function update () {   
        //Declare constants for movement
        const playerMoveAmt = 200;
        const arrowMoveAmt = 1500;
        this.player.setDrag(2000); 
    
        //Move the player left or right
        if (this.cursors.right.isDown) 
        this.player.setVelocityX(playerMoveAmt);
        if (this.cursors.left.isDown)
        this.player.setVelocityX(-playerMoveAmt);
    
        //Rotation of the player
        if (this.cursors.up.isDown && this.player.angle > -45) {
            this.player.angle -= 1;}
        if (this.cursors.down.isDown && this.player.angle < 0) {
            this.player.angle += 1;}
    
        //Shooting with the spacebar
        if (Phaser.Input.Keyboard.JustDown(this.spacebar)) {
            //Animate the shooting
            this.player.anims.play('shoot', true);
            //Arrow shooting
            let arrow = this.physics.add.sprite(this.player.x, (this.player.y + 20), 'arrow');
            arrow.enableBody = true;
            arrow.body.immovable = false;
            arrow.setGravityY(4000);    //Gravity will affect the arrows
            arrow.angle = this.player.angle //Angle the arrows with the player
            arrow.setVelocityX(arrowMoveAmt);
            arrow.setVelocityY((this.player.angle * 50));
            this.arrows.push(arrow);
            this.arrowSound.play();
        } else if(this.target.body.touching.left) {
    
            var i;
    
            for (i = 0; i < this.arrows.length; i++) {
              newArrows = this.arrows[i];
    
              newArrows.setGravityY(0);
              newArrows.setVelocityX(0);
              newArrows.setVelocityY(0);
            } 
        }
    }
    

    I would also suggest you to reduce the arrow sprite hitbox's size so it looks like it actually hits the target sprite. You can check the current hitbox's size by setting debug: true like so:

    var physicsConfig = {
        default: 'arcade',
        arcade: {
            debug: true
        }
    }