I lesson 29, I changed the name of the Polygon danmaku to DRAW and declared my intention to add functionality to draw more than just regular polygons. The original polygon code basically created a Geom.Polygon object, and calculated the cannon firing angles and bullets speeds by lerping through the Polygon object using getPoints method.
In lesson 28, I created a danmaku which fired streams of bullets in parallel. This involved calculating the positions from where to shoot the bullets by using vectors to hold information about the end points of the "line" of shooting positions (Phaser's Geom.Point might be more intuitively obvious, but Phaser's vector object offers a lot of useful functions to manipulate them), then lerping from one end to the other to get points in between the 2 end points, using Phaser's Vector2 object methods. In that post, I did mention in passing that you could achieve exactly the same result by using Phaser's Curves object.
In this post, we utilise this Curves object to extend the functionality of the "DRAW" danmaku to draw stars.
Rewrite of the "Parallel" danmaku code to test
But before we get started on the star code, I have amended the "PARALLEL" danmaku code slightly, to make utilise the Curves object. This is not directly relevant to the star code that we are about to write, except that it makes the code "consistent" (but I did not go as far as factoring any of the code to share with the star code - maybe I will do that one day).
First, I have disaggregated the code so that rather than call one method to create the line and create the cannon related arrays, they are done in 2 steps.
case "PARALLEL":
const line = createLine(this.danmakuSize,
this.referenceAngle,this.danmakuPosition)
cannonsFromLine(
line,
this.referenceAngle,
this.danmakuCount,
this.danmakuRandom,
this.cannonAngles,
this.cannonPositions
);
this.cannonShotSpeeds = this.shotSpeedArray(this.cannonShotType);
break;
The actual functions to create the Curves.Line object and set up the cannon related arrays are as follows. The code is essentially the same as that explained in lesson 28, except split across 2 separate functions. The important bits are the line in blue, which creates a Curves.Line object, and the line in RED which gets points across the Curves.Line object and pushes them into the cannon positions array.
function createLine(length, angle, offset) {
const aa = new Phaser.Math.Vector2().setToPolar(angle + Math.PI/2, length/2).add(offset);
const bb = new Phaser.Math.Vector2().setToPolar(angle - Math.PI/2, length /2).add(offset);
const line = new Phaser.Curves.Line(aa,bb)
return line
}
function cannonsFromLine(line, angle, numberOfPoints, randomFactor, anglesArray, pointsArray) {
const speedsArray = []
const length = line.getLength();
for (let i = 0; i < numberOfPoints; i++) {
const random = Phaser.Math.Between(-randomFactor/2, randomFactor/2) / length;
pointsArray.push(line.getPoint((i+random)/(numberOfPoints - 1)));
anglesArray.push(angle);
}
} // end of parallelCannons function
Utilising Curves.Path object to create a Star
If you look at the documentation of the Curves object (here), there are lots of members, in addition to Line. We are going to utilise a member called "Path", which essentially is an object which allows you to join multiple lines together and treat the result as one object.
Below, is the function to create a star, as a Curves.Path object. It's looks a little bit complicated because of the code to take care of offseting the object (because Phaser's (0,0) is at the top left hand corner) and rotation.
The lines in blue creates the 5 "vertices" of a star using polar coordinates, assuming a "size" of 100 pixels. The lines in red creates the Curves.Path object. The first line substantiates a path object with the initial vertex. After that it loops through the vertices, joining them with curves.Line's. The final line "closes" the star by joining the fifth vertex to the first vertex.
function createStar(heading, offset) {
const angle = heading - Math.PI/2;
const nominalSize = 100;
const vertices = [];
vertices.push(new Phaser.Math.Vector2().setToPolar(-Math.PI / 2, nominalSize).rotate(angle).add(offset));
vertices.push(new Phaser.Math.Vector2().setToPolar(Math.PI / 3, nominalSize).rotate(angle).add(offset)) ;
vertices.push(new Phaser.Math.Vector2().setToPolar(-5/6 * Math.PI, nominalSize).rotate(angle).add(offset)) ;
vertices.push(new Phaser.Math.Vector2().setToPolar(-Math.PI / 6, nominalSize).rotate(angle).add(offset)) ;
vertices.push(new Phaser.Math.Vector2().setToPolar(2 / 3 * Math.PI, nominalSize).rotate(angle).add(offset));
var star = new Phaser.Curves.Path(vertices[0].x,vertices[0].y)
for (let i = 1; i< vertices.length; i++) {
star.lineTo(vertices[i].x, vertices[i].y)
}
star.closePath()
return star;
}
The object created is then passed to the below function which creates the various cannon related arrays.
function cannonsFromCurve(curve, centre, numberOfPoints, speed, anglesArray, speedArray) {
const scaleFactor = speed / 100;
const speeds = [];
const pointsArray = []
for (let i=0; i < numberOfPoints; i++) {
pointsArray.push(curve.getPoint(i/(numberOfPoints - 1)));
anglesArray.push(Phaser.Math.Angle.BetweenPoints(centre, pointsArray[i]));
speeds.push(Phaser.Math.Distance.BetweenPoints(centre, pointsArray[i]) * scaleFactor);
}
speedArray.push(speeds)
}
This meat of the code is very similar to the createLine function, except the random factor, and most importantly, the createLine function does not create the array of shotSpeeds. I am sure I could factor it, but that is for another day.
There are also similarities to the equivalent code for polygons.
function cannonsFromPolygon(polygon, centre, numberOfPoints, speed, anglesArray, speedArray) {
const scaleFactor = speed / 100;
const speeds = [];
const points = polygon.getPoints(numberOfPoints);
//calculate the angles and distances of each point from the origin and store into specified arrays
for (let i = 0; i < numberOfPoints; i++) {
anglesArray.push(Phaser.Math.Angle.BetweenPoints(centre, points[i]));
speeds.push(Phaser.Math.Distance.BetweenPoints(centre, points[i]) *scaleFactor);
}
speedArray.push(speeds)
}
However I noticed that Geom.Polygon offers a getPoints (plural!) method which generates n points in one go, whereas there is no Curves.Path equivalent; in the code above, it essentially "lerps" through the entire path, creating one point at a time. There is a getPoints method for Curves.Path but unlike the polygon associated method which takes the total number of points to be generated as a parameter, the Curves.Path equivalent requires "divisions per curve", ie it will generate n+1 points along each "curve" which makes up the curve - this is likely to confuse (myself) when setting the parameters. I could recreate the createPolygon function to create a Curves object rather than a Polygon object, and that would allow me to factor the function to create the cannon related arrays, but I will leave that for another day.
Now all that is needed is the code to call the new functions.
case "STAR":
const star = createStar(this.rotation, this.danmakuPosition);
cannonsFromCurve(
star, // this is the polygon object on which danmaku pattern will be based
this.danmakuPosition, // this is the "centre" of polygon from which the cannonshot speeds will be calibrated
this.danmakuCount, // number of bullets to create polygon
this.bulletSpeed,
this.cannonAngles, // this is the array to hold the cannon angles - pass by reference
this.cannonShotSpeeds // this is the array to hold the cannon shoot speeds - pass by reference
);
break;
I have coded the functions so that the star displayed in the "correct" direction, when the danmaku is pointing downards (i.e. danmaku angle is set at 90).
That's pretty much it for the new star related code. But I have made one important amendment - that is, I have finally gotten round to setting the depth of the bullet sprites so that they are displayed on "above" the enemy and player characters!
I will not be posting the CodePen today, as I will be continuing this revision to the "DRAW" danmaku, to draw more complex patterns.
Comments