top of page
Search
cedarcantab

Danmaku using Phaser 3 (lesson 25): Bending Bullet Revisited

Updated: Nov 4, 2021

We created danmaku that can fire bullets that fly along a curve in lesson 9. Whilst the code achieved what I wanted to achieve, a lot of limitations came to light as I added new features. For example, you could not have bullets that rotated as they flew along a curve, or you could not have bullets that flew along a waving curve.


In this post we amend the code to overcome these limitations.


Reviewing the existing code

In the existing code, the parameters of the bullet.fire method is as follows:

fire(
    scene,
    {
      // set defaults for the all the acceptable parameters
      x = 0, // origin of bullet spawn
      y = 0, // origin of bullet spawn
      bulletTexture = null, // texture to be used for the bullet image
      bulletFrame = null, // if the texture is a spritesheet, then specify frame to be used
      shootAngle = 0, // direction in which the bullet is fired
      bulletSpeed = 0, // the speed of the bullet
      bulletAcceleration = 0, // acceleration of bullet
      bulletAngle = 0, // this is the angle of the bullet image
      bulletAngularVelocity = 0, // this is the angular velocity of the bullet image
      bulletType = "NORMAL",
      bulletWaveCycle = 0,
      bulletTransform = { type: "NONE" }, // lifepan of bullet in ms. if set to -1, infinite.
      degreesPerTurn = 0,
      bulletTarget = null
    }
  ) {
    // from hereon, the main part of the fire function
    .............

As you can see there are 2 variables relating to angle's: shootAngle and bulletAngle in the parameter set for the fire function (the danmaku object, when calling the bullet.fire function, always sets both the shootAngle and bulletAngle to the cannon angle). The angle of the sprite (and hence the physics body) is set to to shootAngle, and thereafter, the angle property of the sprite is referred to directly and referred to directly in adjusting the velocity direction of the bullet, eg in the bend type. In otherwords, bulletAngle is not used at the moment. In essence, what we need to do is to change the code so that the 2 parameters are used for the respective purposes they were intended for.


Before we start making changes to the code, we will do is to change the name of shootAngle to bulletBearing to more clearly indicate that this variable is intended to specify the direction of bullet's flight.


When we set the physics.body angle, we will do that with reference to the bulletAngle variable, as opposed to shootAngle. And, I have added angularVelocity as a parameter to the explode function so that we can have the exploded fragments rotate as they fly away.


Changing the base unit to radians from degrees


I'm taking this opportunity to change the working unit of bulletAngle and bulletBearing to radians, internally within the bullet class. I will still pass the danmaku the angle parameters all in degrees for simple use, but when they are accepted by the bullet class, I will convert them all into radians. Existing versions of functions like seek() is a jumble of radians and degrees, and is too confusing.


The angle related parameters (specifically bulletBearing, bulletAngle, degreesPerTurn (changed name to perTurnConstraint)) will be converted to radians by the danmaku object, when the bullet.fire method is called.


However, for some reason I don't understand, the method to set the physics body's angularVelocity only works in degrees. So the angularVelocity will remain in degrees.

I have changed the name of degreesPerTurn to perTurnConstraint


The critical function requiring amendment is the adjustVelocity function (called preUpdate, in case of "BEND", "HOMING" and "WAVING" bullet types), which in the current version of the code resets the velocity vector of the bullet's physics body, based on this.angle; i.e. the angle of the physics.body. We change the angle referred to by the adjustVelocity function to this.bulletBearing. In the current version of the code, this.bulletBearing is not adjusted after its initial setting; hence we need a mechanism to adjust that "on-the-fly" for "BEND", "HOMING" and "WAVING". We will tackle these 3 types of bullets in turn, taking the easier ones first.


"WAVING":


We need to change the direction of the velocity to this.bulletBearing + this.swing.getValue(). We will also add a line to change the angle of the bullet image to this angle


"HOMING"


We need to amend the seek function. The current version of the code resets the sprite's body angle directly. We change it so that bulletBearing gets changed.


"BEND"


The existing version of the code simply calls the adjustVelocity function, which resets the velocity of the bullet to the current angle of the physics body. The revised version of the adjustVelocity function resets the velocity based on the current this.bulletBearing. We need a way to adjust this.bulletBearing according to some kind of predetermined "curve angle". For now, we will recreate the existing algorithm, namely setting an "angularVelocity" (i.e. how many degrees to change the angle every second). We will call this bulletBearingVelocity.


At the moment, we use the bulletType to identify bullets that bend. However, so that we can potentially have bullets that "wave" as it flies across a curve, we will assume that a bullet should bend if bulletBearingVelocity contains a value (default value shall be null - meaning it should be treated as a straight flying bullet). This means that we will no longer recognise "BEND" as a valid bulletType.


In terms of mechanism, every 1000ms, bulletBearing needs to be incremented by bulletBearingVelocity, or bulletBearing needs to increase by 360 degrees (or 2PI) every (360/bulletBendVelocity)*1000 milliseconds. With that in mind, we can create a tween as follows.

if (this.bulletBearingVelocity) {
      this.bearingChange = this.scene.tweens.addCounter({
        from: this.bulletBearing,
        to: this.bulletBearing + Math.sign(this.bulletBearingVelocity) * 2 * Math.PI,
        duration: ((2 * Math.PI) / Phaser.Math.DegToRad(Math.abs(this.bulletBearingVelocity))) * 1000,
        repeat: -1
      }); // end of tween set up
    }

The blue lines essentially sets up a number counter which counts from the original bulletBearing to bulletBearing +2PI (or bulletBearing -2PI, if the bearing velocity is negative), thereby "rotating" the direction 360 degrees over the time period specified in red.


Using Radians, refactoring of cannon angle setting code, and other changes


I have finally gotten around to changing the base working unit of angles to radians, by having the danmaku object convert all the angle related variables to radians as soon as they are received (ie the "human" can still pass parameters in degrees, since that is easier to work with). I say all, but I have left the angularVelocity variables that are set to physics bodies in degrees, because for some reason that I do not know, Phaser's setAngularVelocity function works in degrees.


The existing code had a lot of repetitive code that calculated cannon angles (or bullet angles in the case of explode) across an angle. I have refactored that by creating a new function called getArcAngles, as below. I have included a "offset" parameter, just for use by the GAP_CIRCLE danmaku, which requires the angles to be "inverted" as the gap needs to point in the direction of the player.



function getArcAngles(centre, points, angleRange, offset) {
  // this returns array of angles across an arc, in degrees
  const directions =[]
  
    switch (angleRange) {
      case Math.PI * 2:
        for (let i = 0; i < points; i++) {
          directions.push(centre + offset + i * Math.PI * 2 / points);      
        }
        break;
      default:
        for (let i = 0; i < points; i++) {
          directions.push(centre + offset + (i / (points - 1) - 0.5) * angleRange); 
        } // end of for loop where cannon range is less than 2PI (360)
        break;
    }
  return directions
}


There's a lot of other minor changes but that is pretty much it as far as the important changes are concerned.


Desired effect achieved!


This means that we can now have bullets that rotate as they fly along a curve, or wave as they fly along a curve. For example, set the bulletBearingVelocity right enough, waving bullets create the following type of pattern (don't forget to set a finite lifespan).



And that's it! Here is the CodePen for your perusal and comment.











Comments


記事: Blog2_Post
bottom of page