变换

但是,如果我们希望角色可以向左走而不是向右走该怎么办?诚然,我们可以绘制另一组精灵,但我们也可以使用另一种方式在画布上绘图。

我们可以调用scale方法来缩放之后绘制的任何元素。该方法接受两个输入参数,第一个参数是水平缩放比例,第二个参数是竖直缩放比例。

  1. <canvas></canvas>
  2. <script>
  3. let cx = document.querySelector("canvas").getContext("2d");
  4. cx.scale(3, .5);
  5. cx.beginPath();
  6. cx.arc(50, 50, 40, 0, 7);
  7. cx.lineWidth = 3;
  8. cx.stroke();
  9. </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)进行。

变换 - 图1

但是如果我们先旋转 20 度,然后平移原点到(50,50),此次的平移会发生在已经旋转过的坐标系中,因此会有不同的方向。变换发生顺序会影响最后的结果。

我们可以使用下面的代码,在指定的x坐标处竖直反转一张图片。

  1. function flipHorizontally(context, around) {
  2. context.translate(around, 0);
  3. context.scale(-1, 1);
  4. context.translate(-around, 0);
  5. }

我们先把y轴移动到我们希望镜像所在的位置,然后进行镜像翻转,最后把y轴移动到被翻转的坐标系当中相应的位置。下面的图片解释了以上代码是如何工作的:

变换 - 图2

上图显示了通过中线进行镜像翻转前后的坐标系。对三角形编号来说明每一步。如果我们在x坐标为正值的位置绘制一个三角形,默认情况下它会出现在图中三角形 1 的位置。调用filpHorizontally首先做一个向右的平移,得到三角形 2。然后将其翻转到三角形 3 的位置。这不是它的根据给定的中线翻转之后应该在的最终位置。第二次调用translate方法解决了这个问题。它“去除”了最初的平移的效果,并且使三角形 4 变成我们希望的效果。

我们可以沿着特征的竖直中心线翻转整个坐标系,这样就可以画出位置为(100,0)处的镜像特征。

  1. <canvas></canvas>
  2. <script>
  3. let cx = document.querySelector("canvas").getContext("2d");
  4. let img = document.createElement("img");
  5. img.src = "img/player.png";
  6. let spriteW = 24, spriteH = 30;
  7. img.addEventListener("load", () => {
  8. flipHorizontally(cx, 100 + spriteW / 2);
  9. cx.drawImage(img, 0, 0, spriteW, spriteH,
  10. 100, 0, spriteW, spriteH);
  11. });
  12. </script>