top of page
Search
  • cedarcantab

Danmaku using Phaser 3 (lesson 38 Part 1): PAINT re-revisted

Updated: Nov 23, 2021


In this post, we revisit the PAINT danmaku. I have now changed the code for the following:

  • the picture data, rather than being defined in the main code, it now defined in a separate JSON file, and loaded.

  • the pictures can be "fired" from multi-nway, and even PARALLEL and BI_DIRECTIONAL danmaku (which following the revision in the previous post, are sub-types of the nway danmaku, as opposed to a separate danmaku class), whereas before, it could only be fired from a standard nway



But before we get into the main PICTURE danmaku code, some cleaning up was required of the code from previous post.


Some fixing of NWAY code

Single cannon

I knew it was too good to be true. I was surprised to see the code working after such fundamental re-write in the previous post. I found that the getArcAngles function fell apart if it was fed with a single nway cannon (i.e. this.danmakuCountA=1) and the danmakuConfig.angleRange paramter was set to a value less then 360 degrees. So I have amended the following to include a check for this condition.

switch (nways) {
    case 1: // if number of nways is 1 then simply called the getAngles function once      
      directions = (cannonsInNway === 1) ? [heading+offset] : getAngles(heading+offset,nwayRange, cannonsInNway);
      break;

repeatShotsCounter

Because of the way the repeatShotsCounter was declared, things fell apart from single shots (hardly ever used - in my example, only used by the player cannon). I have now created a new local variable called this.repeatShotsCount and code has been added to check for existence of a repeat event timer.


Offset for BI_DIRECTIONAL

I hadn't included any code to allow offsetting of shot origin for bi-directional. This has now been added.


Further use of higher order array functions

The main code of the getArcAngles for multi-nways was as follows (if anything, this is being documented for my own benefit since I do not necessarily find higher order functions easier to read than traditional for..loops).


const nwaysIndex = getAngles(heading+offset, totalRange, nways);
for (let j=0; j < nwaysIndex.length; j++) {     
  directions.push(...getAngles(nwaysIndex[j],nwayRange,cannonsInNway));        
}

this has been replaced by:

directions = getAngles(heading+offset, totalRange, nways)
  .reduce((acc,j)=>[...acc, ...getAngles(j,nwayRange,cannonsInNway)],[])

I have also replaced the many for.. loop arrays looping through angles arrays, with forEach method, so for example, the following code:


for (let j = 0; j < directions.length; j++) {
  const angle = directions[j];
  const shift = new Phaser.Math.Vector2(vOffset,hOffset).rotate(angle);
  if (vOffset !==0 || hOffset !==0) {        
    originsArray.push(new Phaser.Math.Vector2(0,0).add(shift));
  }
  spokesArray.push(angle);
}

becomes the following:

directions.forEach(angle => {
  const shift = new Phaser.Math.Vector2(vOffset,hOffset).rotate(angle);
  if (vOffset !==0 || hOffset !==0) {        
    originsArray.push(new Phaser.Math.Vector2(0,0).add(shift));
  }
  spokesArray.push(angle);
})

Changing the "scope" of the getArcAngles and setUpCannons functions

And here comes the biggest changes. I had to amend the getArcAngles and setUpCannons functions again. And changed the name of getArcAngles function to getCannonSpokes. In particular, in the previous version of the code, the getArcAngles function went as far as calculating the cannon angles for multi-nway. Then, the setUpCannons function would get the result, and calculate the angles for the additional cannons for PARALLEL and Bi-directional patterns, before calculating the cannon origins and finally the shot speeds.


In the new version of the code, the revised getArcAngles function, now called getCannonSpokes function goes as far as calculating the angles for ALL cannons (hence the change in name) and partly the cannon origins (offsets, based on origin being 0,0 - the danmaku centre position is added within the setUpCannons function.


This makes it easier to implement the main object of this post - which is to be able to fire "pictures" from not just nway, but also multi-nways, as well as parallel and bi-directional patterns.


The entire revised getCannonSpokes function is as below. The blue lines are the main ones - they have been essentially lifted from setUpCannons function of the pervious version.

function getCannonSpokes({
  danmakuType = "NWAY",
  heading, 
  vOffset = 0,
  hOffset = 0,
  numberOfPoints = 1,
  cannonsInNway=1, nwayRange = Math.PI*2, 
  nways = 1, totalRange = Math.PI*2,
  offset = 0,
  width,
  spokesArray, // output array to hold cannon angles
  originsArray // output array to hold cannon positions
}) 
{

  let directions = []; // this is the working array to hold the cannon angles --> pushed to spokesArray at end of this function
  
  // function within this function to get angles across an arc
  function getAngles(centreAngle, range, points) {
    let tempArray = [];
    switch (range) {
    case Math.PI * 2:
        tempArray = new Array(points).fill(centreAngle).map((item,index) => Phaser.Math.Angle.Wrap(item + (index * Math.PI * 2) / points))
      break;
    default:
        tempArray = new Array(points).fill(centreAngle).map((item,index) => Phaser.Math.Angle.Wrap(item + (index / (points - 1) - 0.5) * range))
    }
    return tempArray
  }
  
  // this is the main body of this function
  switch (nways) {
    case 1: // if number of nways is 1 then simply called the getAngles function once      
      directions = getAngles(heading + offset, nwayRange, cannonsInNway);
      break;
    default: // otherwise call getAngles function, x nways times
      directions = getAngles(heading+offset, totalRange, nways)
        .reduce((acc,j)=>[...acc, ...getAngles(j,nwayRange,cannonsInNway)],[])
  } 

  // do a bit more processing for PARALLEL and BI-DIRECTIONAL danmaku
  switch (danmakuType) {
    case "PARALLEL":
      const line = createLine(0, width); 
      directions.forEach(angle => {
        const shift = new Phaser.Math.Vector2(vOffset, hOffset).rotate(angle);
        for (let i=0; i < numberOfPoints; i++) {
          const point = line.getPoint(i / (numberOfPoints - 1)).rotate(angle).add(shift);
          originsArray.push(point);
          spokesArray.push(angle);
        }
      });
      break;        
    case "BI_DIRECTIONAL":
      spokesArray.push(...directions,...directions.map(x=>-x));
      spokesArray.forEach(angle => {
        const shift = new Phaser.Math.Vector2(vOffset,hOffset).rotate(angle);
        if (vOffset !==0 || hOffset !==0) {        
          originsArray.push(new Phaser.Math.Vector2(0,0).add(shift));
        }
      })
      break;
    default:
      directions.forEach(angle => {
        const shift = new Phaser.Math.Vector2(vOffset,hOffset).rotate(angle);
        if (vOffset !==0 || hOffset !==0) {        
          originsArray.push(new Phaser.Math.Vector2(0,0).add(shift));
        }
        spokesArray.push(angle);
      })
  }

} // end of getCamnonSpokes function

And that's it for this post. I will document the rest of the revised PAINT danmaku code in the next post.





記事: Blog2_Post
bottom of page