top of page
Search
  • cedarcantab

Danmaku using Phaser 3 (lesson 39): DRAW re-re-revisted

Updated: Nov 28, 2021


Having recoded the entire nway/multi-nway code and the PAINT code to allow firing of PAINT pictures off multi-nways, I set about doing the same with the DRAW danmaku.


Once again, the work has been mostly to do with making sure that the code works with the fundamental changes made to the nway danmaku but I have also added a few new functionalities (and with it, new parameters). One of those new functionalities is to allow DRAW pictures to "disintegrate" in combination with STOP&GO bullets; i.e. move for a while keeping its shape, then disintegrate.



Changing of parameter names

First things first. I had to change names of existing parameters because with the change in the nway/multi-nway danmaku code, some of the parameters used for "DRAW" danmaku clashed. In other cases I had to create new names for new options, and in some cases the names were confusing (for myself).


As a reminder (to myself), the parameters that resulted in the "clash" (since in the previous version of the code, "DRAW" was never intended to be fired in conjunction with multi-nway or parallel cannons) were:

  • danmakuConfig.countA - define the number of cannons in each nway

  • danmakuConfig.countB - define the number of cannons in each "PARALLEL"

  • danmakuConfig.multiple - define number of nways for multi-nway

The key new parameters are:

  • cannonConfig.cannonCount: used to define the number of bullets (points) to draw each picture, as the danmamuConfig.count (which is now countA and countB) are already used.

  • danmakuConfig.paramA - use to determine number of vertices in polygon. Previously it was danmakuConfig.size

  • danmakuConfig.paramB - currently not used

  • danmakuConfig.size: used to define number of vertices in a polygon in "DRAW"

  • cannonConfig.size is now used to determine the size of the picture (before , used to set width and height separately. this was confusing and unnecessary).

I have changed the parameter to set the shots in one "spread" shot to cannonConfig.spreadCount as it was too confusing.


Changing the structure of the "DRAW" danmaku

In the previous version of the code, everytime setProperties method is called, createPicture function is called which creates the relevant Curves.Path object (STAR, SPADE etc.), and this object is assigned to this.danmakuPicture (createPicture function is also called by the explode function).


Then from fireShot function, the cannonsFromCurve function is called with relevant parameters including the Curves.Path object that has been created.


The first thing cannonsFromCurve does is to call the getCannonSpokes function. The code has now been restructured such that getSpokes function is called directly from the fireShot function, then the cannonsFromCurve functino (without the calling getCannonSpokes code) is called.


With all the parameters for getCannonSpokes function aligned with the other danmakuClass (in fact, there is now only DRAW and mutli-nway danmaku), the fireShot function can be greatly simplified by changing "DRAW" to a cannon shot class as opposed to a danmaku. In otherwords, in the fireShot function, the getCannonSpokes function is first called to set up the angles and positions of the cannons, then the relevant cannoshot class is called. In the previous version of the code, the SetUpCannons function dealt with the following cannonShotTypes:

  1. "NORMAL"

  2. "OVERTAKE"

  3. "SPREAD"

  4. "PAINT"

  5. "DRAW" is now a cannonConfig.type.


I have spun off the "PAINT" code to a separate function from setUpCannons and called it paintCannons, since including this code within setUpCannons was not really factoring anything, just cramming lots of different code into one function. The new paintCannons function looks as follows. The algorithm is pretty much the same except more higher order functions have been used to make it easier to read.


function paintCannons({
  spokesArray,
  originsArray,
  centre,
  bulletSpeed, 
  counter, 
  picture,
  pAngle,
  pAngleRange,
  pWidth = 100, // width for KeepShape pictures
  keepShape = false,
  anglesArray,
  pointsArray,
  speedArray
})
{
  const speeds = [];
  const textLine = picture[counter]
  const count = textLine.length;
  const line = createLine(0, pWidth);
  spokesArray.forEach((angle,j)=> {
    const realOrigin = (originsArray.length === 0) ? centre : originsArray[j].add(centre); 
    Array.from(textLine).forEach((char,i)=>{
      if (char !== " ") {
        switch (keepShape) {
          case true:
            const point = line.getPoint(i / (count - 1)).rotate(angle).add(realOrigin);
            pointsArray.push(point);
            anglesArray.push(angle);
            break;
          default:
            anglesArray.push(angle + pAngleRange * (0.5 - i / (count - 1)));
        } // end of switch statement
        speeds.push(bulletSpeed);
      }
    })
  })
  speedArray.push(speeds);
} // end of paintCannons function

So the fireShot function now contains a switch statement which calls the relevant functions depending on the cannonShotClass as opposed to danmakuClass. I have also "relegated" the "SPREAD" and "OVERTAKE" cannonConfig.type to cannonConfig.shotType to distinguish them from the likes of DRAW pictures (ie cannonConfig.type is used to define what picture is drawn, like "SPADE", "CLUB" etc.).


Revised cannonsFromCurve function

The revised code is as follows. The basic logic remains the same but I have fixed a bug which did not allow pictures to "implode" properly. In particular, in the previous version of the code, danmakuConfig.option = this.danmakuOption = option in the below, was set to zero to make pictures fly across the screen. However, in the new version of the code, danmakuConfig.switch = this.danmakuSwitch = emerge should be set to zero (as opposed to 1 for expanding picture, and -1 for imploding picture) to make pictures fly across the screen.


