top of page
Search
  • cedarcantab

Danmaku using Phaser 3 (lesson 9): Bending Bullets

Updated: Oct 21, 2021

The Spiral danmaku created in lesson2 and the Multiple Spiral danmaku created in lesson 6 both gave the impression that the bullets are flying along a curve. In fact, as you would know from code, the bullets themselves are flying in a straight line. It is that successive bullets are fired at slightly different directions, making the stream of bullets 'bend'.


What if the bullets did actually fly along a curve? That is the subject of this post - bending bullets.



The key to making the bullets fly along a curve as opposed to a straight line is of course in the Bullet object class.


The original Bullet Class has had quite a few changes made since the original version, particularly some of its variables names have been changed over its various iterations. For clarity, I have pasted the current version of the main Bullet object code below.


class Bullet extends Phaser.Physics.Arcade.Image {
  fire(config) {
    // set some basic properties of body and game object when first fired
    this.enableBody(true, config.x, config.y, true, true);
    this.body.collideWorldBounds = true;
    this.body.onWorldBounds = true;
    this.setTexture(config.bulletType); // set the texture of the bullet

    // initialise various attributes for the bullet when first fired
    this.shootAngle = config.shootAngle || 0; // this is the direction in which the bullet should shoot out at.
    this.setAngle(this.shootAngle); // the initial angle of the bullet image should be the shoot angle
    
    this.body.setAngularVelocity(config.bulletAngleVelocity || 0); // this is the angular velocity of the bullet image
    this.bulletSpeed = config.bulletSpeed || 300; // this is the speed of bullet travel. default set to 300.
    this.body.speed = this.bulletSpeed; // it's necessary to manually set speed of body otherwise sometimes pre-update sets the velocity to zero, before the bullet gets going.
    this.bulletAcceleration = config.bulletAcceleration || 0; // this is the acceleration of bullet. defaults is 0.
    this.bendBullet = config.bendBullet;
    // when first fired, set the velocity (vector) of the bullet in accordance with the speed (bulletSpeed) and direction (shootAngle)
    this.scene.physics.velocityFromAngle(
      this.angle,
      this.bulletSpeed,
      this.body.velocity
    );
    // when first fired, set the acceleration vector of the bullet in accordance with the acceleration passed as parameter and direction (shootAngle)
    this.scene.physics.velocityFromAngle(
      this.angle,
      this.bulletAcceleration,
      this.body.acceleration
    );
  
  } // end of fire function

  preUpdate(time, delta) {
    if (!this.bendBullet) {return}
    
    this.scene.physics.velocityFromAngle(
      this.angle,
      this.body.speed,
      this.body.velocity
    );
    this.scene.physics.velocityFromAngle(
      this.angle,
      this.bulletAcceleration,
      this.body.acceleration
    );
  } // end of bending bullet specific preUpdate function
  
  onWorldBounds() {
    this.disableBody(true, true);
  } // function to disable bullet when outside of screen

} // end of Bullet class

When the bullet first fired, the sprite is 'rotated', or in Phaser speak, the 'angle' property of the Body is set in the direction of that the cannon is facing (bulletAngle). The velocity (i.e. a 2D vector facing the direction, whose magnitude is body.speed) is initially calculated when the bullet object is activated by using the this.scene.physics.velocityFromAngle method. This makes the bullet fly off in the right direction...in a straight line.


So making the bullet fly along a curve path would involve changing the 'direction' of the velocity vector as the bullet flies. Put another way if we could re-calculate the velocity vector at regular intervals during the bullet's flight with adjusted direction, that would do the trick. We could of course do this by manually keeping track of the "direction", adjust it and recalculate the velocity vector of the bullet, say every frame by calling it from the main update function. However, I would like Phaser to do as much of the work as possible.


In lesson 3, we used the "angle" property of the bullet object to make the bullet itself rotate as it flew. Phaser's physics engine took care of the calculations involved in rotating the bullet 'image' around its centre, by adjusting the body's 'angle' property, taking into account the 'body.angularVelocity'. The flight path of the bullet was straight though. What if used the 'angle' property of the sprite as the 'direction' of the bullet? All we have to do then, is to call the "this.scene.physics.velocityFromAngle" method regulatory based on the 'angle' property of the sprite. Of course, we can do this from the main update function. However, there is a way to achieve the same effect by using something called the preUpdate function.


The preUpdate function, if inserted in a extended sprite class, is executed every frame, just like the update function. All we need to change from the 'vanilla' Bullet object code is to add the code to recalculate the velocity using the latest 'angle' property. Everything else can remain the same. As such, we can extend the Bullet class, like below:


preUpdate(time, delta) {
    if (!this.bendBullet) {return}
    
    this.scene.physics.velocityFromAngle(
      this.angle,
      this.body.speed,
      this.body.velocity
    );
    this.scene.physics.velocityFromAngle(
      this.angle,
      this.bulletAcceleration,
      this.body.acceleration
    );
  } // end of bending bullet specific preUpdate function

The preUpdate function is called every frame. So each frame, the purple piece of code recalculates the velocity vector of the bullet by reference to the angle of the bullet, which in turn is adjusted automatically by the physics engine according to body.angularVelocity. The code in orange does something similar to the acceleration vector.


In fact, the above piece of code is relatively straightforward but what took me a very long time sort out is the bold highlighted text in the main constructor function, in particular the line which sets the body.speed. This is the magnitude of the velocity vector of the physics body, and is set in the latter part of the constructor function. I kept finding that for some reason, some bullets would get fired and just sit there, without moving! It is probably something to do with the preUpadte function firing before the constructor function was complete, and hence setting the bullet velocity with speed of zero. I would have thought it would get reset once the constructor function is completed. It is a very odd phenomenon. Anyway, once inserted the code to manually set the body.speed, the problem went away.


And that is it.


If we want bullets to bend, then we include the following when we instantiate the danmaku object.

bendBullet: true,

This new property needs to be read by the danmaku object construction function, and passed to the bullet object when it is shot via the fireweapon function.


Setting the parameters


The danmaku pictured at the top looks just like a standard rotating nway with straight flying bullets in a still picture, but it does look different when moving - you can see the key parameters settings in the picture itself.


Interestingly, by choosing the right combination of bullet speed, time between shots, cannon rotation speed and the strength of bullet "bend", you can get an nway with straight arms that rotate. Again, the still picture looks like a still nway - please do look at moving, by setting the parameters yourself in the code as pictured below.



The entire code (the one with only one Bullet object) is available as a CodePen Pen below, for your perusal.



記事: Blog2_Post
bottom of page