In the previous post I utilised Phaser's Curves.Path object to draw a star, which consisted of straight lines connecting 5 vertices. In fact, Phaser's Curves.Path object can handle a lot more - specifically, it can handle...as the name suggests, curves. We will use this feature to draw playing card pictures; namely heart, spade, club and diamond.
The members (?) or methods we will use to draw such more complex shapes are: Ellipses, Quadratic Bezier curves, and Cubic Bezier curves. If you want to understand Bezier curves in detail, there are plenty of excellent tutorials on the internet - for our purposes, all we need to know is that you can create curves that bend in the manner you want by setting control points. You can probably figure out the concepts by looking at the code for creating a Curves.Path object for Heart, below (ignore the rotate(angle) and add(centre) for ease of understanding).
function createHeart(heading, offset){
const angle = heading - Math.PI/2;
const nominalSize = 100;
const height = nominalSize;
const width = nominalSize;
const topCurveHeight = nominalSize * 0.3;
const centre = new Phaser.Math.Vector2(offset.x, offset.y).add(new Phaser.Math.Vector2(0,-nominalSize/2).rotate(angle));
const start = new Phaser.Math.Vector2(0, topCurveHeight).rotate(angle).add(centre);
const heart = new Phaser.Curves.Path(start.x,start.y);
// top left curve
heart.cubicBezierTo(
new Phaser.Math.Vector2(0,0).rotate(angle).add(centre),
new Phaser.Math.Vector2(-width/2,0).rotate(angle).add(centre),
new Phaser.Math.Vector2(-width / 2, topCurveHeight ).rotate(angle).add(centre)
);
// bottom left curve
heart.cubicBezierTo(
new Phaser.Math.Vector2(- width / 2, (height + topCurveHeight) / 2).rotate(angle).add(centre),
new Phaser.Math.Vector2(0, (height + topCurveHeight) / 2).rotate(angle).add(centre),
new Phaser.Math.Vector2( 0, height).rotate(angle).add(centre)
);
// bottom right curve
heart.cubicBezierTo(
new Phaser.Math.Vector2(0, (height + topCurveHeight) / 2).rotate(angle).add(centre),
new Phaser.Math.Vector2(width / 2, (height + topCurveHeight) / 2).rotate(angle).add(centre),
new Phaser.Math.Vector2(width / 2, topCurveHeight ).rotate(angle).add(centre)
);
// top right curve
heart.cubicBezierTo(
new Phaser.Math.Vector2(width / 2, 0).rotate(angle).add(centre) ,
new Phaser.Math.Vector2(0,0).rotate(angle).add(centre),
new Phaser.Math.Vector2(0, topCurveHeight ).rotate(angle).add(centre)
);
return heart
} // end of function to create Heart object
In creating the above code, and the code for the other shapes, I have leveraged extensively the code published here which explains how to draw playing card pictures to canvas using Javascript, but using the Phaser equivalent of the canvas API.
For the Club shape, I made changes to the code in terms of the order in which the curves are drawn and draw arcs as opposed to complete circles - the reference code draws overlapping complete circles and fills them in. Also the way the Phaser curves ellipseTo function works appears to be quite a bit different from the standard Javascript canvas arc function.
function createClub(heading, offset){
const angle = heading - Math.PI/2;
const nominalSize = 100;
const height = nominalSize;
const width = nominalSize;
const circleRadius = width * 0.25;
const bottomWidth = width * 0.5;
const bottomHeight = height * 0.35;
const centre = new Phaser.Math.Vector2(offset.x, offset.y).add(new Phaser.Math.Vector2(0,-nominalSize *0.3).rotate(angle));
const start = new Phaser.Math.Vector2(0, circleRadius *2).rotate(angle).add(centre);
const club = new Phaser.Curves.Path(start.x, start.y)
// bottom left circle
club.ellipseTo(circleRadius,circleRadius,0,-90,false,Phaser.Math.RadToDeg(angle));
// top circle
club.ellipseTo(circleRadius, circleRadius,180,0, false,Phaser.Math.RadToDeg(angle));
// bottome right circle
club.ellipseTo(circleRadius,circleRadius,-90,180,false,Phaser.Math.RadToDeg(angle));
// bottom part of club
club.moveTo(new Phaser.Math.Vector2(0,(height * 0.6)).rotate(angle).add(centre));
club.quadraticBezierTo(
new Phaser.Math.Vector2(0, height).rotate(angle).add(centre) ,
new Phaser.Math.Vector2( - bottomWidth / 2, height).rotate(angle).add(centre)
)
club.lineTo(new Phaser.Math.Vector2(bottomWidth / 2,height).rotate(angle).add(centre));
club.quadraticBezierTo(
new Phaser.Math.Vector2(new Phaser.Math.Vector2(0,height).rotate(angle).add(centre)),
new Phaser.Math.Vector2(new Phaser.Math.Vector2(0,(height * 0.6)).rotate(angle).add(centre))
);
return club
}
Similarly for the spade shape, I had to change the code quite a bit because the original code assumes that the shape is filled in.
function createSpade(heading, offset){
const angle = heading - Math.PI/2;
const nominalSize = 100;
const height = nominalSize;
const width = nominalSize;
var bottomWidth = width * 0.7;
var topHeight = height * 0.7;
var bottomHeight = height * 0.3;
const centre = new Phaser.Math.Vector2(offset.x, offset.y).add(new Phaser.Math.Vector2(0,-nominalSize/2).rotate(angle));
const spade = new Phaser.Curves.Path().moveTo(centre);
// top left of spade
spade.cubicBezierTo(
new Phaser.Math.Vector2(0, topHeight / 2).rotate(angle).add(centre),// control point 1
new Phaser.Math.Vector2(- width / 2, topHeight / 2).rotate(angle).add(centre), // control point 2
new Phaser.Math.Vector2(- width / 2, topHeight).rotate(angle).add(centre) // end point
);
// bottom left of spade
spade.cubicBezierTo(
new Phaser.Math.Vector2( - width / 2, topHeight * 1.3).rotate(angle).add(centre), // control point 1
new Phaser.Math.Vector2(0, topHeight * 1.3).rotate(angle).add(centre), // control point 2
new Phaser.Math.Vector2(0, topHeight).rotate(angle).add(centre) // end point
);
// bottom right of spade
spade.cubicBezierTo(
new Phaser.Math.Vector2(0, topHeight * 1.3).rotate(angle).add(centre), // control point 1
new Phaser.Math.Vector2(width / 2, topHeight * 1.3).rotate(angle).add(centre), // control point 2
new Phaser.Math.Vector2( width / 2, topHeight).rotate(angle).add(centre) // end point
);
// top right of spade
spade.cubicBezierTo(
new Phaser.Math.Vector2(width / 2, topHeight / 2).rotate(angle).add(centre), // control point 1
new Phaser.Math.Vector2(0, topHeight / 2).rotate(angle).add(centre), // control point 2
new Phaser.Math.Vector2(0, 0).rotate(angle).add(centre) // end point
);
spade.moveTo(new Phaser.Math.Vector2(0, topHeight).rotate(angle).add(centre))
spade.quadraticBezierTo(
new Phaser.Math.Vector2(0, topHeight + bottomHeight).rotate(angle).add(centre), // control point
new Phaser.Math.Vector2( - bottomWidth / 2, topHeight + bottomHeight).rotate(angle).add(centre) // end point
);
spade.lineTo(new Phaser.Math.Vector2(bottomWidth / 2, topHeight + bottomHeight).rotate(angle).add(centre));
spade.quadraticBezierTo(
new Phaser.Math.Vector2(0, topHeight + bottomHeight).rotate(angle).add(centre), // control point
new Phaser.Math.Vector2(0, topHeight).rotate(angle).add(centre) // end point
);
return spade
} // end of function to create spade object
Diamond is the simplest of them all. I have put in a width to height ratio of 1.4, since without, it just looks like a square. For all the other shapes, the height and width is set to 1:1.
function createDiamond(heading, offset){
const angle = heading - Math.PI/2;
const nominalSize = 100;
const height = nominalSize;
const width = nominalSize / 1.4
const centre = new Phaser.Math.Vector2(offset.x, offset.y).add(new Phaser.Math.Vector2(0,-nominalSize/2).rotate(angle));
const diamond = new Phaser.Curves.Path().moveTo(centre);
// top left edge
diamond.lineTo(new Phaser.Math.Vector2(-width/2, height/2).rotate(angle).add(centre));
// bottom left edge
diamond.lineTo(new Phaser.Math.Vector2(0, height).rotate(angle).add(centre));
// bottom right edge
diamond.lineTo(new Phaser.Math.Vector2(width/2, height/2).rotate(angle).add(centre));
// closing the path automatically creates
// the top right edge
diamond.closePath();
return diamond
} // end of function to create diamond object
And that's it for this post. I will not publish the CodePen, because I have one other addition to make to the DRAW danmaku code.
Comments