function drawCannons({
  curve,
  angleIndex,
  originIndex,
  size=0,
  centre, // this is the origin of the danmaku
  numberOfPoints,
  speed,
  bearingOption = 1,
  pRotation,
  keepShape = true,  
  emerge = 1, // 1: explode, -1: implode
  open = false, 
  anglesArray,
  speedArray,
  cannonPositions,
  flyAwayOption = 0,
  flyAwayAngles = []
})
{
  const aspectRatio = new Phaser.Math.Vector2(size/2, size/2);
  const speeds = []; 
  angleIndex.forEach((angle, j) => {
    const realOrigin = (originIndex.length === 0) ? centre : originIndex[j].add(centre); 
    let angleT = 0;
    for (let i = 0; i < numberOfPoints; i++) {      
      const point = curve.getPoint(i / (numberOfPoints - 1 * open)).rotate(Math.PI/2 + (pRotation === undefined ? angle : pRotation)); 
      if (keepShape) {
        switch (emerge) {
          case 0:
            anglesArray.push(angle);
            speeds.push(speed)
            break;
          default:
            anglesArray.push(emerge === 1 ? Phaser.Math.Angle.BetweenPoints(Phaser.Math.Vector2.ZERO, point) : Phaser.Math.Angle.BetweenPoints(point, Phaser.Math.Vector2.ZERO));
            speeds.push(Phaser.Math.Distance.BetweenPoints(Phaser.Math.Vector2.ZERO, point) * speed);
        }         
      } else {
        switch (emerge) {
          case 0:
            anglesArray.push(angle);
            speeds.push(Phaser.Math.Distance.BetweenPoints(Phaser.Math.Vector2.ZERO, point) * speed);
            break;
          default:
            anglesArray.push(emerge === 1 ? Phaser.Math.Angle.BetweenPoints(Phaser.Math.Vector2.ZERO, point)+angleT*bearingOption : Phaser.Math.Angle.BetweenPoints(point, Phaser.Math.Vector2.ZERO)-+angleT*bearingOption);
            speeds.push(speed)
        }
      }
      cannonPositions.push(point.multiply(aspectRatio).add(realOrigin));
      flyAwayAngles.push(Phaser.Math.RadToDeg(angle + angleT * flyAwayOption));
      angleT += Math.PI*2/numberOfPoints;
    }
  })
  speedArray.push(speeds);
} // end of drawCannons - create cannons from curve function

Two important additions are:

  1. pRotation: this parameter is used to specify the angle of the picture, as opposed to aligning with the direction of the cannon (by default).

  2. flyAwayAngles: an array to hold the angles based on angleT* bearing Option, to be fed to bulletTransform.


The above changes adds a lot more variety to the danmaku types.


I have also renamed the function to drawCannons, in line with the PAINT cannonConfig.type.


Pictures shot by parallel cannons

The original objective to be able to shoot pictures from multi-nway and parallel cannons was simple. The following parameter set creates a x2 parallel cannons spaced 270 pixels apart which shoots downwards from the top of the screen (ie vertical offset -200 pixels from the centre of the danmaku), and shoots stars made up of 36 bullets each, with fixed picture rotation of -90 degrees.


  danmakuPattern.push({
    name: "FALLING STARS",
    danmakuConfig: {
      type: "PARALLEL", 
      countB: 2,
      vOffset: -200,
      width: 270,
      angle: 90,
      switch: 0,
      keepShape: true,
    },
    cannonConfig: {
      class: "DRAW", type: "STAR",
      count:36,
      pRotation: -90,
      fireRate: 3000, 
      size: 100
    },
    bulletConfig: {
      class: "NORMAL" , 
      speed: 100,
      angularVelocity: 180,
      texture: "starSML", frame: 5
    },
  });


And the following parameter set was used to create the danmaku pictured at the top of this post. It creates a 5-way from which stars at shot, angled all at -90 degrees, option=0 means they are shot in the direction of the cannons. There is a STOP&GO bulletTransform where bullets sly for 1.2 seconds, before stopping for 3 seconds, then fly off in directions specified by flyAngleOptions set at 5.


  danmakuPattern.push({
    name: "SPLITTING STAR",
    danmakuConfig: {
      countA: 5,
      switch: 0,
      angle: 90,
    },
    cannonConfig: {
      class: "DRAW", type: "STAR", 
        size: 120,
      pRotation: -90,
      count:36, // for DRAW count is used to define number of bullets to draw each picture
      flyAwayOption: 5, // use this in combination with bulletTransform to get picture to "burst" after stopping
      fireRate: 5000, 
    },
    bulletConfig: {
      class: "NORMAL" ,
      speed: 80,
      texture: "starSML", frame: 1,
    },
    bulletTransform: {
      class: "STOP&GO",
      speed: 80,
      stage1Time: 1200,
      stage2Time: 3000,
      texture: "starSML", frame: 0
    },
  });
      

Re-writing of createStar and creation of new picture codes

I have re-written the code for createStar, added createCircle, createSquare and createTriangle, which are self-explanatory.


I have also written a createPentagram function which simply involves adding 1 line to the revised createStar function.




Reminder to my future self

There's not much added functionality but the time required to complete each post is getting longer as the complexity of these marginal functional improvements is increasing - ho hum. In fact, as I write this post I have realised another limitation in the current "DRAW" function, which is that keepShape=true cannot be used in conjunction with speed initially set to zero with some acceleration, as the speed settings get messed up. This is a topic for the future.


Here is the CodePen for your perusal and comment.





記事: Blog2_Post
bottom of page