This has been a huge re-write. However, not much to show for it in terms of new functionality. For the sake of posting a picture, I have added a functionality to shoot bullets offset from the centre from a standard Nway, and an option to "enforce" the bullet heading in a certain direction that is different from the cannon angle. Combing rotating nway with the forcing the bullet heading at 90 degrees generates patterns like below.
Anyway, onto an explanation of the re-write.
Re-writing the getArcAngles function
I have redone all of the code relating to NWAY, the most basic and most important of all danmaku patterns. Fundamental to this was the re-write of the getArcAngles function which is used to calculate the angles of each cannon space around a circle, or part of a circle.
It was a long time ago when I wrote the getArcAngles function. The danmaku patterns back then were very limited. The number of parameters has since then ballooned. The old version of the code takes only 4 parameters: heading, points, angleRange, offset.
function getArcAngles({
heading,
cannonsInNway=1, nwayRange=Math.PI*2,
nways=1,totalRange=Math.PI*2,
offset=0,
outputArray1
})
{
// this returns array of angles across an arc, in radians, centered around heading
let directions = [];
// function within the getArcAngles to get angles!!!
function getAngles(centre, range, points) {
let tempArray = [];
switch (range) {
case Math.PI * 2:
tempArray = new Array(points).fill(centre).map((item,index) => Phaser.Math.Angle.Wrap(item + (index * Math.PI * 2) / points))
break;
default:
tempArray = new Array(points).fill(centre).map((item,index)=>Phaser.Math.Angle.Wrap(item + (index / (points - 1) - 0.5) * range))
}
return tempArray
}
// this is the main body of getArcAngles 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
const nwaysIndex = getAngles(heading+offset, totalRange, nways);
for (let j=0; j < nwaysIndex.length; j++) {
directions.push(...getAngles(nwaysIndex[j],nwayRange,cannonsInNway));
}
}
outputArray1.push(...directions);
} // end of getArcAngles function
The new version of the code takes 7 parameters, including the outputArray (ie you pass the array that you want to hold the angles, as opposed to the function returning an array), as shown by the following code within the fireShot() method calls the above function.
default:
//this is NWAY - first get the "heading" angles of all the cannons around a circle/part of circle, ie the "spokes"
getArcAngles({
heading: this.referenceAngle,
cannonsInNway: this.danmakuCountA, nwayRange: this.cannonAngleRangeRef,
nways: this.danmakuMultiple, totalRange: this.danmakuAngleRange,
offset: this.danmakuAOffset,
outputArray1: cannonIndex
});
Some key variables have been changed.
this.danmakuCount to this.danmakuCountA - this is used to determine the number of cannons in each nway group. There is also now a this.danmakuCountB, to determine the number of parallel cannons to set up at the end of each spoke of an nway
this.danmakuMultiple is now used to determine how many nways.
Now able to deal with multiple nways as standard
The original version was written with the single nway in mind. The multiple nway came along later, and I had written the multiple nway such that the getArcAngle was called x multiple times. In the new version of the code, the function creates cannon angles for multiple nway by default. A single nway is just a multiple nway, with multiple set to 1. This means that, whereas in the past "MULTIPLE_NWAY" and "NWAY" were two separate danmaku classes, they are now one.
No more separate GAP_CIRCLE danmaku class
GAP_CIRCLE is simply a nway with the primary cannon angle facing the opposite direction. Rather than maintain the separate danmaku class "GAP_CIRCLE", I have now added a new parameter called danmakuConfig.aOffset = this.danmakuAOffset, which is used to "rotate" the entire group of cannons by that angle - by setting this to 180, you can now flip the nway hence create GAP_CIRCLE; hence no more separate GAP_CIRCLE danmaku class.
setUpCannon functions to replace the shotSpeeds Array method
In the previous version of the code, the this.anglesArray was set directly by the getArcAngles function called from the fireShot method. In the new version, a temporary array called cannonIndex is set up to hold the "spokes" information. Then that is fed to the setUpCannons (newly created) to set up all the cannon releated arrays that hold the angles (this.cannonAngles), shot speeds (this.cannonShotSpeeds), and shot origin (this.cannonPositions), to replace the shotSpeedsArray function which used to set up only the bulletSpeeds for each cannon.
function setUpCannons({
danmakuType = "NORMAL",
shotType,
cannonIndex,
centre,
vOffset = 0,
hOffset = 0,
shotsCount,
cannonCount,
bulletSpeed,
counter,
shotAcceleration,
width=100,
numberOfPoints,
anglesArray,
pointsArray,
speedArray
})
{
switch (danmakuType) {
case "PARALLEL":
const line = createLine(0, width);
for (let j=0; j<cannonIndex.length; j++) {
const angle = cannonIndex[j];
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(centre).add(shift);
pointsArray.push(point);
anglesArray.push(angle);
}
}
break;
case "BI_DIRECTIONAL":
anglesArray.push(...cannonIndex,...cannonIndex.map(x=>-x));
break;
default:
for (let j = 0; j < cannonIndex.length; j++) {
const angle = cannonIndex[j];
const shift = new Phaser.Math.Vector2(vOffset,hOffset).rotate(angle);
if (vOffset !==0 || hOffset !==0) {
pointsArray.push(new Phaser.Math.Vector2(0,0).add(centre).add(shift));
}
anglesArray.push(angle);
}
}
switch (shotType) {
case "OVERTAKE":
speedArray.push(new Array(anglesArray.length).fill(bulletSpeed + (shotsCount - counter) * shotAcceleration));
break;
case "SPREAD":
speedArray.push(...new Array(cannonCount).fill(bulletSpeed).map((item,i) => new Array(anglesArray.length).fill(item+shotAcceleration*i)))
break;
default:
// "NORMAL"
speedArray.push(new Array(anglesArray.length).fill(bulletSpeed));
}
} // end of setUpCannons
BI_DIRECTIONAL no longer a separate danmaku class but danmaku type of NWAY
The BI_DIRECTIONAL danmaku was a separate danmaku class in the previous version of the code. However, the base of this class is essentially an NWAY. Hence I have "relegated" this to a danmakutype of Nway danmakuclass.
Looking back at the code, it is amazingly convoluted. All it requires it for every "spoke" of a standard nway, you need to generate an pairing spoke whose angle is 360 degrees minus the cannon angle of the original spoke; ie the paired cannon rotates in the opposite direction, and that is what the original code, shown below is doing.
case "BI_DIRECTIONAL":
for (let i = 0; i < this.danmakuCount; i++) {
this.cannonAngles.push(this.referenceAngle + i * ((Math.PI * 2) / this.danmakuCount));
this.cannonAngles.push(Math.PI * 2 -this.referenceAngle +i * ((Math.PI * 2) / this.danmakuCount));
} // end of for loop
However, 360 degrees minus angle is simply minus the angle (as 360 degrees is same as 0 degrees!), ie it is simply the cannonIndex + the cannonIndex with its elements with their signs reversed.
As the "original" spokes are now calculated by the getArcAngles function and assigned to cannonIndex, with the use of JS higher order functions, the new code becomes this!
case "BI_DIRECTIONAL":
anglesArray.push(...cannonIndex,...cannonIndex.map(x=>-x));
Comments