变换
但是,如果我们希望角色可以向左走而不是向右走该怎么办?诚然,我们可以绘制另一组精灵,但我们也可以使用另一种方式在画布上绘图。
我们可以调用scale
方法来缩放之后绘制的任何元素。该方法接受两个输入参数,第一个参数是水平缩放比例,第二个参数是竖直缩放比例。
<canvas></canvas>
<script>
let cx = document.querySelector("canvas").getContext("2d");
cx.scale(3, .5);
cx.beginPath();
cx.arc(50, 50, 40, 0, 7);
cx.lineWidth = 3;
cx.stroke();
</script>
因为调用了scale
,因此圆形长度变为原来的 3 倍,高度变为原来的一半。scale
可以调整图像所有特征,包括线宽、预定拉伸或压缩。如果将缩放值设置为负值,可以将图像翻转。由于翻转发生在坐标(0,0)
处,这意味着也会同时反转坐标系的方向。当水平缩放 –1 时,在x
坐标为 100 的位置画出的图形会绘制在缩放之前x
坐标为 –100 的位置。
为了翻转一张图片,只是在drawImage
之前添加cx.scale(–1,–1)
是没用的,因为这样会将我们的图片移出到画布之外,导致图片不可见。为了避免这个问题,我们还需要调整传递给drawImage
的坐标,将绘制图形的x
坐标改为 –50 而不是 0。另一个解决方案是在缩放时调整坐标轴,这样代码就不需要知道整个画布的缩放的改变。
除了scale
方法还有一些其他方法可以影响画布里坐标系统的方法。你可以使用rotate
方法旋转绘制完的图形,也可以使用translate
方法移动图形。毕竟有趣但也容易引起误解的是这些变换以栈的方式工作,也就是说每个变换都会作用于前一个变换的结果之上。
如果我们沿水平方向将画布平移两次,每次移动 10 像素,那么所有的图形都会在右方 20 像素的位置重新绘制。如果我们先把坐标系的原点移动到(50, 50)
的位置,然后旋转 20 度(大约0.1π
弧度),此次的旋转会围绕点(50,50)
进行。
但是如果我们先旋转 20 度,然后平移原点到(50,50)
,此次的平移会发生在已经旋转过的坐标系中,因此会有不同的方向。变换发生顺序会影响最后的结果。
我们可以使用下面的代码,在指定的x
坐标处竖直反转一张图片。
function flipHorizontally(context, around) {
context.translate(around, 0);
context.scale(-1, 1);
context.translate(-around, 0);
}
我们先把y
轴移动到我们希望镜像所在的位置,然后进行镜像翻转,最后把y
轴移动到被翻转的坐标系当中相应的位置。下面的图片解释了以上代码是如何工作的:
上图显示了通过中线进行镜像翻转前后的坐标系。对三角形编号来说明每一步。如果我们在x
坐标为正值的位置绘制一个三角形,默认情况下它会出现在图中三角形 1 的位置。调用filpHorizontally
首先做一个向右的平移,得到三角形 2。然后将其翻转到三角形 3 的位置。这不是它的根据给定的中线翻转之后应该在的最终位置。第二次调用translate
方法解决了这个问题。它“去除”了最初的平移的效果,并且使三角形 4 变成我们希望的效果。
我们可以沿着特征的竖直中心线翻转整个坐标系,这样就可以画出位置为(100,0)
处的镜像特征。
<canvas></canvas>
<script>
let cx = document.querySelector("canvas").getContext("2d");
let img = document.createElement("img");
img.src = "img/player.png";
let spriteW = 24, spriteH = 30;
img.addEventListener("load", () => {
flipHorizontally(cx, 100 + spriteW / 2);
cx.drawImage(img, 0, 0, spriteW, spriteH,
100, 0, spriteW, spriteH);
});
</script>